@percayso-community/cayso-contracts 0.26.0 → 0.29.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.
@@ -0,0 +1,189 @@
1
+ /**
2
+ * `handle-change` — a Citizen's Handle change, charged by Cayso, and the signed receipt
3
+ * Percayso ID relies on before it changes the Handle record (CAY-0091).
4
+ *
5
+ * Every Handle change after approval is charged in Cayso (Percayso ID's onboarding PRD
6
+ * §7.2). Cayso holds the price and makes the charge; Percayso ID changes the Handle only
7
+ * once Cayso has answered with a receipt. Served at `ROUTES.handleChange`, under the
8
+ * operation `'change-handle'`, and read back at `ROUTES.handleChangeReceipt`, under
9
+ * `'read-handle-change-receipt'`.
10
+ *
11
+ * ## Who calls it
12
+ *
13
+ * Percayso ID, over its back channel, as it presents a founder claim. The Citizen
14
+ * confirms on Percayso ID's own pages; Percayso ID mints a `change-handle` assertion for
15
+ * that interactive session — never from the background source (CAY-0070) — and presents
16
+ * it here. The assertion's `sub` is the Citizen ID.
17
+ *
18
+ * ## One `charge` for a Community service, or nothing
19
+ *
20
+ * The charge is one `charge` movement from the Citizen's own Citizen Wallet to the
21
+ * Organisation Wallet of the organisation that operates Percayso ID, named in Cayso's
22
+ * configuration — never a Citizen Wallet (CAY-0084). A Handle change is a Community
23
+ * service (CAY-0076, as extended by CAY-0091), and a `charge` may draw restricted lots
24
+ * like any charge, so restricted and unrestricted cents are drawn together. There is no
25
+ * sterling leg and so no lifecycle: the answer is a {@link HandleChangeCharged} carrying
26
+ * the signed receipt, or a {@link HandleChangeRefused} meaning nothing moved.
27
+ *
28
+ * ## Nothing in the request names anything
29
+ *
30
+ * There is no request body. The paying wallet is the verified subject's own Citizen
31
+ * Wallet, resolved inside the service (CAY-0021). The price is Cayso's configuration.
32
+ * The change reference — the single-use reference Percayso ID issued when it reserved
33
+ * the proposed Handle — is carried INSIDE the signed assertion, as
34
+ * `handleChangeReference`, because a reference named in a body would be the caller's
35
+ * claim about which Handle change is theirs. Cayso never learns the Handle.
36
+ *
37
+ * ## At most one charge per change reference
38
+ *
39
+ * The change reference is the charge's key. The first presentation charges; a later
40
+ * presentation of the same reference for the same Citizen, under a fresh assertion,
41
+ * charges nothing and answers the receipt of the charge already made — so Percayso ID,
42
+ * having had no response, retries with the same reference and is answered idempotently.
43
+ * A reference already charged to another wallet is refused. The same assertion
44
+ * presented twice never reaches this far: its `jti` is single-use, and the replay is
45
+ * refused at the gate.
46
+ *
47
+ * ## The receipt is a statement Cayso signs
48
+ *
49
+ * {@link HandleChangeReceipt} is a {@link HandleChangeReceiptStatement} and an `ES256`
50
+ * signature over {@link encodeHandleChangeReceiptStatement} of it, made with Cayso's
51
+ * receipt key (CAY-0083 §1, as amended by CAY-0091) and published in the same key set
52
+ * as a marketplace receipt's. It is BOUND to the charge: it names the change reference
53
+ * and the issuer and `jti` of the assertion the charge was made under, which Percayso ID
54
+ * minted and alone can tie to a Citizen. It names a wallet id and never a Percayso
55
+ * identity (CAY-0006, CAY-0054): there is no `sub` and no Handle.
56
+ *
57
+ * ## Was this reference charged? A receipt or "no charge", never "pending"
58
+ *
59
+ * {@link HandleChangeReceiptReadResponse} answers the read Percayso ID makes before it
60
+ * releases a held Handle: the receipt of the charge the reference landed, or
61
+ * `no-charge` — nothing was charged under this reference for this Citizen. There is no
62
+ * `pending`: a movement is written or it is not (CAY-0023). A read writes no row and
63
+ * moves nothing.
64
+ *
65
+ * ## The bytes that are signed, and why they cannot be a marketplace receipt
66
+ *
67
+ * {@link encodeHandleChangeReceiptStatement} is the one encoding, called by the signer
68
+ * and by every verifier: the statement's fields in the order the interface declares
69
+ * them, as UTF-8 JSON with no whitespace, refusing any field it does not name. Its first
70
+ * field is `"reason":"handle-change"`, and no marketplace receipt has a `reason` field —
71
+ * that encoder refuses one — so the one key signing both cannot make either read as the
72
+ * other.
73
+ *
74
+ * ## What is not here
75
+ *
76
+ * No figure: the price is a canon figure, configured, and the receipt carries the cents
77
+ * that moved. No key material and no verifier: which keys Percayso ID trusts is Percayso
78
+ * ID's configuration, empty by default.
79
+ */
80
+ import type { MarketplaceReceiptKeyId, MarketplaceReceiptLeg } from './marketplace-receipt.js';
81
+ import type { Millis } from './wallet.js';
82
+ /**
83
+ * What Cayso signs about one Handle-change charge. Every field is signed, including
84
+ * `keyId`, so which key signed is part of what is signed.
85
+ */
86
+ export interface HandleChangeReceiptStatement {
87
+ /** The reason for the charge. Always the literal, and always first in the encoding. */
88
+ readonly reason: 'handle-change';
89
+ /** The act: one `charge`, from the payer to the payee. */
90
+ readonly movementKind: 'charge';
91
+ /** The movement that carried the cents. */
92
+ readonly movementId: string;
93
+ /** The change reference charged for, exactly as Percayso ID minted it into the assertion. */
94
+ readonly handleChangeReference: string;
95
+ /** The issuer of the assertion the charge was made under. */
96
+ readonly assertionIssuer: string;
97
+ /** The `jti` of the assertion the charge was made under. */
98
+ readonly assertionId: string;
99
+ /** The paying wallet: the Citizen's own Citizen Wallet. */
100
+ readonly payerWalletId: string;
101
+ /** The whole price, to the configured Organisation Wallet, as the cents that moved. */
102
+ readonly payeeLeg: MarketplaceReceiptLeg;
103
+ /** When the movement was written. */
104
+ readonly writtenAt: Millis;
105
+ /** The receipt key the signature was made with. */
106
+ readonly keyId: MarketplaceReceiptKeyId;
107
+ }
108
+ /** A receipt: the statement, and Cayso's signature over its encoding. */
109
+ export interface HandleChangeReceipt {
110
+ readonly statement: HandleChangeReceiptStatement;
111
+ /**
112
+ * `ES256` over {@link encodeHandleChangeReceiptStatement}`(statement)`: `r || s`, 64
113
+ * bytes, as unpadded base64url.
114
+ */
115
+ readonly signature: string;
116
+ }
117
+ /** The Handle change was charged, by this request or by an earlier one for the same reference. */
118
+ export interface HandleChangeCharged {
119
+ readonly outcome: 'charged';
120
+ readonly receipt: HandleChangeReceipt;
121
+ }
122
+ /** The charge was refused, with one member of the closed union. Nothing was written. */
123
+ export interface HandleChangeRefused {
124
+ readonly outcome: 'refused';
125
+ readonly error: HandleChangeRefusal;
126
+ }
127
+ /** The ONE declared answer to a Handle change, discriminated by `outcome`. */
128
+ export type HandleChangeResponse = HandleChangeCharged | HandleChangeRefused;
129
+ /**
130
+ * Why a Handle change was refused: CAY-0091's set, closed. Every member means the same
131
+ * thing about money: nothing moved.
132
+ */
133
+ export type HandleChangeRefusal =
134
+ /**
135
+ * The verification method is outside the money-grade assurance profile
136
+ * (`ASSURANCE_POLICY['change-handle']`). `dev-bypass` is never inside it.
137
+ */
138
+ 'assurance_not_money_grade'
139
+ /** The verified subject resolves to no Citizen Wallet. A Handle change never provisions one. */
140
+ | 'payer_has_no_citizen_wallet'
141
+ /** The payer's cents, restricted and unrestricted together, are short of the price. */
142
+ | 'cents_insufficient'
143
+ /** A holding the charge would draw is frozen. */
144
+ | 'cents_frozen'
145
+ /** The change reference has already been charged, to another wallet. */
146
+ | 'handle_change_reference_charged_to_another_wallet'
147
+ /** Too many concurrent draws on the paying wallet. Nothing moved; retry with the same reference. */
148
+ | 'carve_contended';
149
+ /** The reference was charged for this Citizen: the receipt of that charge. */
150
+ export interface HandleChangeReceiptFound {
151
+ readonly outcome: 'receipt';
152
+ readonly receipt: HandleChangeReceipt;
153
+ }
154
+ /**
155
+ * Nothing was charged under this reference for this Citizen. Never "pending": there is
156
+ * no pending charge.
157
+ */
158
+ export interface HandleChangeNoCharge {
159
+ readonly outcome: 'no-charge';
160
+ }
161
+ /**
162
+ * Why the read could not be answered. Closed, and a refusal is never a statement about
163
+ * the reference: a caller must not read one as "no charge".
164
+ */
165
+ export type HandleChangeReceiptReadRefusal =
166
+ /**
167
+ * No wallet is bound to the verified subject now, so there is no paying wallet to ask
168
+ * about, and whether the reference was charged is not known.
169
+ */
170
+ 'payer_has_no_citizen_wallet'
171
+ /** The reference was charged, to another wallet. */
172
+ | 'handle_change_reference_charged_to_another_wallet';
173
+ /** The read was refused, with one reason. Nothing was written. */
174
+ export interface HandleChangeReceiptReadRefused {
175
+ readonly outcome: 'refused';
176
+ readonly error: HandleChangeReceiptReadRefusal;
177
+ }
178
+ /** The ONE declared answer to the read: a receipt, "no charge", or a refusal. Never "pending". */
179
+ export type HandleChangeReceiptReadResponse = HandleChangeReceiptFound | HandleChangeNoCharge | HandleChangeReceiptReadRefused;
180
+ /**
181
+ * The exact bytes a Handle-change receipt's signature is computed over.
182
+ *
183
+ * Builds a fresh object from the statement's named fields, in a fixed order, so the
184
+ * encoding depends on nothing but their values. THROWS a `TypeError` for anything that
185
+ * is not exactly a {@link HandleChangeReceiptStatement}: a wrong literal, a missing or
186
+ * extra field, a string field that is not a string, or cents or a time that is not a
187
+ * safe whole number no less than zero.
188
+ */
189
+ export declare function encodeHandleChangeReceiptStatement(statement: HandleChangeReceiptStatement): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,96 @@
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
+ // --- The encoding ------------------------------------------------------------
17
+ /** The statement's own fields, in the order they are encoded. */
18
+ const FIELDS = [
19
+ 'reason',
20
+ 'movementKind',
21
+ 'movementId',
22
+ 'handleChangeReference',
23
+ 'assertionIssuer',
24
+ 'assertionId',
25
+ 'payerWalletId',
26
+ 'payeeLeg',
27
+ 'writtenAt',
28
+ 'keyId',
29
+ ];
30
+ /** Every string field, each of which must be a string when it arrives. */
31
+ const STRING_FIELDS = [
32
+ 'movementId',
33
+ 'handleChangeReference',
34
+ 'assertionIssuer',
35
+ 'assertionId',
36
+ 'payerWalletId',
37
+ 'keyId',
38
+ ];
39
+ /** The fields of a leg, in the order they are encoded. */
40
+ const LEG_FIELDS = ['walletId', 'cents'];
41
+ /**
42
+ * The exact bytes a Handle-change receipt's signature is computed over.
43
+ *
44
+ * Builds a fresh object from the statement's named fields, in a fixed order, so the
45
+ * encoding depends on nothing but their values. THROWS a `TypeError` for anything that
46
+ * is not exactly a {@link HandleChangeReceiptStatement}: a wrong literal, a missing or
47
+ * extra field, a string field that is not a string, or cents or a time that is not a
48
+ * safe whole number no less than zero.
49
+ */
50
+ export function encodeHandleChangeReceiptStatement(statement) {
51
+ const value = statement;
52
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
53
+ throw new TypeError('a Handle-change receipt statement is an object');
54
+ }
55
+ requireExactly(value, FIELDS, 'a Handle-change receipt carries exactly its own fields');
56
+ if (value['reason'] !== 'handle-change' || value['movementKind'] !== 'charge') {
57
+ throw new TypeError('a Handle-change receipt is for a Handle change made by a charge');
58
+ }
59
+ for (const field of STRING_FIELDS) {
60
+ if (typeof value[field] !== 'string') {
61
+ throw new TypeError('every identifier of a Handle-change receipt is a string');
62
+ }
63
+ }
64
+ requireWholeNumber(value['writtenAt'], 'a Handle-change receipt is written at whole milliseconds');
65
+ const canonical = {};
66
+ for (const field of FIELDS) {
67
+ canonical[field] = field === 'payeeLeg' ? canonicalLeg(value[field]) : value[field];
68
+ }
69
+ return new TextEncoder().encode(JSON.stringify(canonical));
70
+ }
71
+ /** A leg rebuilt in its fixed order, or a refusal. */
72
+ function canonicalLeg(leg) {
73
+ if (typeof leg !== 'object' || leg === null || Array.isArray(leg)) {
74
+ throw new TypeError('a Handle-change receipt leg is an object');
75
+ }
76
+ const value = leg;
77
+ requireExactly(value, LEG_FIELDS, 'a Handle-change receipt leg carries a wallet id and cents only');
78
+ if (typeof value['walletId'] !== 'string') {
79
+ throw new TypeError('a Handle-change receipt leg names its wallet by id');
80
+ }
81
+ requireWholeNumber(value['cents'], 'a Handle-change receipt leg carries whole cents');
82
+ return { walletId: value['walletId'], cents: value['cents'] };
83
+ }
84
+ /** Refuse an object whose own keys are not exactly `fields`. */
85
+ function requireExactly(value, fields, category) {
86
+ const keys = Object.keys(value);
87
+ if (keys.length !== fields.length || !fields.every((field) => Object.hasOwn(value, field))) {
88
+ throw new TypeError(category);
89
+ }
90
+ }
91
+ /** Refuse anything that is not a safe whole number no less than zero. */
92
+ function requireWholeNumber(value, category) {
93
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
94
+ throw new TypeError(category);
95
+ }
96
+ }
package/dist/index.d.ts CHANGED
@@ -76,6 +76,45 @@
76
76
  * this money surface gains that Piper's own contracts lack, since theirs share
77
77
  * one global version bumped across all twenty-two together (CAY-0026).
78
78
  *
79
+ * `0.28.0` → `0.29.0` publishes and SERVES the Handle change (CAY-0091):
80
+ * `ROUTES.handleChange`, `POST /api/handle-changes`, mapped to the new operation
81
+ * `'change-handle'`, money-grade in `ASSURANCE_POLICY`; `ROUTES.handleChangeReceipt`,
82
+ * `GET /api/handle-changes/receipt`, mapped to the new read-scope operation
83
+ * `'read-handle-change-receipt'`; the assertion claim `handleChangeReference`, which
84
+ * travels with those two operations in both directions; the client methods
85
+ * `changeHandle` and `readHandleChangeReceipt`; the signed {@link HandleChangeReceipt}
86
+ * and its one encoding, {@link encodeHandleChangeReceiptStatement}; and
87
+ * `CaysoApiErrorCode` gains {@link HandleChangeRefusal} and
88
+ * {@link HandleChangeReceiptReadRefusal} by reference and the seam codes
89
+ * `handle_change_store_not_configured`, `handle_change_price_not_configured` and
90
+ * `handle_change_payee_not_configured`. BREAKING in the package's sense because closed
91
+ * tables grew. Additive on the wire: no route a `0.28.0` consumer calls changed or
92
+ * answers anything new, and a credential a `0.28.0` issuer mints carries neither the new
93
+ * scope keys nor the new claim (CAY-0086).
94
+ *
95
+ * `0.27.0` → `0.28.0` SERVES a Citizen publisher's marketplace sale (CAY-0090):
96
+ * `POST /api/marketplace/orders` settles a signed order whose payee is a
97
+ * `citizen_publisher` as one `marketplace_settlement`, answering the
98
+ * `MarketplaceSettlementReceiptStatement` `0.24.0` already published. The share is
99
+ * read from a new signed-order fact, `communityShareBasisPoints`, which Store Platform
100
+ * signs beside `allocationPolicyVersion`. {@link MarketplaceOrderRefusal} gains
101
+ * `allocation_share_out_of_range` and `payee_wallet_is_the_buyers`, and
102
+ * `allocation_policy_version_not_configured` is retired (kept, never answered). The
103
+ * operator `MovementKind` gains `marketplace_settlement`. BREAKING in the package's
104
+ * sense because closed tables grew. On the wire, every order a `0.27.0` consumer could
105
+ * present — one Store Platform signed without the new fact — is answered exactly as
106
+ * before, a Citizen publisher's with `third_party_marketplace_sale_not_served`; only an
107
+ * order carrying the new fact can reach a new code (CAY-0086).
108
+ *
109
+ * `0.26.0` → `0.27.0` publishes and SERVES the Citizen transfer (CAY-0088):
110
+ * `ROUTES.citizenTransfer`, `POST /api/citizen-transfers`, mapped to the new
111
+ * operation `'make-citizen-transfer'`, money-grade in `ASSURANCE_POLICY`; the client
112
+ * method `makeCitizenTransfer`; and `CaysoApiErrorCode` gains
113
+ * {@link CitizenTransferRefusal} by reference and the seam code
114
+ * `citizen_transfer_store_not_configured`. It is BREAKING in the package's sense
115
+ * because three closed tables grew. It is additive on the wire: no route a `0.26.0`
116
+ * consumer calls changed or answers anything new (CAY-0086).
117
+ *
79
118
  * `0.25.0` → `0.26.0` publishes the operator read surface on its own entry point,
80
119
  * `@percayso-community/cayso-contracts/operator` (CAY-0026 Amendment 4), and describes
81
120
  * it in the schema under `operator`: the movement list, the purchase list and the
@@ -513,7 +552,7 @@
513
552
  * have needed a breaking bump on their own. Under semver below `1.0.0` the minor
514
553
  * is where a breaking change goes, so the minor moves and the patch does not.
515
554
  */
516
- export declare const CONTRACT_VERSION: "0.26.0";
555
+ export declare const CONTRACT_VERSION: "0.29.0";
517
556
  /** The package's own name, so a consumer can name what it depends on. */
518
557
  export declare const packageName = "@percayso-community/cayso-contracts";
519
558
  /**
@@ -647,12 +686,19 @@ export { REQUEST_RATE_LIMITED } from './errors.js';
647
686
  * one of the four.
648
687
  */
649
688
  export { MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM, encodeMarketplaceReceiptStatement, } from './marketplace-receipt.js';
689
+ /**
690
+ * The one encoding of a Handle-change receipt statement, called by the signer and by
691
+ * Percayso ID's verifier (CAY-0091).
692
+ */
693
+ export { encodeHandleChangeReceiptStatement } from './handle-change.js';
650
694
  export type { PositiveCents } from './money.js';
651
695
  export type { CaysoApiErrorCode, CaysoErrorResponse } from './errors.js';
652
696
  export type { HolderType, Millis, Cents, WalletRecord, Balance, Allowance, WalletView, CaysoWallet, } from './wallet.js';
653
697
  export type { SignUpGrantTerms, SignUpGrant, CitizenshipNotProvisioned, CitizenshipProvisioned, CitizenshipFounderClaimed, Citizenship, CitizenshipView, FounderClaimRefusal, } from './citizenship.js';
654
698
  export type { OrganisationView } from './organisation.js';
655
699
  export type { CommercialPaymentAuthority, CommercialPaymentRequest, CommercialPayment, CommercialPaymentMade, CommercialPaymentRefused, CommercialPaymentResponse, CommercialPaymentRefusal, } from './commercial-payment.js';
700
+ export type { CitizenTransferRequest, CitizenTransfer, CitizenTransferMade, CitizenTransferRefused, CitizenTransferResponse, CitizenTransferRefusal, } from './citizen-transfer.js';
701
+ export type { HandleChangeReceiptStatement, HandleChangeReceipt, HandleChangeCharged, HandleChangeRefused, HandleChangeResponse, HandleChangeRefusal, HandleChangeReceiptFound, HandleChangeNoCharge, HandleChangeReceiptReadRefusal, HandleChangeReceiptReadRefused, HandleChangeReceiptReadResponse, } from './handle-change.js';
656
702
  export type { QuoteSide, QuoteRequest, Quote, QuotePlaced, QuoteRefused, QuoteResponse, QuoteEntryRefusal, QuotePlacementRefusal, QuoteWithdrawalRequest, QuoteWithdrawal, QuoteWithdrawn, QuoteWithdrawalRefused, QuoteWithdrawalResponse, QuoteWithdrawalRefusal, BuyBackRequest, BuyBack, BuyBackMade, BuyBackRefused, BuyBackResponse, BuyBackRefusal, BookLevel, QuoteStatus, QuoteWithdrawalReason, OwnQuote, OwnQuoteNotWithdrawn, OwnQuoteWithdrawn, CitizenMarketView, CitizenMarketViewed, CitizenMarketViewRefusal, CitizenMarketViewRefused, CitizenMarketResponse, } from './citizen-market.js';
657
703
  export type { MarketplaceReceiptSignatureAlgorithm, MarketplaceReceiptKeyId, MarketplaceSaleKind, MarketplaceReceiptLeg, MarketplaceSettlementReceiptStatement, HousePublisherChargeReceiptStatement, ThirdPartyRefundReceiptStatement, HousePublisherRefundReceiptStatement, MarketplaceReceiptStatement, MarketplaceReceipt, MarketplaceReceiptReadRequest, MarketplaceReceiptFound, MarketplaceNoMovement, MarketplaceReceiptReadRefusal, MarketplaceReceiptReadRefused, MarketplaceReceiptReadResponse, } from './marketplace-receipt.js';
658
704
  export type { StoreSignedStatement, MarketplaceOrderRequest, MarketplaceRefundRequest, MarketplaceOrderReceipt, MarketplaceRefundReceipt, MarketplaceOrderSettled, MarketplaceOrderRefused, MarketplaceOrderResponse, MarketplaceOrderRefusal, MarketplaceRefundSettled, MarketplaceRefundRefused, MarketplaceRefundResponse, MarketplaceRefundRefusal, } from './marketplace-settlement.js';
package/dist/index.js CHANGED
@@ -92,6 +92,45 @@
92
92
  * this money surface gains that Piper's own contracts lack, since theirs share
93
93
  * one global version bumped across all twenty-two together (CAY-0026).
94
94
  *
95
+ * `0.28.0` → `0.29.0` publishes and SERVES the Handle change (CAY-0091):
96
+ * `ROUTES.handleChange`, `POST /api/handle-changes`, mapped to the new operation
97
+ * `'change-handle'`, money-grade in `ASSURANCE_POLICY`; `ROUTES.handleChangeReceipt`,
98
+ * `GET /api/handle-changes/receipt`, mapped to the new read-scope operation
99
+ * `'read-handle-change-receipt'`; the assertion claim `handleChangeReference`, which
100
+ * travels with those two operations in both directions; the client methods
101
+ * `changeHandle` and `readHandleChangeReceipt`; the signed {@link HandleChangeReceipt}
102
+ * and its one encoding, {@link encodeHandleChangeReceiptStatement}; and
103
+ * `CaysoApiErrorCode` gains {@link HandleChangeRefusal} and
104
+ * {@link HandleChangeReceiptReadRefusal} by reference and the seam codes
105
+ * `handle_change_store_not_configured`, `handle_change_price_not_configured` and
106
+ * `handle_change_payee_not_configured`. BREAKING in the package's sense because closed
107
+ * tables grew. Additive on the wire: no route a `0.28.0` consumer calls changed or
108
+ * answers anything new, and a credential a `0.28.0` issuer mints carries neither the new
109
+ * scope keys nor the new claim (CAY-0086).
110
+ *
111
+ * `0.27.0` → `0.28.0` SERVES a Citizen publisher's marketplace sale (CAY-0090):
112
+ * `POST /api/marketplace/orders` settles a signed order whose payee is a
113
+ * `citizen_publisher` as one `marketplace_settlement`, answering the
114
+ * `MarketplaceSettlementReceiptStatement` `0.24.0` already published. The share is
115
+ * read from a new signed-order fact, `communityShareBasisPoints`, which Store Platform
116
+ * signs beside `allocationPolicyVersion`. {@link MarketplaceOrderRefusal} gains
117
+ * `allocation_share_out_of_range` and `payee_wallet_is_the_buyers`, and
118
+ * `allocation_policy_version_not_configured` is retired (kept, never answered). The
119
+ * operator `MovementKind` gains `marketplace_settlement`. BREAKING in the package's
120
+ * sense because closed tables grew. On the wire, every order a `0.27.0` consumer could
121
+ * present — one Store Platform signed without the new fact — is answered exactly as
122
+ * before, a Citizen publisher's with `third_party_marketplace_sale_not_served`; only an
123
+ * order carrying the new fact can reach a new code (CAY-0086).
124
+ *
125
+ * `0.26.0` → `0.27.0` publishes and SERVES the Citizen transfer (CAY-0088):
126
+ * `ROUTES.citizenTransfer`, `POST /api/citizen-transfers`, mapped to the new
127
+ * operation `'make-citizen-transfer'`, money-grade in `ASSURANCE_POLICY`; the client
128
+ * method `makeCitizenTransfer`; and `CaysoApiErrorCode` gains
129
+ * {@link CitizenTransferRefusal} by reference and the seam code
130
+ * `citizen_transfer_store_not_configured`. It is BREAKING in the package's sense
131
+ * because three closed tables grew. It is additive on the wire: no route a `0.26.0`
132
+ * consumer calls changed or answers anything new (CAY-0086).
133
+ *
95
134
  * `0.25.0` → `0.26.0` publishes the operator read surface on its own entry point,
96
135
  * `@percayso-community/cayso-contracts/operator` (CAY-0026 Amendment 4), and describes
97
136
  * it in the schema under `operator`: the movement list, the purchase list and the
@@ -529,7 +568,7 @@
529
568
  * have needed a breaking bump on their own. Under semver below `1.0.0` the minor
530
569
  * is where a breaking change goes, so the minor moves and the patch does not.
531
570
  */
532
- export const CONTRACT_VERSION = '0.26.0';
571
+ export const CONTRACT_VERSION = '0.29.0';
533
572
  /** The package's own name, so a consumer can name what it depends on. */
534
573
  export const packageName = '@percayso-community/cayso-contracts';
535
574
  /**
@@ -663,3 +702,8 @@ export { REQUEST_RATE_LIMITED } from './errors.js';
663
702
  * one of the four.
664
703
  */
665
704
  export { MARKETPLACE_RECEIPT_SIGNATURE_ALGORITHM, encodeMarketplaceReceiptStatement, } from './marketplace-receipt.js';
705
+ /**
706
+ * The one encoding of a Handle-change receipt statement, called by the signer and by
707
+ * Percayso ID's verifier (CAY-0091).
708
+ */
709
+ export { encodeHandleChangeReceiptStatement } from './handle-change.js';
@@ -12,8 +12,10 @@
12
12
  *
13
13
  * **This is the contract surface only.** Nothing here settles anything. Since
14
14
  * `0.24.0` a route serves `settle-marketplace-order` for a house-publisher sale
15
- * (`ROUTES.marketplaceOrder`); the third-party sale and the refund are not served. The sale table, the registry of Store Platform order
16
- * keys and the ledger stay in this proprietary tree (CAY-0083 §3).
15
+ * (`ROUTES.marketplaceOrder`), and since `0.28.0` for a Citizen publisher's sale too,
16
+ * as one `marketplace_settlement` with a publisher leg and a Community leg (CAY-0090).
17
+ * The refund is not served, for either kind of sale. The sale table, the registry of
18
+ * Store Platform order keys and the ledger stay in this proprietary tree (CAY-0083 §3).
17
19
  *
18
20
  * ## Two operations, because two authorities are checked
19
21
  *
@@ -41,7 +43,9 @@
41
43
  * disagree eventually. What Cayso reads from them, once verified, is CAY-0082's
42
44
  * list — the order digest, the checkout's idempotency key, the price, the paying
43
45
  * wallet, the payee wallet or the house-publisher fact, and the allocation-policy
44
- * version.
46
+ * version — and, since `0.28.0`, the Community's share in basis points, which Store
47
+ * Platform snapshots into the checkout from its allocation policy and signs beside the
48
+ * version (CAY-0090, amending CAY-0082 §3).
45
49
  *
46
50
  * **Nothing about the sale is taken from the caller's word.** The house-publisher
47
51
  * fact, the payee wallet, the price and the idempotency key are all inside the
@@ -198,17 +202,33 @@ export type MarketplaceOrderRefusal =
198
202
  */
199
203
  | 'idempotency_key_reused_for_a_different_order'
200
204
  /**
201
- * The allocation-policy version the order names has no figure configured here, so
202
- * a third-party sale's split cannot be computed. A closed door, never a default
203
- * split. A house-publisher sale has no split and never meets it.
205
+ * RETIRED in `0.28.0`, and never answered since. It was published for a Cayso that
206
+ * would hold each allocation-policy version's figure itself; CAY-0090 put the figure
207
+ * in Store Platform's signed order instead, so no version can be "not configured"
208
+ * here. Kept in the union so a consumer's exhaustive map keeps compiling; a consumer
209
+ * may stop rendering it.
204
210
  */
205
211
  | 'allocation_policy_version_not_configured'
212
+ /**
213
+ * The Community's share the signed order carries (`communityShareBasisPoints`) is not
214
+ * a whole number from 0 to 10000, or, for a Citizen publisher's sale, leaves the
215
+ * publisher leg or the Community leg at zero cents at this price. A leg is a lot that
216
+ * arrives, and a lot of no cents is not one (CAY-0078, CAY-0090), so a share of 0 or
217
+ * 10000, or a price too small to split, is refused rather than settled with one leg.
218
+ */
219
+ | 'allocation_share_out_of_range'
206
220
  /** No wallet has the payee's id. */
207
221
  | 'payee_wallet_not_found'
208
222
  /** The payee's wallet is an Organisation Wallet or the Treasury. A paid sale's publisher is a Citizen (CAY-0082 §8). */
209
223
  | 'payee_wallet_not_a_citizens'
210
224
  /** No Citizen is bound to the payee's wallet now, for example because the binding was erased. */
211
225
  | 'payee_wallet_has_no_live_binding'
226
+ /**
227
+ * The payee's wallet is the buyer's own: a publisher buying their own listing. A
228
+ * movement never runs from a wallet to itself, so nothing is paid and nothing is
229
+ * written (CAY-0090).
230
+ */
231
+ | 'payee_wallet_is_the_buyers'
212
232
  /**
213
233
  * A third-party sale: the buyer's unrestricted cents are short. Restricted cents
214
234
  * are never counted and never drawn for one (CAY-0076), so a wallet holding enough
@@ -85,7 +85,7 @@ export declare const OPERATOR_READ_ROUTES: {
85
85
  * movement list's `kind` filter refuses anything outside it (`400 invalid_kind`), and
86
86
  * every row's `kind` is one of these.
87
87
  */
88
- export type MovementKind = 'issuance' | 'purchase' | 'grant' | 'charge' | 'payout' | 'transfer' | 'burn' | 'forfeit' | 'commercial_payment' | 'quoted_sale' | 'buy_back' | 'quoted_sale_compensation';
88
+ export type MovementKind = 'issuance' | 'purchase' | 'grant' | 'charge' | 'payout' | 'transfer' | 'burn' | 'forfeit' | 'commercial_payment' | 'quoted_sale' | 'buy_back' | 'quoted_sale_compensation' | 'marketplace_settlement';
89
89
  /** Every {@link MovementKind}, in the service's order — for a filter a person picks from. */
90
90
  export declare const MOVEMENT_KINDS: readonly MovementKind[];
91
91
  /**
@@ -96,6 +96,11 @@ export declare const MOVEMENT_KINDS: readonly MovementKind[];
96
96
  * `fromWalletId` is null when the cents came from unissued (an issuance) and
97
97
  * `toWalletId` is null when they went back to it (a burn, a forfeiture). Both are
98
98
  * wallet ids — the operator-facing handle — and never an identity.
99
+ *
100
+ * A `marketplace_settlement` lands in two wallets (CAY-0082 §1): `toWalletId` is the
101
+ * publisher's Citizen Wallet and `cents` the whole price the buyer paid. The Community
102
+ * leg's share of it went to the Treasury inside the same movement, and the row does not
103
+ * itemise it; the signed receipt names both legs (CAY-0090).
99
104
  */
100
105
  export interface OperatorMovementRow {
101
106
  /** The row's own opaque handle: a stable list key, not an identity. */
package/dist/operator.js CHANGED
@@ -45,4 +45,5 @@ export const MOVEMENT_KINDS = [
45
45
  'quoted_sale',
46
46
  'buy_back',
47
47
  'quoted_sale_compensation',
48
+ 'marketplace_settlement',
48
49
  ];
package/dist/routes.d.ts CHANGED
@@ -76,6 +76,24 @@
76
76
  * (CAY-0079 §2). A path that took a payee's identity would make this route a
77
77
  * directory of identities for anyone holding a mandate.
78
78
  *
79
+ * ## Why `citizenTransfer` names only the payee, and never the sender
80
+ *
81
+ * `POST /api/citizen-transfers` is a Citizen transferring Cayso to another Citizen
82
+ * (CAY-0088). The body carries the payee's wallet id, the cents and the key. The
83
+ * sending wallet is NOT in the request: it is the verified subject's own Citizen
84
+ * Wallet, resolved inside `apps/api`, because a sending wallet the caller named would
85
+ * be the caller's claim about whose cents these are (CAY-0021).
86
+ *
87
+ * ## Why `handleChange` and `handleChangeReceipt` take nothing but the credential
88
+ *
89
+ * `POST /api/handle-changes` is a Citizen's Handle change, charged by Cayso, and
90
+ * `GET /api/handle-changes/receipt` asks whether a change reference was charged
91
+ * (CAY-0091). Nothing either request could carry is the caller's to say: the paying
92
+ * wallet is the verified subject's own, the price is Cayso's configuration, and the
93
+ * change reference is inside the signed assertion. So there is nothing to send but the
94
+ * credential, as with the founder claim. They are two paths because they are two
95
+ * operations, and one path carries one scope requirement at the gate.
96
+ *
79
97
  * ## Why the citizen market is FOUR PATHS, and none of them names a wallet
80
98
  *
81
99
  * `citizenMarket` reads the book, `placeQuote` places a quote, and `withdrawQuote`
@@ -146,6 +164,18 @@ export declare const ROUTES: {
146
164
  readonly method: "POST";
147
165
  readonly path: "/api/commercial-payments";
148
166
  };
167
+ readonly citizenTransfer: {
168
+ readonly method: "POST";
169
+ readonly path: "/api/citizen-transfers";
170
+ };
171
+ readonly handleChange: {
172
+ readonly method: "POST";
173
+ readonly path: "/api/handle-changes";
174
+ };
175
+ readonly handleChangeReceipt: {
176
+ readonly method: "GET";
177
+ readonly path: "/api/handle-changes/receipt";
178
+ };
149
179
  readonly citizenMarket: {
150
180
  readonly method: "GET";
151
181
  readonly path: "/api/citizen-market";
package/dist/routes.js CHANGED
@@ -91,6 +91,24 @@
91
91
  * (CAY-0079 §2). A path that took a payee's identity would make this route a
92
92
  * directory of identities for anyone holding a mandate.
93
93
  *
94
+ * ## Why `citizenTransfer` names only the payee, and never the sender
95
+ *
96
+ * `POST /api/citizen-transfers` is a Citizen transferring Cayso to another Citizen
97
+ * (CAY-0088). The body carries the payee's wallet id, the cents and the key. The
98
+ * sending wallet is NOT in the request: it is the verified subject's own Citizen
99
+ * Wallet, resolved inside `apps/api`, because a sending wallet the caller named would
100
+ * be the caller's claim about whose cents these are (CAY-0021).
101
+ *
102
+ * ## Why `handleChange` and `handleChangeReceipt` take nothing but the credential
103
+ *
104
+ * `POST /api/handle-changes` is a Citizen's Handle change, charged by Cayso, and
105
+ * `GET /api/handle-changes/receipt` asks whether a change reference was charged
106
+ * (CAY-0091). Nothing either request could carry is the caller's to say: the paying
107
+ * wallet is the verified subject's own, the price is Cayso's configuration, and the
108
+ * change reference is inside the signed assertion. So there is nothing to send but the
109
+ * credential, as with the founder claim. They are two paths because they are two
110
+ * operations, and one path carries one scope requirement at the gate.
111
+ *
94
112
  * ## Why the citizen market is FOUR PATHS, and none of them names a wallet
95
113
  *
96
114
  * `citizenMarket` reads the book, `placeQuote` places a quote, and `withdrawQuote`
@@ -134,6 +152,9 @@ export const ROUTES = {
134
152
  purchaseResult: { method: 'GET', path: '/api/purchases/:purchaseId' },
135
153
  purchaseCancel: { method: 'POST', path: '/api/purchases/:purchaseId/cancel' },
136
154
  commercialPayment: { method: 'POST', path: '/api/commercial-payments' },
155
+ citizenTransfer: { method: 'POST', path: '/api/citizen-transfers' },
156
+ handleChange: { method: 'POST', path: '/api/handle-changes' },
157
+ handleChangeReceipt: { method: 'GET', path: '/api/handle-changes/receipt' },
137
158
  citizenMarket: { method: 'GET', path: '/api/citizen-market' },
138
159
  placeQuote: { method: 'POST', path: '/api/quotes' },
139
160
  withdrawQuote: { method: 'POST', path: '/api/quote-withdrawals' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percayso-community/cayso-contracts",
3
- "version": "0.26.0",
3
+ "version": "0.29.0",
4
4
  "description": "The versioned capability contract surface through which anything outside this repository reads a wallet. Published; keep it the surface of every consumer outside this tree, Piper and the Percayso Cloud, and nothing internal.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {