@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,215 @@
1
+ /**
2
+ * `citizenship` — provisioning a citizen, and the sign-up grant that arrives
3
+ * with it (CAY-0031).
4
+ *
5
+ * Provisioning is an EXPLICIT, IDEMPOTENT CALL. It is not a side effect of
6
+ * reading a wallet, and it is not an event on a bus. `cayso.wallet` used to
7
+ * create a wallet on the first authenticated read (CAY-0022); it no longer
8
+ * creates anything, because a citizen who never opens a wallet page would never
9
+ * have been credited, and two concurrent first reads had no defined outcome.
10
+ * CAY-0031 amends that record: the wallet and the sign-up grant are
11
+ * provisioned together, by one call, in one transaction.
12
+ *
13
+ * ## A consumer can ask, and asking provisions nothing
14
+ *
15
+ * {@link ROUTES.citizenship} is a READ. It answers {@link CitizenshipView} for
16
+ * the calling citizen and writes nothing whatever the answer is — so a consumer
17
+ * may render "not provisioned yet" without that render being the thing that
18
+ * provisions. {@link ROUTES.citizenshipProvision} is the write, and it is the
19
+ * only one.
20
+ *
21
+ * The two are separate PATHS, not one path with two methods, and that is
22
+ * deliberate: the scope requirement CAY-0030's gate enforces is registered
23
+ * per-path, so a `GET` and a `POST` sharing `/api/citizenship` would share one
24
+ * scope requirement and the read would silently inherit the write's — or, worse,
25
+ * the write would inherit the read's. One path, one operation, one scope.
26
+ *
27
+ * ## The state is a CLOSED VALUE, never prose
28
+ *
29
+ * {@link Citizenship} is a discriminated union on `state`, so a consumer switches
30
+ * on a literal rather than pattern-matching a sentence. There is no `pending`, no
31
+ * `provisioning` and no `partial`. The call commits the wallet and the grant
32
+ * together or it commits neither, so a state meaning "half of it happened" would
33
+ * describe something this surface cannot produce.
34
+ *
35
+ * ## The third state is not "in between" (CAY-0081)
36
+ *
37
+ * A founder wallet is created at genesis, holding a founder's allocation, and is
38
+ * bound to its Citizen by a founder claim. A claimed founder was never provisioned
39
+ * and must never be: no sign-up grant reaches them by any route. So they are
40
+ * neither of the first two states. Reporting them as `not-provisioned` would
41
+ * invite the one call that must not happen, and a background retry of it would
42
+ * never stop. {@link CitizenshipFounderClaimed} says this Citizen holds a wallet,
43
+ * was never provisioned, and is not waiting to be.
44
+ *
45
+ * {@link FounderClaimRefusal} is the founder claim's closed set of refusals,
46
+ * answered by {@link ROUTES.citizenshipFounderClaim}. It carries
47
+ * `founder_wallet_lapsed` before anything can produce it, so that a consumer
48
+ * renders every member before it can arrive.
49
+ *
50
+ * ## NO NUMBERS LIVE HERE, AND THAT IS THE POINT
51
+ *
52
+ * {@link SignUpGrantTerms} names the sign-up grant's amount and its
53
+ * restriction period as FIELDS A CONSUMER READS AT RUNTIME. Their source is the
54
+ * canon PRD — the deck's tiered schedule, recorded in the canon's own questions
55
+ * table as governing — and they reach this surface as configuration read in one
56
+ * place, exactly as the policy figures do.
57
+ *
58
+ * **Do not "fix" this by inlining a figure.** A literal here would be a second
59
+ * home for a number whose only home is the canon, and the two go stale in
60
+ * opposite directions; the canon's own record of the figure exists precisely
61
+ * because an earlier verbal one was superseded. `cents` is a field, not a value.
62
+ * `restrictionMonths` is a field, not a duration. The same rule `cayso.wallet`
63
+ * states for `publishedValuePence` (CLAUDE.md, "Where numbers live").
64
+ *
65
+ * ## What this file must not decide
66
+ *
67
+ * - **No operator path.** There is no field here by which somebody other than
68
+ * the citizen could be provisioned, because in v1 there is not one: the call
69
+ * is authorised by the citizen's own assertion and nothing else (CAY-0031).
70
+ * The absence is structural, so there is nothing to refuse at runtime.
71
+ * - **No sweep, no forfeiture, no vesting.** `expiresAt` is the promise that
72
+ * was recorded (CAY-0020). Recording it is not the same as acting on it, and
73
+ * nothing here acts on it.
74
+ * - **No carve, no holding, no movement shape.** `movementId` is a handle a
75
+ * consumer can quote back; the movement itself is not a consumer's business
76
+ * and is not on this surface. Naming a grant `'restricted'` on arrival is where
77
+ * it LANDS; what happens when it moves is CAY-0074's — a carve does not move
78
+ * restriction, and a restricted lot may not be transferred.
79
+ */
80
+ import type { Cents, Millis, WalletRecord } from './wallet.js';
81
+ /**
82
+ * The wire code a read refuses with when the citizen it names has not been
83
+ * provisioned — a value, so a consumer can compare against the contract's own
84
+ * string rather than retyping ours.
85
+ *
86
+ * `cayso.wallet`'s read answers this, with no wallet and no balance, for a
87
+ * citizen who has never been provisioned. It is deliberately the same spelling
88
+ * Piper already uses for the same state: one word for one condition across the
89
+ * two pillars, rather than two that have to be mapped.
90
+ */
91
+ export declare const NOT_PROVISIONED: "not_provisioned";
92
+ /**
93
+ * The terms a provisioning applies: how much the sign-up grant is, and how
94
+ * long it lands restricted for.
95
+ *
96
+ * Read at runtime, from the deployment that serves them. See the file docblock:
97
+ * these are the canon's figures reaching a consumer as configuration, and they
98
+ * are never restated as literals anywhere — not here, not in a comment, not in
99
+ * the schema, not in an ADR.
100
+ */
101
+ export interface SignUpGrantTerms {
102
+ /** The sign-up grant, in cents. A field, not a value. */
103
+ readonly cents: Cents;
104
+ /** How many months the grant lands restricted for. A field, not a duration. */
105
+ readonly restrictionMonths: number;
106
+ }
107
+ /**
108
+ * The sign-up grant a provisioned citizen actually received — one movement,
109
+ * final and append-only (CAY-0023). There is at most one of these per wallet,
110
+ * ever.
111
+ *
112
+ * It lands RESTRICTED and carries its expiry, because granted Cayso lands
113
+ * restricted and purchased Cayso does not (CONTEXT.md). `restriction` is typed as
114
+ * the literal `'restricted'` rather than the two-member vocabulary: a sign-up
115
+ * grant that arrived unrestricted is not a thing this surface can describe,
116
+ * which is the type half of the invariant the schema also enforces.
117
+ */
118
+ export interface SignUpGrant {
119
+ /** The movement that carried the cents. Final; there is no second one. */
120
+ readonly movementId: string;
121
+ /** What arrived, in cents. The figure of the day it was granted, not today's terms. */
122
+ readonly cents: Cents;
123
+ /** Granted Cayso lands restricted. The literal is the invariant. */
124
+ readonly restriction: 'restricted';
125
+ /** The promised date the restriction lifts. Recorded, never swept (CAY-0020). */
126
+ readonly expiresAt: Millis;
127
+ readonly grantedAt: Millis;
128
+ }
129
+ /** A citizen this deployment has never provisioned. No wallet, no grant. */
130
+ export interface CitizenshipNotProvisioned {
131
+ readonly state: 'not-provisioned';
132
+ }
133
+ /**
134
+ * A provisioned citizen: the wallet, and the one sign-up grant that arrived
135
+ * with it. Both, always — they were committed together, so a shape carrying one
136
+ * without the other would describe a state the call cannot produce.
137
+ */
138
+ export interface CitizenshipProvisioned {
139
+ readonly state: 'provisioned';
140
+ readonly wallet: WalletRecord;
141
+ readonly signUpGrant: SignUpGrant;
142
+ }
143
+ /**
144
+ * A Citizen bound to a founder wallet by a founder claim (CAY-0081). The wallet,
145
+ * and NO sign-up grant.
146
+ *
147
+ * The absence is the type half of "a claimed founder never receives a sign-up
148
+ * grant". There is no `signUpGrant` field to be null, empty or zero, so a
149
+ * consumer cannot render a grant that was never made. The founder allocation is
150
+ * not described here either. It is the wallet's balance, read like any other
151
+ * balance, and not a fact about the claim.
152
+ *
153
+ * This is the answer to the founder claim, and the answer the citizenship read
154
+ * and the provisioning call give for the same Citizen. The provisioning call
155
+ * mints nothing when it gives it.
156
+ */
157
+ export interface CitizenshipFounderClaimed {
158
+ readonly state: 'founder-claimed';
159
+ readonly wallet: WalletRecord;
160
+ }
161
+ /**
162
+ * Not provisioned, provisioned, or bound by a founder claim. Switch on `state`.
163
+ * There are three answers and a consumer's switch must handle all three.
164
+ */
165
+ export type Citizenship = CitizenshipNotProvisioned | CitizenshipProvisioned | CitizenshipFounderClaimed;
166
+ /**
167
+ * Why a founder claim is refused. A closed union, so a consumer renders a
168
+ * member instead of pattern-matching prose (CAY-0023), and every member is a
169
+ * wire code. The status each is answered with is recorded in CAY-0081 and
170
+ * served by the route. A new reason is a new member and a version bump.
171
+ *
172
+ * The gate's refusals of a credential are NOT repeated here. A bad credential, a
173
+ * missing scope, an unaccepted verification method and a claim credential with
174
+ * no founder claim reference are all refused before the claim is asked about,
175
+ * with the codes the gate already answers.
176
+ */
177
+ export type FounderClaimRefusal =
178
+ /**
179
+ * This deployment has performed no founder genesis, so there is nothing to
180
+ * claim. Unconfigured, so closed.
181
+ */
182
+ 'founder_genesis_not_performed'
183
+ /** The founder claim reference names no founder wallet. */
184
+ | 'founder_claim_reference_unknown'
185
+ /** Another Citizen has already claimed this founder wallet. A claim is made once. */
186
+ | 'founder_wallet_already_claimed'
187
+ /**
188
+ * The founder wallet's allocation lapsed and was forfeited to unissued. An
189
+ * empty founder wallet is not an allocation, and claiming it would leave a
190
+ * founder with neither an allocation nor a sign-up grant.
191
+ *
192
+ * In the union from its first version although nothing produces it yet: the
193
+ * lapse is built last (CAY-0081, open decision 6). A consumer renders it now
194
+ * rather than meeting it later as a change to a closed union.
195
+ */
196
+ | 'founder_wallet_lapsed'
197
+ /**
198
+ * The caller is already bound to another wallet, provisioned ordinarily or
199
+ * claimed through another founder wallet. A Citizen holds one Citizen Wallet.
200
+ */
201
+ | 'citizen_already_holds_a_wallet';
202
+ /**
203
+ * The answer to both citizenship routes: the current terms, and whether this
204
+ * citizen has been provisioned.
205
+ *
206
+ * `terms` is present either way on purpose. A consumer showing a citizen what
207
+ * provisioning would give them needs the figures BEFORE provisioning, and a
208
+ * consumer showing what a past provisioning gave them reads
209
+ * {@link SignUpGrant.cents} — the two can differ, because terms are today's
210
+ * configuration and a grant is a historical fact.
211
+ */
212
+ export interface CitizenshipView {
213
+ readonly terms: SignUpGrantTerms;
214
+ readonly citizenship: Citizenship;
215
+ }
@@ -0,0 +1,26 @@
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 wire code a read refuses with when the citizen it names has not been
18
+ * provisioned — a value, so a consumer can compare against the contract's own
19
+ * string rather than retyping ours.
20
+ *
21
+ * `cayso.wallet`'s read answers this, with no wallet and no balance, for a
22
+ * citizen who has never been provisioned. It is deliberately the same spelling
23
+ * Piper already uses for the same state: one word for one condition across the
24
+ * two pillars, rather than two that have to be mapped.
25
+ */
26
+ export const NOT_PROVISIONED = 'not_provisioned';
@@ -0,0 +1,288 @@
1
+ /**
2
+ * `@percayso-community/cayso-contracts/client` — the thin client (CAY-0026).
3
+ *
4
+ * This ships from a *separate* entry point so a consumer wanting nothing but the
5
+ * types (`@percayso-community/cayso-contracts`) takes on no runtime at all. The barrel reaches none
6
+ * of this file; a test on the built output is what enforces that, not this
7
+ * comment (CAY-0026: the client is on its own subpath for exactly this reason).
8
+ *
9
+ * The point of the client is to make the correct call the *easy* call: it owns
10
+ * the fetch, the `Authorization: Bearer <assertion>` header and the idempotent
11
+ * retry once, so a consumer does not own each of them badly. Piper's tree is the
12
+ * cautionary tale — 22 hand-rolled `lib/*Api.ts` modules, one of which redeclares
13
+ * the wallet shape and loses its guarantee.
14
+ *
15
+ * ## The assertion is fetched per request, never cached
16
+ *
17
+ * `getAssertion()` is called *inside* each network attempt, never once at
18
+ * construction and never into a variable that outlives the call. An assertion is
19
+ * short-lived (60–120 seconds; see `assertion.ts`), so a credential fetched once
20
+ * and reused is a bug that only surfaces under load — which is to say in
21
+ * production, on the money path (CAY-0021: the assertion is verified per request
22
+ * and never cached; the caller must mint it per request in kind). A *retried*
23
+ * request re-fetches for the same reason: a backoff can outlast the credential,
24
+ * so each attempt gets a fresh one. That the retry re-fetches is a decision, not
25
+ * an accident.
26
+ *
27
+ * ## One method per route, no method that is not
28
+ *
29
+ * Every method reads its method and path from `ROUTES` — no URL is hand-written
30
+ * here. `ROUTES` exists precisely so that no consumer hand-writes one, and a
31
+ * client that hand-wrote them would be the first consumer to break the rule.
32
+ *
33
+ * ## The retry rule, and only this rule
34
+ *
35
+ * Retry only a request that is safe to repeat — a `GET`, or a `POST` *carrying an
36
+ * idempotency key* (that key is the whole reason a purchase is safe to send
37
+ * twice; CAY-0023) — and only on 429, 502, 503, 504 and a network failure. Three
38
+ * attempts, exponential backoff, honouring `Retry-After`. A 4xx that is not 429
39
+ * is *never* retried: retry a declined card and it becomes a fraud signal; fail
40
+ * to retry a flaky network and somebody's purchase is lost.
41
+ *
42
+ * `Retry-After` is honoured in both forms the header takes: an integer count of
43
+ * seconds, and an HTTP-date. A value in neither form is ignored and the computed
44
+ * backoff is used instead.
45
+ *
46
+ * ## What this file is not
47
+ *
48
+ * Transport, not a cache. The client holds no wallet, no balance, no purchase, no
49
+ * assertion and no ETag; `createCaysoClient` hands back an object closing over the
50
+ * options and nothing else. It never converts, rounds, formats or divides a money
51
+ * amount — amounts pass through untouched (CLAUDE.md: integer cents, always). And
52
+ * it declares no dependency — `fetch` is global on Node 26, and if you find
53
+ * yourself wanting a retry library you have found the part of this slice that is
54
+ * the point.
55
+ *
56
+ * ## The one line it logs, and why it is safe
57
+ *
58
+ * The client is otherwise silent — a debug line printing an `Authorization` header
59
+ * is a credential in a log aggregator, and there is none here. It makes exactly one
60
+ * exception: {@link CaysoClient.getVersion} warns, ONCE per client instance, when
61
+ * the server reports a contract version whose MAJOR differs from the one this client
62
+ * was built against ({@link CONTRACT_VERSION}). A different major is a
63
+ * backwards-incompatible surface, and a consumer that never hears about it debugs
64
+ * shape mismatches blind — so this is the one thing worth a line. The line carries
65
+ * ONLY version strings (the server's and this client's); it never touches the
66
+ * assertion, a header, a wallet id or any identity. It is routed through an
67
+ * injectable `warn` (default `console.warn`) so a consumer can capture it and a test
68
+ * can prove both what it says and that it says it once — never per request.
69
+ */
70
+ import type { BuyBackMade, BuyBackRequest, CitizenMarketViewed, QuotePlaced, QuoteRequest, QuoteWithdrawalRequest, QuoteWithdrawn } from './citizen-market.js';
71
+ import type { CitizenshipFounderClaimed, CitizenshipView } from './citizenship.js';
72
+ import type { CommercialPaymentMade, CommercialPaymentRequest } from './commercial-payment.js';
73
+ import type { OrganisationView } from './organisation.js';
74
+ import type { PurchaseAuthorised, PurchaseCancelled, PurchaseRequest, PurchaseResult, PurchaseTerminal } from './purchase.js';
75
+ import type { WalletView } from './wallet.js';
76
+ /**
77
+ * The version surface `GET /api/version` answers (slice 29). Four fields, each
78
+ * from ONE source on the server: the `contractVersion` this deployment publishes,
79
+ * the `supportedContractVersions` it will still speak (the negotiation set), the
80
+ * `schemaVersion` of its database (the migration runner's own number) and the
81
+ * `commit` it was built from — `'unknown'`, honestly, when the build did not stamp
82
+ * one. It carries no wallet, no balance and no economic number; a version endpoint
83
+ * reports versions.
84
+ */
85
+ export interface CaysoVersion {
86
+ readonly contractVersion: string;
87
+ readonly supportedContractVersions: readonly string[];
88
+ readonly schemaVersion: number;
89
+ readonly commit: string;
90
+ }
91
+ export interface CaysoClientOptions {
92
+ readonly baseUrl: string;
93
+ /** Called before every request. Assertions are short-lived, so never cache one. */
94
+ getAssertion(): Promise<string>;
95
+ /** Injectable for tests. Defaults to global fetch. */
96
+ fetchImpl?: typeof fetch;
97
+ /**
98
+ * Where a contract-version-skew warning goes. Defaults to `console.warn`. This is
99
+ * the ONLY thing the client ever logs, it fires at most ONCE per client instance,
100
+ * and its message is version strings alone — never an assertion, a header, a
101
+ * wallet id or any identity (see the file docblock). Injected so a consumer can
102
+ * route it and a test can capture it.
103
+ */
104
+ warn?: (message: string) => void;
105
+ }
106
+ /**
107
+ * One method per entry in `ROUTES`, and no method that is not. `getMyWallet`
108
+ * answers `null` where the contract says a wallet may not exist yet, rather than
109
+ * throwing.
110
+ */
111
+ export interface CaysoClient {
112
+ getMyWallet(): Promise<WalletView | null>;
113
+ /**
114
+ * Whether the caller has been provisioned, and on what terms. Reads only —
115
+ * calling this provisions nobody, which is the whole reason it is a separate
116
+ * route from {@link provisionCitizenship} (CAY-0031).
117
+ */
118
+ getMyCitizenship(): Promise<CitizenshipView>;
119
+ /**
120
+ * Provision the caller: a wallet and its sign-up grant, committed together.
121
+ *
122
+ * IDEMPOTENT BY THE CALLER'S OWN IDENTITY, so it carries no idempotency key.
123
+ * The key a purchase needs exists because two purchases by one citizen are a
124
+ * legitimate thing to want and only the key tells them apart; two provisionings
125
+ * of one citizen are the same act by definition, and the schema makes the second
126
+ * one a no-op that answers with the first one's result. Calling it twice is
127
+ * safe, and so is retrying it.
128
+ */
129
+ provisionCitizenship(): Promise<CitizenshipView>;
130
+ /**
131
+ * Claim the founder wallet the caller's assertion names, and answer the wallet
132
+ * record (CAY-0081).
133
+ *
134
+ * IT TAKES NO ARGUMENT, for {@link provisionOrganisation}'s reason. The founder
135
+ * wallet is named by the founder claim reference, which travels inside the signed
136
+ * assertion and nowhere else; a reference passed beside the credential would be
137
+ * the caller's claim about which allocation is theirs (CAY-0021).
138
+ *
139
+ * IDEMPOTENT BY THE CALLER'S OWN IDENTITY, like {@link provisionCitizenship}: the
140
+ * Citizen who already claimed the wallet gets the same answer and nothing is
141
+ * written. So it carries no idempotency key and a retry is safe. Each attempt
142
+ * fetches a fresh assertion, which is what a single-use `jti` needs.
143
+ *
144
+ * The answer is `founder-claimed` alone, with no `terms` beside it. A founder is
145
+ * never offered a sign-up grant, so the grant's terms are not part of the answer.
146
+ * A refusal arrives as a thrown {@link CaysoApiError} whose `code` is a
147
+ * `FounderClaimRefusal` member, or one of the gate's codes.
148
+ */
149
+ claimFounderWallet(): Promise<CitizenshipFounderClaimed>;
150
+ /**
151
+ * Provision the ORGANISATION the caller's assertion attests authority for, and
152
+ * answer its wallet (CAY-0059).
153
+ *
154
+ * IT TAKES NO ARGUMENT, AND THAT IS THE DECISION RATHER THAN AN OVERSIGHT. The
155
+ * Organisation ID is read from the organisation-authority claim on the verified
156
+ * assertion and from nowhere else — so there is no value a caller could pass
157
+ * that would reach the holder key, and a signature here naming an organisation
158
+ * would be the wallet-creation oracle CAY-0057 describes.
159
+ *
160
+ * IDEMPOTENT on the attested Organisation ID, exactly as
161
+ * {@link provisionCitizenship} is idempotent on the caller's own identity, so it
162
+ * carries no idempotency key and a repeat is the same act rather than a second
163
+ * one. A credential carrying no organisation authority is refused; absence
164
+ * provisions nothing.
165
+ */
166
+ provisionOrganisation(): Promise<OrganisationView>;
167
+ /**
168
+ * Authorise a purchase — or REPLAY one (CAY-0032). Posting the same
169
+ * `idempotencyKey` twice returns the ORIGINAL authorisation, at the original
170
+ * price, with a freshly minted client secret; the purchase is never minted twice
171
+ * and there is no second hold at the payment gateway. Resume IS this call, which
172
+ * is why there is no `resumePurchase` beside it.
173
+ *
174
+ * The answer is the contract's declared union, MINUS its refused branch: a
175
+ * refusal arrives as a non-2xx and this client turns every non-2xx into a thrown
176
+ * {@link CaysoApiError} whose `code` is that branch's `error` — one closed reason,
177
+ * reached through the mechanism a consumer already handles. So a caller switches
178
+ * on `outcome` over the two branches it can be handed, and catches for the third.
179
+ *
180
+ * `'terminal'` means the key's purchase has finished and will not change: a
181
+ * repeat of it will keep answering `'terminal'` forever, and the remedy is a new
182
+ * purchase under a NEW key.
183
+ */
184
+ authorisePurchase(req: PurchaseRequest): Promise<PurchaseAuthorised | PurchaseTerminal>;
185
+ getPurchase(purchaseId: string): Promise<PurchaseResult>;
186
+ /**
187
+ * Cancel a purchase the caller started, releasing its reservation (CAY-0032).
188
+ *
189
+ * IDEMPOTENT: cancelling an already-cancelled purchase answers the same
190
+ * {@link PurchaseCancelled} and is not an error, so a retry after a dropped
191
+ * connection is safe and carries no key of its own — the purchase id IS the key.
192
+ * A purchase that has already reached a terminal status is REFUSED, and the
193
+ * refusal arrives as a thrown {@link CaysoApiError} whose `code` is the closed
194
+ * {@link import('./purchase.js').PurchaseCancelRefusalReason}; a succeeded purchase can never be
195
+ * cancelled, because undoing an issued and captured purchase is a refund and
196
+ * that is a different act.
197
+ */
198
+ cancelPurchase(purchaseId: string): Promise<PurchaseCancelled>;
199
+ /**
200
+ * Pay a Citizen from an Organisation Wallet — or REPLAY a payment already made
201
+ * under the same key (CAY-0079).
202
+ *
203
+ * A replay with the same key, payee and cents answers the movement already
204
+ * written, in the same shape, so a retry after a dropped connection is safe and
205
+ * never moves cents twice. The same key with a different payee or a different
206
+ * number of cents is refused, never answered at either.
207
+ *
208
+ * The answer is {@link CommercialPaymentMade} alone. A refusal arrives as a
209
+ * thrown {@link CaysoApiError} whose `code` is a `CommercialPaymentRefusal`
210
+ * member, one of the gate's codes, or a seam code, as {@link authorisePurchase}'s
211
+ * refusals do. A refusal means nothing moved.
212
+ */
213
+ makeCommercialPayment(req: CommercialPaymentRequest): Promise<CommercialPaymentMade>;
214
+ /**
215
+ * Read the citizen market as the caller sees it (CAY-0080 §1.4): the published
216
+ * value and its band, both bounds per quote unit, the best bid and the best ask with
217
+ * their sizes, and the caller's own quotes. No match price and no history. Reading
218
+ * writes nothing. A refusal arrives as a thrown {@link CaysoApiError} whose `code`
219
+ * is a `CitizenMarketViewRefusal` member or a seam code.
220
+ */
221
+ getCitizenMarket(): Promise<CitizenMarketViewed>;
222
+ /**
223
+ * Place a quote — or REPLAY one already placed under the same key (CAY-0080 §1).
224
+ *
225
+ * A replay with the same key, side, price and size answers the quote already
226
+ * placed, in the same shape, so a retry after a dropped connection never places
227
+ * two. The answer says the quote was accepted and nothing about fills: what has
228
+ * matched is on {@link getCitizenMarket}, among the caller's own quotes. A refusal
229
+ * arrives as a thrown {@link CaysoApiError} whose `code` is a
230
+ * `QuotePlacementRefusal` member, one of the gate's codes, or a seam code, and
231
+ * means nothing was written.
232
+ */
233
+ placeQuote(req: QuoteRequest): Promise<QuotePlaced>;
234
+ /**
235
+ * Withdraw one of the caller's resting quotes, after its minimum resting time.
236
+ *
237
+ * IDEMPOTENT: withdrawing a quote the caller already withdrew answers the same
238
+ * {@link QuoteWithdrawn}, so a retry carries no key of its own — the quote id IS
239
+ * the key. A refusal arrives as a thrown {@link CaysoApiError} whose `code` is a
240
+ * `QuoteWithdrawalRefusal` member, and the quote rests as it was.
241
+ */
242
+ withdrawQuote(req: QuoteWithdrawalRequest): Promise<QuoteWithdrawn>;
243
+ /**
244
+ * Ask the Treasury to buy back unrestricted cents at the floor reference, in whole
245
+ * quote units — or REPLAY a buy-back already made under the same key (CAY-0080 §2.2).
246
+ *
247
+ * A replay with the same key and size answers the buy-back already made, in the same
248
+ * shape, so a retry after a dropped connection never sells twice. The same key with
249
+ * a different size is refused. A refusal arrives as a thrown {@link CaysoApiError}
250
+ * whose `code` is a `BuyBackRefusal` member, one of the gate's codes, or a seam code,
251
+ * and means nothing moved and nothing is paid.
252
+ */
253
+ requestBuyBack(req: BuyBackRequest): Promise<BuyBackMade>;
254
+ getVersion(): Promise<CaysoVersion>;
255
+ }
256
+ /**
257
+ * Raised for any non-2xx response and any network failure. `code` is parsed from
258
+ * the body's `{ error, message }`, falling back to `'request_failed'` when the
259
+ * body is absent, empty, unparseable or the wrong shape — the client never throws
260
+ * a `SyntaxError` out at a consumer. Exported as a value, extending `Error` with
261
+ * a fixed prototype and name, so `instanceof CaysoApiError` holds for a consumer.
262
+ */
263
+ export declare class CaysoApiError extends Error {
264
+ readonly code: string;
265
+ readonly status: number;
266
+ readonly detail?: string;
267
+ constructor(code: string, status: number, detail?: string);
268
+ }
269
+ /**
270
+ * The injectable timing seam, kept deliberately *off* `CaysoClientOptions` — the
271
+ * public options are `baseUrl`, `getAssertion` and `fetchImpl` and nothing more.
272
+ * The suite drives the backoff schedule through this without sleeping for real; a
273
+ * test suite that slept for real is a test suite people stop running.
274
+ */
275
+ interface CaysoClientInternals {
276
+ sleep(ms: number): Promise<void>;
277
+ }
278
+ export declare function createCaysoClient(options: CaysoClientOptions): CaysoClient;
279
+ /**
280
+ * The construction seam the test suite uses to inject `sleep`. Not part of the
281
+ * supported client surface — a consumer calls {@link createCaysoClient}. It lives
282
+ * here rather than on the public options type so the timing stays untestable-by-
283
+ * accident for a consumer and fully testable for the suite.
284
+ *
285
+ * @internal
286
+ */
287
+ export declare function createCaysoClientWithInternals(options: CaysoClientOptions, internals: CaysoClientInternals): CaysoClient;
288
+ export {};