@percayso-community/cayso-contracts 0.23.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.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +114 -0
  3. package/dist/assertion.d.ts +867 -0
  4. package/dist/assertion.js +63 -0
  5. package/dist/assurance.d.ts +353 -0
  6. package/dist/assurance.js +551 -0
  7. package/dist/citizen-market.d.ts +468 -0
  8. package/dist/citizen-market.js +16 -0
  9. package/dist/citizenship.d.ts +215 -0
  10. package/dist/citizenship.js +26 -0
  11. package/dist/client.d.ts +288 -0
  12. package/dist/client.js +370 -0
  13. package/dist/commercial-payment.d.ts +243 -0
  14. package/dist/commercial-payment.js +16 -0
  15. package/dist/contract-schema.generated.d.ts +14 -0
  16. package/dist/contract-schema.generated.js +3107 -0
  17. package/dist/errors.d.ts +171 -0
  18. package/dist/errors.js +28 -0
  19. package/dist/index.d.ts +621 -0
  20. package/dist/index.js +624 -0
  21. package/dist/marketplace-receipt.d.ts +306 -0
  22. package/dist/marketplace-receipt.js +116 -0
  23. package/dist/marketplace-settlement.d.ts +298 -0
  24. package/dist/marketplace-settlement.js +16 -0
  25. package/dist/money.d.ts +84 -0
  26. package/dist/money.js +34 -0
  27. package/dist/organisation.d.ts +60 -0
  28. package/dist/organisation.js +16 -0
  29. package/dist/purchase.d.ts +531 -0
  30. package/dist/purchase.js +107 -0
  31. package/dist/restriction.d.ts +262 -0
  32. package/dist/restriction.js +316 -0
  33. package/dist/routes.d.ts +155 -0
  34. package/dist/routes.js +128 -0
  35. package/dist/schema-types.d.ts +101 -0
  36. package/dist/schema-types.js +16 -0
  37. package/dist/schema.d.ts +24 -0
  38. package/dist/schema.js +16 -0
  39. package/dist/wallet.d.ts +92 -0
  40. package/dist/wallet.js +59 -0
  41. package/package.json +41 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * The marketplace receipt: what Cayso signs about a marketplace sale or refund,
3
+ * and the exact bytes the signature is computed over (CAY-0083 §1).
4
+ *
5
+ * Store Platform grants a paid entitlement only on a receipt it verifies itself,
6
+ * for the exact order digest (Store's invariant 8). Piper carries the receipt from
7
+ * Cayso to Store Platform (Store decision V), so the receipt has to verify with no
8
+ * call back to Cayso: it is a {@link MarketplaceReceiptStatement} and a signature
9
+ * over {@link encodeMarketplaceReceiptStatement} of it, made with a key Cayso alone
10
+ * holds and that signs receipts only.
11
+ *
12
+ * ## What is signed
13
+ *
14
+ * CAY-0083 §1's list, and nothing else: the movement id, the order digest, the
15
+ * idempotency key, the sale's kind, each leg as a wallet id and a whole number of
16
+ * cents, the allocation-policy version and the time the movement was written. The
17
+ * statement adds three things the list implies. `movementKind` says which of the
18
+ * ledger's acts the movement is, and so which way its legs run. `buyerWalletId`
19
+ * names the other end of every leg. `keyId` names the key the signature was made
20
+ * with, so selecting the key is part of what is signed.
21
+ *
22
+ * **A receipt names wallet ids only, never a Percayso identity** (CAY-0006,
23
+ * CAY-0054). There is no field a subject could be put in.
24
+ *
25
+ * ## Four statements, and a type that cannot express a fifth
26
+ *
27
+ * A sale is a `marketplace_settlement` when a third party published the release and
28
+ * a plain `charge` when the house publisher did (CAY-0082 §1, §2). A refund is a
29
+ * `marketplace_refund` of either (§5), and names the order digest of the sale it
30
+ * refunds. Each is its own interface, discriminated by `movementKind` and
31
+ * `saleKind` together:
32
+ *
33
+ * - a third-party statement has a publisher leg and a Community leg;
34
+ * - a house-publisher statement has a Community leg and **no publisher leg field
35
+ * at all**, so a house-publisher sale that pays a publisher is not a value of
36
+ * this type; and
37
+ * - there is one publisher leg or none, never a list, because a paid sale has
38
+ * exactly one publisher (CAY-0082 §8) and a split among publishers is not
39
+ * designed.
40
+ *
41
+ * On a sale every leg runs from the buyer's wallet to the leg's wallet. On a refund
42
+ * every leg runs from the leg's wallet to the buyer's. The Community leg's wallet is
43
+ * the Treasury, named by its wallet id like any other.
44
+ *
45
+ * ## The bytes that are signed
46
+ *
47
+ * {@link encodeMarketplaceReceiptStatement} is the one encoding, and the signer and
48
+ * every verifier call it: two implementations of "the same JSON" are two encodings
49
+ * that will one day disagree by a space, and a verifier that disagrees with its
50
+ * signer by a space refuses every receipt. It is the statement's fields in the order
51
+ * the interfaces declare them, as UTF-8 JSON with no whitespace.
52
+ *
53
+ * It is published here, where CAY-0072 keeps the assertion's encoding out, because
54
+ * the two cases differ in who owns the bytes. Percayso ID mints an assertion and
55
+ * owns its format; Cayso mints a receipt and owns this one, and writing it once
56
+ * where both sides import it is what stops the signer and Store Platform drifting.
57
+ * It pins no cryptographic dependency: it is JSON and a UTF-8 encoder.
58
+ *
59
+ * **The key set is closed, and the encoder refuses a statement that is not exactly
60
+ * one of the four.** A house-publisher statement carrying a `publisherLeg`, a leg
61
+ * carrying a field beside its wallet and cents, or any key a statement does not
62
+ * name is refused rather than dropped. Dropping it would leave a verifier holding an
63
+ * object with a field nobody signed, and a consumer that read it would be reading
64
+ * whatever the transport added.
65
+ *
66
+ * ## The algorithm and the key
67
+ *
68
+ * {@link MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM} is ECDSA over P-256 with SHA-256,
69
+ * written with its JOSE name, `ES256`. The signature is the fixed-length `r || s`
70
+ * concatenation, 64 bytes, as unpadded base64url, and never DER. That is the
71
+ * algorithm and encoding Store Platform already verifies its release statements
72
+ * with (Store `STO-0026`, `STO-0033`), so Store Platform checks a receipt with the
73
+ * code it already has (CAY-0083 §1).
74
+ *
75
+ * A {@link MarketplaceReceiptKeyId} is the RFC 7638 SHA-256 thumbprint of the key's
76
+ * public half as a P-256 JSON Web Key, unpadded base64url. Derived rather than
77
+ * minted, so a key id cannot be re-pointed at a different key.
78
+ *
79
+ * ## The status read: a receipt or "no movement", never "pending"
80
+ *
81
+ * The read exists for one case: the settle call went out and its answer did not
82
+ * come back (CAY-0083 §2). Piper asks it under the buyer's assertion
83
+ * (`read-marketplace-receipt`, read-scope in `ASSURANCE_POLICY`), and Store
84
+ * Platform never does. {@link MarketplaceReceiptReadResponse} has exactly two
85
+ * answers about the key and one refusal:
86
+ *
87
+ * - **`receipt`** — the signed {@link MarketplaceReceipt} of the movement the key
88
+ * landed, the same receipt the lost answer carried;
89
+ * - **`no-movement`** — nothing was written under this key for this wallet. It
90
+ * means **retry with the same key, never a new one**: the first attempt may
91
+ * still be in flight, and the same key lands it exactly once either way; and
92
+ * - **`refused`** — the question could not be asked, with one reason from
93
+ * {@link MarketplaceReceiptReadRefusal}. It is not a third answer about the
94
+ * key, and a caller must never read it as "no movement".
95
+ *
96
+ * **There is no `pending`, and no member could carry one.** A movement is written
97
+ * or it is not (CAY-0023): there is no status column to report, so a read that
98
+ * said "in progress" would be inventing a state the ledger does not have.
99
+ *
100
+ * **The paying wallet is the buyer's own, resolved from the verified subject**
101
+ * through the holders mapping, as settlement resolves it (CAY-0082 §1), and so
102
+ * {@link MarketplaceReceiptReadRequest} names no wallet. CAY-0083 keys the read by
103
+ * the paying wallet and the idempotency key; a request field for the wallet would
104
+ * be a claim about who the caller is, which Cayso never trusts (CAY-0021), and a
105
+ * place another holder's wallet id could be written to read their receipt. With no
106
+ * such field, a read of somebody else's receipt is not a request this type can
107
+ * express. When the subject has no live binding the read is refused rather than
108
+ * answered `no-movement`: the wallet cannot be resolved, so whether a movement
109
+ * exists is not known, and "no movement" would be a guess that invites a retry.
110
+ *
111
+ * The answer is one receipt, never a list, so it rests on a key naming at most one
112
+ * movement for one paying wallet. CAY-0082 §4 holds a sale's key to that across
113
+ * both sale kinds; a refund carries its own key, never the sale's, and the slice
114
+ * that stores a refund has to hold it to the same rule beside the sale's.
115
+ *
116
+ * **A read writes no row of its own and moves nothing**: no audit row, and nothing
117
+ * recording which key was asked. The one record it leaves is the one every verified
118
+ * assertion leaves, its use, which is what refuses a replay (CAY-0030). A holder
119
+ * reading their own record is not surveilled, as a holder reading their own balance
120
+ * is not.
121
+ *
122
+ * Every unconfigured seam the read meets is a `503` and is not in the refusal
123
+ * union, among them CAY-0083 §4's empty registry of Store Platform order keys,
124
+ * which closes all three marketplace operations. The spelling of that one is the
125
+ * registry's slice's to publish. A malformed key is `invalid_request`.
126
+ *
127
+ * ## What is not here
128
+ *
129
+ * No key material of any kind, and no key set: which receipt keys Store Platform
130
+ * trusts is Store Platform's configuration, empty by default, and how Cayso holds,
131
+ * rotates and revokes its key is Cayso's own, in `apps/api`. No verifier: Store
132
+ * Platform verifies. And no figure: a leg carries the cents the movement moved, and
133
+ * the allocation between publisher and Community is Store Platform's canon's.
134
+ */
135
+ import type { Cents, Millis } from './wallet.js';
136
+ /**
137
+ * The algorithm every marketplace receipt is signed with: ECDSA P-256 with SHA-256,
138
+ * `r || s` encoded (JOSE `ES256`), CAY-0083 §1.
139
+ */
140
+ export declare const MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM: "ES256";
141
+ /**
142
+ * The type of {@link MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM}. Written as the
143
+ * literal because the published schema describes literals and not `typeof`, and a
144
+ * test pins the two together.
145
+ */
146
+ export type MarketplaceReceiptSignatureAlgorithm = 'ES256';
147
+ /**
148
+ * The id of one receipt signing key: the RFC 7638 thumbprint of its public half.
149
+ * Opaque to a consumer: compare it, never parse it. It names the key and is never
150
+ * the key.
151
+ */
152
+ export type MarketplaceReceiptKeyId = string;
153
+ /**
154
+ * Who published the release a sale was for. A third party's sale is a marketplace
155
+ * settlement; the house publisher's is a charge (CAY-0082 §1, §2). Cayso decides it
156
+ * from Store Platform's signed order and never from the caller's word.
157
+ */
158
+ export type MarketplaceSaleKind = 'third-party' | 'house-publisher';
159
+ /**
160
+ * One leg of a receipt: the wallet at the far end from the buyer, and the whole
161
+ * cents that crossed between them. On a sale the cents arrived there; on a refund
162
+ * they were drawn from there.
163
+ */
164
+ export interface MarketplaceReceiptLeg {
165
+ readonly walletId: string;
166
+ readonly cents: Cents;
167
+ }
168
+ /** A third-party sale: one `marketplace_settlement`, paying the publisher and the Treasury. */
169
+ export interface MarketplaceSettlementReceiptStatement {
170
+ readonly movementKind: 'marketplace_settlement';
171
+ readonly saleKind: 'third-party';
172
+ readonly movementId: string;
173
+ /** The immutable digest of Store Platform's signed order this sale settled. */
174
+ readonly orderDigest: string;
175
+ /** The checkout's idempotency key, unique per paying wallet (CAY-0082 §4). */
176
+ readonly idempotencyKey: string;
177
+ /** The paying wallet. */
178
+ readonly buyerWalletId: string;
179
+ /** The publisher's share, to the publisher's Citizen Wallet. */
180
+ readonly publisherLeg: MarketplaceReceiptLeg;
181
+ /** The Community's share, straight to the Treasury. */
182
+ readonly communityLeg: MarketplaceReceiptLeg;
183
+ /** The allocation-policy version Store Platform's signed order named. */
184
+ readonly allocationPolicyVersion: string;
185
+ /** When the movement was written. */
186
+ readonly writtenAt: Millis;
187
+ /** The key the signature was made with. */
188
+ readonly keyId: MarketplaceReceiptKeyId;
189
+ }
190
+ /** A house-publisher sale: one `charge` to the Treasury, and no publisher leg. */
191
+ export interface HousePublisherChargeReceiptStatement {
192
+ readonly movementKind: 'charge';
193
+ readonly saleKind: 'house-publisher';
194
+ readonly movementId: string;
195
+ readonly orderDigest: string;
196
+ readonly idempotencyKey: string;
197
+ /** The paying wallet. */
198
+ readonly buyerWalletId: string;
199
+ /** The whole price, to the Treasury. */
200
+ readonly communityLeg: MarketplaceReceiptLeg;
201
+ readonly allocationPolicyVersion: string;
202
+ readonly writtenAt: Millis;
203
+ readonly keyId: MarketplaceReceiptKeyId;
204
+ }
205
+ /** The refund of a third-party sale: each leg drawn back from where it landed, to the buyer. */
206
+ export interface ThirdPartyRefundReceiptStatement {
207
+ readonly movementKind: 'marketplace_refund';
208
+ readonly saleKind: 'third-party';
209
+ readonly movementId: string;
210
+ /** The order digest of the sale this refunds. */
211
+ readonly orderDigest: string;
212
+ /** The refund's own idempotency key, never the sale's. */
213
+ readonly idempotencyKey: string;
214
+ /** The wallet refunded, which is the wallet that paid for the sale. */
215
+ readonly buyerWalletId: string;
216
+ /** The publisher's share, drawn from the publisher's wallet. */
217
+ readonly publisherLeg: MarketplaceReceiptLeg;
218
+ /** The Community's share, drawn from the Treasury. */
219
+ readonly communityLeg: MarketplaceReceiptLeg;
220
+ /** The allocation-policy version of the sale refunded. */
221
+ readonly allocationPolicyVersion: string;
222
+ readonly writtenAt: Millis;
223
+ readonly keyId: MarketplaceReceiptKeyId;
224
+ }
225
+ /** The refund of a house-publisher sale: the whole, drawn from the Treasury, to the buyer. */
226
+ export interface HousePublisherRefundReceiptStatement {
227
+ readonly movementKind: 'marketplace_refund';
228
+ readonly saleKind: 'house-publisher';
229
+ readonly movementId: string;
230
+ /** The order digest of the sale this refunds. */
231
+ readonly orderDigest: string;
232
+ /** The refund's own idempotency key, never the sale's. */
233
+ readonly idempotencyKey: string;
234
+ readonly buyerWalletId: string;
235
+ /** The whole, drawn from the Treasury. */
236
+ readonly communityLeg: MarketplaceReceiptLeg;
237
+ readonly allocationPolicyVersion: string;
238
+ readonly writtenAt: Millis;
239
+ readonly keyId: MarketplaceReceiptKeyId;
240
+ }
241
+ /** What Cayso signs about one marketplace movement. Exactly one of the four. */
242
+ export type MarketplaceReceiptStatement = MarketplaceSettlementReceiptStatement | HousePublisherChargeReceiptStatement | ThirdPartyRefundReceiptStatement | HousePublisherRefundReceiptStatement;
243
+ /** A receipt: the statement, and Cayso's signature over its encoding. */
244
+ export interface MarketplaceReceipt {
245
+ readonly statement: MarketplaceReceiptStatement;
246
+ /**
247
+ * The {@link MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM} signature over
248
+ * {@link encodeMarketplaceReceiptStatement}`(statement)`: `r || s`, 64 bytes, as
249
+ * unpadded base64url.
250
+ */
251
+ readonly signature: string;
252
+ }
253
+ /**
254
+ * Piper asking, for the buyer, what one idempotency key landed (CAY-0083 §2). The
255
+ * paying wallet is the verified subject's own and is never named here: see the file
256
+ * docblock.
257
+ */
258
+ export interface MarketplaceReceiptReadRequest {
259
+ /** The key the sale or refund was presented under, exactly as it was presented. */
260
+ readonly idempotencyKey: string;
261
+ }
262
+ /** The key landed a movement: its signed receipt. */
263
+ export interface MarketplaceReceiptFound {
264
+ readonly outcome: 'receipt';
265
+ readonly receipt: MarketplaceReceipt;
266
+ }
267
+ /**
268
+ * Nothing was written under this key for this wallet. Retry with the SAME key,
269
+ * never a new one: the first attempt may still be in flight, and the same key lands
270
+ * it exactly once either way. It is not "pending": there is no pending movement.
271
+ */
272
+ export interface MarketplaceNoMovement {
273
+ readonly outcome: 'no-movement';
274
+ }
275
+ /**
276
+ * Why the read could not be asked. Closed, and a refusal is never a statement about
277
+ * the key: a caller must not read one as "no movement".
278
+ */
279
+ export type MarketplaceReceiptReadRefusal =
280
+ /**
281
+ * No wallet is bound to the verified subject now, for example after erasure, so
282
+ * there is no paying wallet to ask about and whether the key landed is not known.
283
+ */
284
+ 'wallet_has_no_live_binding';
285
+ /** The read was refused, with one reason. Nothing was written. */
286
+ export interface MarketplaceReceiptReadRefused {
287
+ readonly outcome: 'refused';
288
+ readonly error: MarketplaceReceiptReadRefusal;
289
+ }
290
+ /**
291
+ * The ONE declared answer to the status read, discriminated by `outcome`: a receipt,
292
+ * "no movement for this key", or a refusal. Never "pending".
293
+ */
294
+ export type MarketplaceReceiptReadResponse = MarketplaceReceiptFound | MarketplaceNoMovement | MarketplaceReceiptReadRefused;
295
+ /**
296
+ * The exact bytes a marketplace receipt's signature is computed over.
297
+ *
298
+ * Builds a fresh object from the statement's named fields, in a fixed order, so the
299
+ * encoding depends on nothing but their values: not on the order a caller wrote
300
+ * them in. THROWS a `TypeError` for anything that is not exactly one of the four
301
+ * statements — a pair of kinds that is not admissible, a missing or extra field, a
302
+ * string field that is not a string, or cents or a time that is not a safe whole
303
+ * number no less than zero. A statement arrives from a transport before it is typed,
304
+ * and a value of the wrong kind would still encode, to something nobody signed.
305
+ */
306
+ export declare function encodeMarketplaceReceiptStatement(statement: MarketplaceReceiptStatement): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,116 @@
1
+ /*
2
+ * Copyright 2026 Percayso Community
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * The algorithm every marketplace receipt is signed with: ECDSA P-256 with SHA-256,
18
+ * `r || s` encoded (JOSE `ES256`), CAY-0083 §1.
19
+ */
20
+ export const MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM = 'ES256';
21
+ // --- The encoding ------------------------------------------------------------
22
+ /** The statement's own fields, in the order they are encoded, before and after the legs. */
23
+ const LEADING_FIELDS = [
24
+ 'movementKind',
25
+ 'saleKind',
26
+ 'movementId',
27
+ 'orderDigest',
28
+ 'idempotencyKey',
29
+ 'buyerWalletId',
30
+ ];
31
+ const TRAILING_FIELDS = ['allocationPolicyVersion', 'writtenAt', 'keyId'];
32
+ /** Every string field, each of which must be a string when it arrives. */
33
+ const STRING_FIELDS = [
34
+ 'movementId',
35
+ 'orderDigest',
36
+ 'idempotencyKey',
37
+ 'buyerWalletId',
38
+ 'allocationPolicyVersion',
39
+ 'keyId',
40
+ ];
41
+ /**
42
+ * The four admissible pairs of `movementKind` and `saleKind`, and whether each has a
43
+ * publisher leg. A pair not listed here is not a receipt.
44
+ */
45
+ const ADMISSIBLE = [
46
+ { movementKind: 'marketplace_settlement', saleKind: 'third-party', publisherLeg: true },
47
+ { movementKind: 'charge', saleKind: 'house-publisher', publisherLeg: false },
48
+ { movementKind: 'marketplace_refund', saleKind: 'third-party', publisherLeg: true },
49
+ { movementKind: 'marketplace_refund', saleKind: 'house-publisher', publisherLeg: false },
50
+ ];
51
+ /** The fields of a leg, in the order they are encoded. */
52
+ const LEG_FIELDS = ['walletId', 'cents'];
53
+ /**
54
+ * The exact bytes a marketplace receipt's signature is computed over.
55
+ *
56
+ * Builds a fresh object from the statement's named fields, in a fixed order, so the
57
+ * encoding depends on nothing but their values: not on the order a caller wrote
58
+ * them in. THROWS a `TypeError` for anything that is not exactly one of the four
59
+ * statements — a pair of kinds that is not admissible, a missing or extra field, a
60
+ * string field that is not a string, or cents or a time that is not a safe whole
61
+ * number no less than zero. A statement arrives from a transport before it is typed,
62
+ * and a value of the wrong kind would still encode, to something nobody signed.
63
+ */
64
+ export function encodeMarketplaceReceiptStatement(statement) {
65
+ const value = statement;
66
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
67
+ throw new TypeError('a marketplace receipt statement is an object');
68
+ }
69
+ const shape = ADMISSIBLE.find((candidate) => candidate.movementKind === value['movementKind'] && candidate.saleKind === value['saleKind']);
70
+ if (shape === undefined) {
71
+ throw new TypeError('a marketplace receipt statement names an admissible movement and sale kind');
72
+ }
73
+ const legFields = shape.publisherLeg ? ['publisherLeg', 'communityLeg'] : ['communityLeg'];
74
+ const fields = [...LEADING_FIELDS, ...legFields, ...TRAILING_FIELDS];
75
+ requireExactly(value, fields, 'a marketplace receipt statement carries exactly its own fields');
76
+ for (const field of STRING_FIELDS) {
77
+ if (typeof value[field] !== 'string') {
78
+ throw new TypeError('every identifier of a marketplace receipt statement is a string');
79
+ }
80
+ }
81
+ requireWholeNumber(value['writtenAt'], 'a marketplace receipt is written at whole milliseconds');
82
+ const canonical = {};
83
+ for (const field of LEADING_FIELDS)
84
+ canonical[field] = value[field];
85
+ for (const field of legFields)
86
+ canonical[field] = canonicalLeg(value[field]);
87
+ for (const field of TRAILING_FIELDS)
88
+ canonical[field] = value[field];
89
+ return new TextEncoder().encode(JSON.stringify(canonical));
90
+ }
91
+ /** A leg rebuilt in its fixed order, or a refusal. */
92
+ function canonicalLeg(leg) {
93
+ if (typeof leg !== 'object' || leg === null || Array.isArray(leg)) {
94
+ throw new TypeError('a marketplace receipt leg is an object');
95
+ }
96
+ const value = leg;
97
+ requireExactly(value, LEG_FIELDS, 'a marketplace receipt leg carries a wallet id and cents only');
98
+ if (typeof value['walletId'] !== 'string') {
99
+ throw new TypeError('a marketplace receipt leg names its wallet by id');
100
+ }
101
+ requireWholeNumber(value['cents'], 'a marketplace receipt leg carries whole cents');
102
+ return { walletId: value['walletId'], cents: value['cents'] };
103
+ }
104
+ /** Refuse an object whose own keys are not exactly `fields`. */
105
+ function requireExactly(value, fields, category) {
106
+ const keys = Object.keys(value);
107
+ if (keys.length !== fields.length || !fields.every((field) => Object.hasOwn(value, field))) {
108
+ throw new TypeError(category);
109
+ }
110
+ }
111
+ /** Refuse anything that is not a safe whole number no less than zero. */
112
+ function requireWholeNumber(value, category) {
113
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
114
+ throw new TypeError(category);
115
+ }
116
+ }