ckb-session-kit 0.0.0-stage → 2.0.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,337 @@
1
+ # Changelog
2
+
3
+ This project follows [semantic versioning](https://semver.org/). The session lock is
4
+ deployed on CKB **testnet only**; there is no mainnet deployment yet.
5
+
6
+ ## 2.0.0 (2026-11-03)
7
+
8
+ ### Upgrading from 1.0.0
9
+ Every 1.0.0 export is still there with the same signature; new parameters are
10
+ optional. Some calls that 1.0.0 accepted are now refused, on purpose:
11
+ - `assertSigningLock`, `openSession`, `openSubscription` refuse an owner (or
12
+ charger) lock that opens with no signature: PW-Lock (its deployed build is
13
+ anyone-can-pay) and the Nostr lock in proof of work mode
14
+ (`reason: "owner-lock-unsafe"`; no option lets these through). Sessions
15
+ already open under such a lock should be closed or recovered now.
16
+ - They also refuse configurations not reviewed: Omnilock auth 0x07 or mode bits
17
+ other than time-lock (0x04) and supply (0x08), secp256k1, multisig or Nostr
18
+ locks with args their code would not accept, and any unknown lock
19
+ (`reason: "owner-lock-unreviewed"`). Pass `allowUnreviewedOwnerLock: true`
20
+ once you have checked the lock to get the 1.0.0 behaviour back, with a warning;
21
+ for the creator's charger lock in `openSubscription`, pass
22
+ `allowUnreviewedChargerLock: true`.
23
+ - Anyone-can-pay, PW-Lock and always-success referenced by data hash
24
+ (`data`, `data1`, `data2`) are refused like their type-hash versions.
25
+ - A send whose outcome cannot be checked throws `SendOutcomeUnknownError`
26
+ (with the tx hash); it is never retried, so check the hash before resending.
27
+ - `openSession` and `openSubscription` refuse `TESTNET_LEGACY_DEPLOYMENTS`,
28
+ `hashType: "data"`, and a deployment whose cell dep does not hold its code on
29
+ the client's network (one `getCellLive` per client and deployment).
30
+ - `closeSession` refuses a signer whose lock is not the session's owner lock.
31
+ - `openSession` with no rate limit, or `minInterval` 1, calls `console.warn`
32
+ (or `opts.onWarning`).
33
+ - `SessionPolicyError` and `SessionBalanceError` carry a `reason`; code that
34
+ matched error messages should match `reason` instead.
35
+
36
+ The wallet matrix's locks (secp256k1, Omnilock 0x12 / Bitcoin / Dogecoin,
37
+ Nostr signing mode, JoyID) all pass unchanged.
38
+
39
+ ### Fixed (Week 6 adversarial review, 2 Nov)
40
+ - Token subscriptions: look-alike cells typed `xUDT(token.args ++ extra)`
41
+ (xUDT accepts them, anyone can create them with 0 units, and the indexer's
42
+ type filter matched them by prefix) counted toward `MAX_SUBSCRIPTION_CELLS`
43
+ but were never swept. The filter now asks for the exact script length, only
44
+ cells really of the subscription's kind count, raw reads are bounded, and
45
+ `clearPlantedCells` and `cancelSubscription` sweep spendable look-alikes back
46
+ to the subscriber (M1).
47
+ - A status or a charge counted every recent transaction under the lock toward
48
+ its budget of 64, creations by anyone included, and before the age check.
49
+ Now only spends count, an entry older than the period is never counted, and
50
+ creation-only entries are paged past under their own budget of 1024
51
+ (`MAX_CREATED_HISTORY`); past either budget it still refuses (M2).
52
+ - `clearPlantedCells`: sweeps at most 30 cells per transaction, so a status
53
+ never dates the cleanup as a charge (L3); with cells left unread, sweeps the
54
+ planted cells it read instead of refusing (L1); marks its transaction with
55
+ `CLEAR_PLANTED_MEMO` in the subscriber's own witness, so a status does not
56
+ take it for a cancel and a charge of the whole balance stays paid (L2).
57
+ - `signAndSend` throws `SendOutcomeUnknownError` (new export) when a send fails
58
+ and the status lookup fails too, instead of rethrowing the send's error: a
59
+ `Dead` answer from CCC's fallback node after a lost reply was retried and
60
+ could pay twice (L4). It is never retried.
61
+ - Payout cells under `payTo` are listed by the indexer's filter (plain and
62
+ empty, or exactly the token with 16 data bytes), so junk planted under an
63
+ anyone-can-pay address no longer pushes the real cells out of reach (L5).
64
+ - `assertSigningLock` refuses anyone-can-pay, PW-Lock and always-success by the
65
+ data hash of their deployed binaries under every data hash_type (`data`,
66
+ `data1`, `data2`), not only by type id (L6).
67
+
68
+ ### Changed (Week 6 adversarial review, 2 Nov)
69
+ - `openSubscription` checks the creator's charger lock with its own opt-in,
70
+ `allowUnreviewedChargerLock`; `allowUnreviewedOwnerLock` now covers the
71
+ subscriber's lock only (I1).
72
+ - `openSession` raises `no-rate-limit` for `minInterval` 1 too (I3).
73
+ - The testnet scripts keep `.env.smoke` at the repository root whatever the
74
+ current directory (I4).
75
+ - Docs: a multisig with a since or an Omnilock time-lock also delays the
76
+ owner's sweep (I2).
77
+
78
+ ### Added (mentor review, tracker #47, 2 Nov)
79
+ - Payout cell contention. A top-up (`spendInSession` with `topUp`,
80
+ `chargeSubscription`, `openSubscription`) picks the recipient's cell with
81
+ `pickPayoutCell`: confirmed cells first, shuffled, and the first free one of
82
+ up to `MAX_POOL_CHECKS` (4) cells checked with `get_live_cell` and
83
+ `include_tx_pool`, so a cell another payment in the node's pool spends is
84
+ skipped. At most `MAX_PAYOUT_CELLS` (64) of the recipient's cells are read.
85
+ - `spendInSession` and `chargeSubscription` take `retry: { attempts, delayMs }`
86
+ (`RetryOptions`, `DEFAULT_RETRY`: 3 attempts, 1000 ms doubling, with jitter).
87
+ A send the node refuses for a spent input (`Dead`, `Unknown`, or an RBF
88
+ refusal, `poolConflict`) is built again with another payout cell; the cell a
89
+ `Dead` refusal names is not picked again. It used to be retried once, with no
90
+ wait, and only with a client cache. A send whose outcome is unknown is still
91
+ never retried.
92
+ - `SessionBalanceError` reason `"payout-busy"`: every payout cell checked is
93
+ spent in the pool and none is left. Nothing is sent; it is retried like a
94
+ refusal. The demo's charge route answers it with 503 and retry.
95
+ - `createPayoutCells(funder, payTo, { count, capacity?, token? })` gives an
96
+ anyone-can-pay address more cells to top up. `npm run setup:creator` uses it
97
+ and now gives the demo's creator eight cells (was three).
98
+ - `clearPlantedCells(subscriber, deployment, subscription)`: the subscriber
99
+ sweeps cells a third party created under the subscription's lock back to
100
+ itself, keeping the subscription's own cells, so it no longer has to cancel
101
+ and open again past `MAX_SUBSCRIPTION_CELLS`.
102
+
103
+ ### Changed (mentor review, 2 Nov)
104
+ - `assertSigningLock` accepts only reviewed owner lock configurations:
105
+ secp256k1, multisig (v1, v2), Omnilock with auth 0x00-0x06 or 0x12 and no
106
+ mode other than time-lock or supply, the Nostr lock in signing mode
107
+ (`args[0]` = 0), and JoyID. The wallet matrix's locks all still pass.
108
+ - PW-Lock is now refused. The build deployed on mainnet and testnet is
109
+ lay2dev/pw-lock's anyone-can-pay variant: with no signature in the witness it
110
+ unlocks by payment, whatever the args, so anyone could sweep every session.
111
+ - The Nostr lock in proof of work mode (`args[0]` not 0) is now refused: it
112
+ unlocks with work, not the holder's key.
113
+ - Anyone-can-pay is refused by code hash on both networks (and the older
114
+ deployments pw-core lists), whatever the client's network; always-success
115
+ too. These refusals carry `reason: "owner-lock-unsafe"`.
116
+ - Locks and configurations not reviewed (Omnilock auth 0xFC-0xFE, other Omnilock
117
+ auth flags or mode bits, the ckb-proxy-locks, unknown locks, args the lock's
118
+ code would not accept, multisig v2 beta) are refused with
119
+ `reason: "owner-lock-unreviewed"`. Omnilock auth 0x07 and unknown Omnilock mode
120
+ bits used to pass.
121
+ - Subscription cells are listed with the indexer's type filter (no type, or the
122
+ subscription's token), so cells under any other type script no longer count
123
+ toward `MAX_SUBSCRIPTION_CELLS`.
124
+ - The last charge is the newest spend with an input under the charger's lock,
125
+ not any spend: a `clearPlantedCells` does not delay the next charge. A spend
126
+ that cannot be told apart still counts as a charge.
127
+ - Chained payments are not used; the README says why.
128
+
129
+ ### Added
130
+ - `openSession` warns when a session has no rate limit (`minInterval` left out
131
+ or 0): every spend is within `maxPerTx`, but spends can repeat every block
132
+ until the session is empty. The warning (`SessionWarning`, code
133
+ `"no-rate-limit"`) goes to the new `opts.onWarning` before anything is
134
+ signed, which may throw to stop the opening, or to `console.warn` without one.
135
+ The demos' sessions open with no rate limit, so they now warn in the console.
136
+ - 23 CKB-VM tests of aggregate outflow across inputs (mentor review, 2 Nov):
137
+ cells with the same args share one limit per transaction (CKB and token), a
138
+ subscription's merged cells pay one price, cells with other args are checked
139
+ on their own, and owner mode sweeps several cells at once. No fault found; the
140
+ lock is unchanged.
141
+ - README and SECURITY-EVIDENCE: browser expiry revokes nothing on chain (funds
142
+ stay at risk until the owner's sweep is confirmed); a period is a block count,
143
+ not a fixed duration; the aggregate-outflow behaviour.
144
+ - `allowUnreviewedOwnerLock` (`SigningLockOptions`) on `assertSigningLock`,
145
+ `openSession` and `openSubscription`: lets an unreviewed owner or charger lock
146
+ through with a `console.warn`. It never lets an "owner-lock-unsafe" lock
147
+ through.
148
+ - `SessionPolicyReason` gains `"owner-lock-unsafe"` and `"owner-lock-unreviewed"`.
149
+ - `SessionBalanceError.reason` (`SessionBalanceReason`): `"no-fee-cell"` or
150
+ `"no-payout-cell"` when the cells missing are the payer's or the recipient's own
151
+ (a charger with no fee cell, a `payTo` with no cell to top up), so a server can
152
+ tell them from the subscription's state without matching messages. The demo's
153
+ charge route now uses it.
154
+ - `SessionPolicyError.reason` (`SessionPolicyReason`): `"payto-not-anyone-can-pay"`
155
+ when a charge needs to top up `payTo` and `payTo` is not anyone-can-pay. The
156
+ demo's charge route answers it with 503 (the deployment's setup, not the
157
+ subscription's state) instead of 409.
158
+
159
+ ### Fixed (audit round 2, 31 Oct)
160
+ - Demo: enforced CSP now also sets `object-src 'none'`, `base-uri 'none'` and
161
+ `form-action 'self'`; the full policy (scripts, styles, images, testnet RPC and
162
+ JoyID aggregator only) is sent as `Content-Security-Policy-Report-Only`.
163
+ - CI: GitHub actions pinned to commit SHAs, checkouts with
164
+ `persist-credentials: false`.
165
+ - `openSession` and `openSubscription` refuse `TESTNET_LEGACY_DEPLOYMENTS`
166
+ (`assertCurrentDeployment`, new). `openSession` used to accept them: a token
167
+ session under the 13 Oct lock was stuck for good (that code refuses 128/160-byte
168
+ args before owner mode), and new sessions under the 8 Oct one lacked errors
169
+ 16-18. Spending, closing, recovering, charging and cancelling still accept old
170
+ deployments, so funds under them can come back.
171
+ - Before building an opening, the deployment's cell dep is read from the
172
+ client's network (`getCellLive`; a dep group is resolved) and must hold code
173
+ hashing to `codeHash` (data hash, or type hash for `hashType: "type"`).
174
+ `TESTNET_DEPLOYMENT` on a mainnet client, a typo in `codeHash` or a wrong out
175
+ point used to put funds under a lock with no code. One lookup per client and
176
+ deployment. `hashType: "data"` is refused too: the lock faults under CKB-VM
177
+ version 0. There is still no mainnet deployment; README and API.md say how to
178
+ pass your own.
179
+ - `spendInSession` and `chargeSubscription`: a key or charger cell above its
180
+ floor but short of the fee threw CCC's plain "Insufficient CKB"; it is now
181
+ `SessionBalanceError` with reason `"no-fee-cell"`. The charger's fee cells must
182
+ hold at least the floor plus 0.01 CKB. Found by the round-2 property suite,
183
+ added in `test/property-library.test.ts` and `test/property-fuzz.test.ts`,
184
+ which now fails on any plain error.
185
+ - `addTopUp` (top-ups in `spendInSession`, `chargeSubscription`,
186
+ `openSubscription`) skips a payee token cell the payment would take past
187
+ 2^128 - 1 units, and throws `SessionBalanceError` `"no-payout-cell"` if every
188
+ cell would, instead of a plain overflow error.
189
+ - `closeSession` refuses a signer whose lock is not `binding.ownerLock` with a
190
+ `SessionPolicyError`, before asking it to sign. After a wallet switch, the
191
+ new wallet was shown a sweep of the old wallet's session to itself (the lock
192
+ refused it on-chain, so nothing was lost).
193
+ - `closeSession` can finish once the session is empty on-chain: with the
194
+ funding committed and nothing indexed, it asks the node whether the funding's
195
+ outputs under the session and the key are still live, and reports
196
+ `nothingFound` when all are spent. Before, a close whose key return was taken
197
+ but whose reply was lost (or a close from a second tab) left a key that could
198
+ never be forgotten. The key return now goes through `signAndSend`, so a lost
199
+ reply the node did take is reported as `keyTx`.
200
+ It reports `nothingFound` only once the node's indexer has reached the tip
201
+ and a second search still finds nothing, so a later spend's key cell that is
202
+ not indexed yet never lets the key be forgotten.
203
+ - CCC's client cache lists a client's own outputs as live until cleared, so a
204
+ tab (or a warm server instance) built every later spend on cells another tab
205
+ had spent, and the node rejected each one. `spendInSession`, `closeSession`,
206
+ `chargeSubscription` and `cancelSubscription` now leave out cached-only cells
207
+ the node no longer has, and on a rejection for a spent or unknown input clear
208
+ the cache and build the transaction once more.
209
+ - `verifyAccess`, `verifySubscription` and `subscriptionStatus` read payments
210
+ with `getTransactionNoCache`. `getTransaction` answers from the cache when
211
+ the node no longer knows a transaction, so a payment that was reorged out kept
212
+ granting access on a long-lived server client.
213
+ - Demo: one "unknown" answer just after a lost send no longer makes a page
214
+ forget the session key (about 62 CKB stranded once the funding lands) or a
215
+ payment record. A key or record is forgotten only when the node rejected the
216
+ transaction, or has not known it for 10 minutes (the rule the payment polls
217
+ already used); meanwhile the page keeps polling it.
218
+ - Demo: every page's wallet lookup drops a late answer from the previous
219
+ wallet, and the subscribe page drops a status refresh started for the
220
+ previous wallet, asset or opening, and clears its status and notes on
221
+ disconnect.
222
+ - Demo: the CKB Place board asks again about a placement `get_transaction`
223
+ missed once, instead of leaving it off the board until a reload.
224
+ - Demo: the charge route builds a new CCC client per request.
225
+
226
+ ### Fixed (security audit, 30 Oct)
227
+ - `recoverSessions`: when the one sweep is refused, sessions are now swept one
228
+ session lock at a time (full lock hash), not one session key hash at a time. A
229
+ planted cell under other args carrying the victim's public key hash (a foreign
230
+ type script the sweep cannot consume) joined the victim's group and blocked its
231
+ recovery on every retry. `findOwnedSessionCells` keeps a typed cell only if its
232
+ type is a plain xUDT on the network (`assertPlainXudt`'s rules) holding exactly
233
+ the 16-byte amount a session makes.
234
+ - `spendInSession` pays the fee from the largest key cell (ties: lowest out
235
+ point), not the first one listed: a 61 CKB cell anyone sent to the key address,
236
+ or the old key cell beside a top-up, blocked every spend. Still one key cell in
237
+ and one smaller one out, as the lock requires.
238
+ - `verifyAccess` and `checkPlacement` count only memos in witnesses a signature
239
+ covers: the first witness of each secp256k1 lock group, and any past the
240
+ inputs. The session cell's and an anyone-can-pay cell's witnesses are signed by
241
+ nobody, so whoever relayed a payment could add a memo there and void it for
242
+ good (or, on a CKB Place board, choose the pixels of a payment without one).
243
+ Several signed memos still fail. `placementMemo` takes the resolved inputs as
244
+ an optional third argument for the exact check; without them it is a prefilter.
245
+ - Docs: with a recipient set, up to `max_per_tx` per transaction can still leave
246
+ as the miner fee (the recipient rule covers outputs only). The README and
247
+ `openSession` now say so and recommend a rate limit (`minInterval`) whenever a
248
+ recipient is set. The lock is unchanged.
249
+ - `subscriptionStatus`, `chargeSubscription` and `findSubscriptionCells` read at
250
+ most `MAX_SUBSCRIPTION_CELLS` (16, exported) cells under a subscription's lock,
251
+ and at most 64 recent transactions of its history. Beyond that they throw
252
+ `SessionPolicyError` with the new reason `"too-many-cells"` instead of reading
253
+ part of the cells, which could deny a paid-up subscriber or let a cell left out
254
+ of a charge pay a second price in the period. Planted cells (anyone can create
255
+ them) made each status request look up every creating transaction and all its
256
+ inputs again: 100 cells cost 3,307 RPC calls. `cancelSubscription` still reads
257
+ and sweeps every cell, so the subscriber can clear planted ones.
258
+ - Subscription payments are checked once per transaction within a call, and a
259
+ transaction whose outputs to `payTo` already fall short is refused before its
260
+ inputs are looked up.
261
+ - `chargeSubscription` picks its fee cell from the first 64 cells under the
262
+ charger's lock instead of reading all of them.
263
+ - Demo: `/api/subscription` answers `"too-many-cells"` with 409 (the page shows
264
+ it), not 503 with retry.
265
+ - Demo: the charger address is the creator's key, so `CHARGER_KEY` on the server
266
+ could spend the creator's income and payout cells. New `npm run setup:charger`
267
+ creates a separate key (`.env.charger`, written once, mode 0600) and prints its
268
+ address and next steps; the docs now require a charger key of its own holding
269
+ only fee cells. `CHARGER` is replaced once that key is funded and set on Vercel.
270
+
271
+ ### Fixed (30 Oct)
272
+ - `chargeSubscription` and `nextChargeIn` date the last charge from the
273
+ subscription lock's indexer history, not from its change cell: a charge of the
274
+ whole balance (the last 153 CKB with a price of 153 CKB or more) leaves no
275
+ change, so a younger top-up could pay a second price under a period later.
276
+ - `subscriptionStatus` counts that last whole-balance charge as a paid period;
277
+ it used to end access as soon as the last period was paid. A cancel still ends
278
+ access at once.
279
+
280
+ ### Fixed (code review, 28 Oct)
281
+ - Token overflow guard: a planted cell of 2^128-1 free tokens could push the
282
+ subscriber's own cell out of a cancel (the guard kept cells in indexer order), and
283
+ blocked `closeSession` and `recoverSessions` of token sessions (no guard at all).
284
+ One guard now serves sessions, recovery and subscriptions; it keeps cells created
285
+ by the owner or the key first.
286
+ - `recoverSessions`, `closeSession`, `cancelSubscription`: an owner sweep the node
287
+ accepted is reported as sent even when the reply times out (it skipped the
288
+ check `signAndSend` does). `recoverSessions` now throws when the owner holds no
289
+ plain cell, instead of reporting every session as failed.
290
+ - `chargeSubscription` waits a full period after the last charge, not only after
291
+ the oldest cell: a top-up cell older than the last charge's change could pay a
292
+ second price in the same period. `nextChargeIn` counts the same way.
293
+ - Session stores: a save of the same key without the on-chain binding (from a
294
+ stale tab) is refused with `SessionStoreConflict` instead of dropping the binding.
295
+ - Demo: the charge route answers 503 with `retry` when the creator's own cells are
296
+ missing (no fee cell for the charger, no payout cell to top up), not 409.
297
+ - `chargeSubscription` pays its fee from a random one of the charger's cells, and a
298
+ CKB `openSubscription` tops up a random payout cell, so concurrent charges and
299
+ openings rarely compete for one cell.
300
+
301
+ ### Changed
302
+ - `verifyAccess` resolves a payment's inputs in parallel once the memo check
303
+ passes; subscription charges and status fetch each creating transaction once.
304
+
305
+ ## 1.0.0 (2026-10-27)
306
+
307
+ First stable release. Everything below is on testnet, with a smoke script and explorer
308
+ links in the README.
309
+
310
+ ### Session lock (Rust)
311
+ - Delegates to a standard owner lock; in session mode it enforces a per-payment
312
+ outflow limit, an optional recipient, and a per-cell rate limit (`min_interval`).
313
+ - Refuses splitting (error 16), a growing key cell when a recipient is set (17), and
314
+ type scripts in session mode (18).
315
+ - Token sessions (xUDT): a per-payment limit in token units (20); tokens always come
316
+ back as tokens (19).
317
+ - Current testnet deployment exported as `TESTNET_DEPLOYMENT`; earlier ones stay in
318
+ `TESTNET_LEGACY_DEPLOYMENTS` so old sessions remain sweepable.
319
+
320
+ ### Library
321
+ - `openSession`, `spendInSession`, `closeSession`: one owner signature to open, none to
322
+ pay, one to close. Keys are saved before funding (`beforeSend`).
323
+ - Browser stores (`indexedDbStore`) that refuse cross-tab overwrites.
324
+ - Device-loss recovery: `recoverSessions` / `findOwnedSessionCells` sweep every
325
+ session back with the owner's wallet alone.
326
+ - Pay-per-read: payments top up the creator's anyone-can-pay cell; `paymentMemo`,
327
+ `signAccess`, `verifyAccess` check paid access on-chain, in CKB or tokens, with no
328
+ database. One payment, one claim.
329
+ - CKB Place: a pixel board rebuilt from the chain (`place` exports).
330
+ - Subscriptions on the same lock: the creator charges a fixed price once per period,
331
+ the subscriber cancels alone; paid in CKB or in a plain xUDT token.
332
+
333
+ ### Docs and demo
334
+ - `docs/API.md` (a test checks every export is documented), runnable `examples/`,
335
+ `docs/WALKTHROUGH.md`, a "Limits" section in the README.
336
+ - Demo at ckb-session-kit.vercel.app: pay-per-read (CKB and tokens), CKB Place,
337
+ pay-per-minute, subscriptions (CKB and tokens), recovery.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luong Thanh Tung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.