@0xdivyanshh/tab-sdk 0.1.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,309 @@
1
+ import { REFUSAL_CODES, WEIGHT_REASONS } from '@0xdivyanshh/tab-protocol';
2
+ import { TabInvalidError } from "./errors.js";
3
+ import { parseAmount } from "./spend.js";
4
+ /**
5
+ * Validated, not cast.
6
+ *
7
+ * A cast would type an unknown string as a valid refusal code, and the UI would
8
+ * then render a rule that does not exist — or worse, `isRetryable` would return
9
+ * false for it and an agent would stop retrying something it should retry. An
10
+ * unrecognised rule is dropped, which shows as a refusal with no rule rather
11
+ * than a refusal with a fictional one.
12
+ */
13
+ function knownRule(value) {
14
+ return typeof value === 'string' && REFUSAL_CODES.includes(value);
15
+ }
16
+ /**
17
+ * Receipt history and pending holds.
18
+ *
19
+ * Both read the gateway's projection rather than HCS directly, because this
20
+ * package must stay browser-safe and a browser has no business walking a topic.
21
+ * A consumer that wants to verify rather than display should run
22
+ * `pnpm verify-tab`, which replays the topic itself and trusts nothing we say.
23
+ */
24
+ export async function holds(client, tab) {
25
+ if (!tab)
26
+ throw new TabInvalidError('holds() needs a tab id');
27
+ const rows = await client.request('GET', `/v1/tabs/${tab}/holds`);
28
+ return rows.map((r) => ({
29
+ holdId: String(r['holdId']),
30
+ counterparty: String(r['counterparty']),
31
+ amount: parseAmount(r['amount'], 'hold.amount'),
32
+ at: String(r['at']),
33
+ expiresAt: String(r['expiresAt']),
34
+ status: r['status'] ?? 'pending',
35
+ }));
36
+ }
37
+ export async function receipts(client, tab) {
38
+ if (!tab)
39
+ throw new TabInvalidError('receipts() needs a tab id');
40
+ const rows = await client.request('GET', `/v1/tabs/${tab}/entries`);
41
+ return rows.map((r) => {
42
+ const leg = String(r['kind']);
43
+ return {
44
+ leg,
45
+ at: String(r['at']),
46
+ window: Number(r['window'] ?? 0),
47
+ ...(r['seq'] !== undefined && r['seq'] !== null ? { seq: Number(r['seq']) } : {}),
48
+ ...(r['requestHash'] ? { requestHash: String(r['requestHash']) } : {}),
49
+ ...(r['token'] ? { token: String(r['token']) } : {}),
50
+ ...(r['counterparty'] ? { counterparty: String(r['counterparty']) } : {}),
51
+ /*
52
+ * A refusal carries `requested`, not `amount` — it moved no money, and
53
+ * conflating the two would make the Refusals view show refused intent as
54
+ * though it were spend. Whichever is present is mapped to `amount` so a
55
+ * table can render one column, and `leg` tells a reader which it is.
56
+ */
57
+ ...(r['amount'] !== undefined && r['amount'] !== null
58
+ ? { amount: parseAmount(r['amount'], `${leg}.amount`) }
59
+ : r['requested'] !== undefined && r['requested'] !== null
60
+ ? { amount: parseAmount(r['requested'], `${leg}.requested`) }
61
+ : {}),
62
+ ...(typeof r['attested'] === 'boolean' ? { attested: r['attested'] } : {}),
63
+ ...(r['transactionId'] ? { transactionId: String(r['transactionId']) } : {}),
64
+ ...(r['holdId'] ? { holdId: String(r['holdId']) } : {}),
65
+ ...(knownRule(r['rule']) ? { rule: r['rule'] } : {}),
66
+ };
67
+ });
68
+ }
69
+ /** Is the gateway up, and which network and window is it on? */
70
+ export async function health(client) {
71
+ const body = await client.request('GET', '/health');
72
+ const topics = body['topics'];
73
+ return {
74
+ ok: body['ok'] === true,
75
+ network: String(body['network'] ?? 'unknown'),
76
+ token: String(body['token'] ?? ''),
77
+ window: Number(body['window'] ?? 0),
78
+ demoMode: body['demoMode'] === true,
79
+ ...(topics?.['receipts'] && topics['ceilings'] && topics['settlements']
80
+ ? {
81
+ topics: {
82
+ receipts: String(topics['receipts']),
83
+ ceilings: String(topics['ceilings']),
84
+ settlements: String(topics['settlements']),
85
+ },
86
+ }
87
+ : {}),
88
+ };
89
+ }
90
+ /**
91
+ * Published independence weights for a tab's counterparties.
92
+ *
93
+ * Reason codes are VALIDATED against `WEIGHT_REASONS`, not cast — an unknown
94
+ * reason from a newer server is dropped rather than typed as a real one, for
95
+ * the same reason an unknown refusal code is: the console would otherwise
96
+ * render a rule that does not exist, and `isBlocking` would answer confidently
97
+ * about it.
98
+ */
99
+ export async function counterparties(client, tab) {
100
+ if (!tab)
101
+ throw new TabInvalidError('counterparties() needs a tab id');
102
+ const rows = await client.request('GET', `/v1/tabs/${tab}/counterparties`);
103
+ return rows.map((r) => {
104
+ const raw = Array.isArray(r['reasons']) ? r['reasons'] : [];
105
+ const reasons = raw.filter(knownReason);
106
+ return {
107
+ counterparty: String(r['counterparty']),
108
+ bp: Number(r['bp'] ?? 0),
109
+ /*
110
+ * Never empty, even if every reason was unrecognised.
111
+ *
112
+ * A weight with no reason is unreadable in the console, so an unknown
113
+ * vocabulary degrades to `INDEPENDENT` ONLY when the weight is full —
114
+ * otherwise it stays empty and the view shows the number without a
115
+ * fabricated justification for it.
116
+ */
117
+ reasons: reasons.length > 0
118
+ ? reasons
119
+ : Number(r['bp'] ?? 0) === 10_000
120
+ ? ['INDEPENDENT']
121
+ : [],
122
+ blocking: r['blocking'] === true,
123
+ revenue: parseAmount(r['revenue'], 'counterparty.revenue'),
124
+ shareBp: Number(r['shareBp'] ?? 0),
125
+ window: Number(r['window'] ?? 0),
126
+ at: String(r['at']),
127
+ ...(r['token'] ? { token: String(r['token']) } : {}),
128
+ // Absent stays absent — see `CounterpartyWeight` for why defaulting any
129
+ // of these would fabricate the evidence the panel exists to show.
130
+ ...(r['firstSeen'] ? { firstSeen: String(r['firstSeen']) } : {}),
131
+ ...(r['funder'] ? { funder: String(r['funder']) } : {}),
132
+ ...(r['funderSeq'] !== undefined && r['funderSeq'] !== null
133
+ ? { funderSeq: Number(r['funderSeq']) }
134
+ : {}),
135
+ ...(r['tabFunder'] ? { tabFunder: String(r['tabFunder']) } : {}),
136
+ };
137
+ });
138
+ }
139
+ function knownReason(value) {
140
+ return typeof value === 'string' && WEIGHT_REASONS.includes(value);
141
+ }
142
+ const TIERS = ['A', 'B', 'C', 'Unrated'];
143
+ const OUTCOMES = ['clean', 'missed', 'carried'];
144
+ /** Validated, not cast — an unknown tier would render as a real one. */
145
+ function tierOf(value) {
146
+ return typeof value === 'string' && TIERS.includes(value)
147
+ ? value
148
+ : 'Unrated';
149
+ }
150
+ function outcomeOf(value) {
151
+ return typeof value === 'string' && OUTCOMES.includes(value)
152
+ ? value
153
+ : 'carried';
154
+ }
155
+ function ceilingOf(r) {
156
+ const inputs = (r['inputs'] ?? {});
157
+ return {
158
+ ceiling: parseAmount(r['ceiling'], 'ceiling.ceiling'),
159
+ ...(r['computed'] !== undefined && r['computed'] !== null
160
+ ? { computed: parseAmount(r['computed'], 'ceiling.computed') }
161
+ : {}),
162
+ window: Number(r['window'] ?? 0),
163
+ binding: String(r['binding'] ?? 'unknown'),
164
+ cause: String(r['cause'] ?? 'unknown'),
165
+ at: String(r['at'] ?? ''),
166
+ model: String(r['model'] ?? ''),
167
+ hash: String(r['hash'] ?? ''),
168
+ ...(r['seq'] !== undefined && r['seq'] !== null ? { seq: Number(r['seq']) } : {}),
169
+ inputs: {
170
+ revenue: parseAmount(inputs['revenue'], 'ceiling.inputs.revenue'),
171
+ revenueAttested: parseAmount(inputs['revenueAttested'], 'ceiling.inputs.revenueAttested'),
172
+ revenueUnattested: parseAmount(inputs['revenueUnattested'], 'ceiling.inputs.revenueUnattested'),
173
+ tier: tierOf(inputs['tier']),
174
+ multBp: Number(inputs['multBp'] ?? 0),
175
+ rampBp: Number(inputs['rampBp'] ?? 0),
176
+ cap: parseAmount(inputs['cap'], 'ceiling.inputs.cap'),
177
+ floor: parseAmount(inputs['floor'], 'ceiling.inputs.floor'),
178
+ defaulted: inputs['defaulted'] === true,
179
+ },
180
+ };
181
+ }
182
+ /**
183
+ * The ceiling in force, its arithmetic, and the series behind it.
184
+ *
185
+ * Read from what the ENGINE published, never recomputed — the SDK is
186
+ * browser-safe and cannot import `@0xdivyanshh/tab-scoring`, and that is the right
187
+ * constraint. A client that recomputed the ceiling would hand a viewer a second
188
+ * answer to compare against the topic, and two answers is worse than one even
189
+ * when they agree.
190
+ *
191
+ * `published: false` is not an error. It means the engine has not run for this
192
+ * tab yet and the starter ceiling is in force — `enforced` says what that is.
193
+ */
194
+ export async function ceiling(client, tab) {
195
+ if (!tab)
196
+ throw new TabInvalidError('ceiling() needs a tab id');
197
+ const body = await client.request('GET', `/v1/tabs/${tab}/ceiling`);
198
+ const history = (Array.isArray(body['history']) ? body['history'] : []).map((row) => ceilingOf(row));
199
+ const published = body['published'] === true;
200
+ return {
201
+ tab: String(body['tab'] ?? tab),
202
+ published,
203
+ enforced: parseAmount(body['enforced'], 'ceiling.enforced'),
204
+ // `current` is the last history element rather than a separate parse of the
205
+ // top-level fields, so "the number in force" and "the last point on the
206
+ // chart" cannot disagree on screen.
207
+ ...(published && history.length > 0 ? { current: history[history.length - 1] } : {}),
208
+ history,
209
+ ...(body['note'] ? { note: String(body['note']) } : {}),
210
+ };
211
+ }
212
+ /**
213
+ * Settled windows, ascending by window.
214
+ *
215
+ * Ordered by WINDOW rather than by arrival: a repaired or late settlement lands
216
+ * on the topic after the windows that follow it, and a table sorted by
217
+ * timestamp would put window 41 above window 39 with no explanation.
218
+ *
219
+ * The gross legs come through as absent when the topic did not carry them. A
220
+ * caller must render that as "not published", never as zero.
221
+ */
222
+ export async function settlements(client, tab) {
223
+ if (!tab)
224
+ throw new TabInvalidError('settlements() needs a tab id');
225
+ const rows = await client.request('GET', `/v1/tabs/${tab}/settlements`);
226
+ return rows.map((r) => ({
227
+ window: Number(r['window'] ?? 0),
228
+ at: String(r['at'] ?? ''),
229
+ ...(r['seq'] !== undefined && r['seq'] !== null ? { seq: Number(r['seq']) } : {}),
230
+ net: parseAmount(r['net'], 'settlement.net'),
231
+ ...(r['credits'] !== undefined && r['credits'] !== null
232
+ ? { credits: parseAmount(r['credits'], 'settlement.credits') }
233
+ : {}),
234
+ ...(r['debits'] !== undefined && r['debits'] !== null
235
+ ? { debits: parseAmount(r['debits'], 'settlement.debits') }
236
+ : {}),
237
+ ...(r['interest'] !== undefined && r['interest'] !== null
238
+ ? { interest: parseAmount(r['interest'], 'settlement.interest') }
239
+ : {}),
240
+ ...(r['outstanding'] !== undefined && r['outstanding'] !== null
241
+ ? { outstanding: parseAmount(r['outstanding'], 'settlement.outstanding') }
242
+ : {}),
243
+ ...(r['receiptCount'] !== undefined && r['receiptCount'] !== null
244
+ ? { receiptCount: Number(r['receiptCount']) }
245
+ : {}),
246
+ outcome: outcomeOf(r['outcome']),
247
+ rampFromBp: Number(r['rampFromBp'] ?? 0),
248
+ rampToBp: Number(r['rampToBp'] ?? 0),
249
+ ...(r['transactionId'] ? { transactionId: String(r['transactionId']) } : {}),
250
+ ...(r['token'] ? { token: String(r['token']) } : {}),
251
+ }));
252
+ }
253
+ /**
254
+ * The tabs this gateway knows about.
255
+ *
256
+ * Named `tabs`, not `registry`, and the response carries
257
+ * `registrationEnforced: false` so a caller cannot mistake it for one. Nothing
258
+ * writes a `register` message yet — one Starter Tab per funding root is
259
+ * unenforced and the README labels it OPEN — so a tab appears here the first
260
+ * time it spends or earns, and there is no registration time to report.
261
+ */
262
+ export async function tabs(client) {
263
+ const body = await client.request('GET', '/v1/tabs');
264
+ const rows = Array.isArray(body['tabs']) ? body['tabs'] : [];
265
+ return {
266
+ window: Number(body['window'] ?? 0),
267
+ /*
268
+ * Defaults to FALSE when the field is absent, never true.
269
+ *
270
+ * An older gateway that does not send it has no registration flow either,
271
+ * so absence means unenforced. Defaulting the permissive way round would
272
+ * let a stale server silently license the "Registry" heading.
273
+ */
274
+ registrationEnforced: body['registrationEnforced'] === true,
275
+ rootsClaimed: Number(body['rootsClaimed'] ?? 0),
276
+ note: String(body['note'] ?? ''),
277
+ tabs: rows.map((raw) => {
278
+ const r = raw;
279
+ const summary = {
280
+ tab: String(r['tab']),
281
+ balance: parseAmount(r['balance'], 'tab.balance'),
282
+ outstanding: parseAmount(r['outstanding'], 'tab.outstanding'),
283
+ holds: parseAmount(r['holds'], 'tab.holds'),
284
+ available: parseAmount(r['available'], 'tab.available'),
285
+ ceiling: parseAmount(r['ceiling'], 'tab.ceiling'),
286
+ entries: Number(r['entries'] ?? 0),
287
+ // Absent stays absent. An unpublished tab has no tier, which is not the
288
+ // same as being Unrated.
289
+ ...(r['tier'] !== undefined && r['tier'] !== null ? { tier: tierOf(r['tier']) } : {}),
290
+ ...(r['publishedCeiling'] !== undefined && r['publishedCeiling'] !== null
291
+ ? { publishedCeiling: parseAmount(r['publishedCeiling'], 'tab.publishedCeiling') }
292
+ : {}),
293
+ ...(r['binding'] ? { binding: String(r['binding']) } : {}),
294
+ ...(r['cause'] ? { cause: String(r['cause']) } : {}),
295
+ ...(r['model'] ? { model: String(r['model']) } : {}),
296
+ ...(r['seq'] !== undefined && r['seq'] !== null ? { seq: Number(r['seq']) } : {}),
297
+ // Absent stays absent: no claim recorded is not the same as denied.
298
+ ...(r['registeredRoot'] ? { registeredRoot: String(r['registeredRoot']) } : {}),
299
+ ...(r['registeredAt'] ? { registeredAt: String(r['registeredAt']) } : {}),
300
+ ...(r['registrationSeq'] !== undefined && r['registrationSeq'] !== null
301
+ ? { registrationSeq: Number(r['registrationSeq']) }
302
+ : {}),
303
+ ...(r['uaid'] ? { uaid: String(r['uaid']) } : {}),
304
+ };
305
+ return summary;
306
+ }),
307
+ };
308
+ }
309
+ //# sourceMappingURL=receipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipts.js","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type MicroUsdc } from '@0xdivyanshh/tab-money';
2
+ import type { TabClient } from './client.ts';
3
+ import type { Quote, SpendRequest, SpendResult, TabState } from './types.ts';
4
+ /** Parse a wire amount, with a message that names the field. */
5
+ declare function amount(value: unknown, field: string): MicroUsdc;
6
+ export declare function spend(client: TabClient, request: SpendRequest): Promise<SpendResult>;
7
+ /**
8
+ * What could this tab afford right now, without reserving anything.
9
+ *
10
+ * Derived from `state()` rather than a dedicated endpoint, and deliberately so:
11
+ * a quote that took a hold would leave a hold that never commits, which is
12
+ * exactly the shape the reconciler exists to clean up. This reserves nothing
13
+ * and therefore guarantees nothing — between a quote and a spend, another call
14
+ * on the same tab can take the headroom.
15
+ */
16
+ export declare function quote(client: TabClient, params: {
17
+ tab: string;
18
+ max: MicroUsdc;
19
+ }): Promise<Quote>;
20
+ export declare function state(client: TabClient, tab: string): Promise<TabState>;
21
+ export { amount as parseAmount };
22
+ //# sourceMappingURL=spend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend.d.ts","sourceRoot":"","sources":["../src/spend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,YAAY,CAAA;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AA4B5E,gEAAgE;AAChE,iBAAS,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CA2CxD;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAiF1F;AAED;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CACzB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,CAAA;CAAE,GACtC,OAAO,CAAC,KAAK,CAAC,CAgChB;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAc7E;AAED,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,CAAA"}
package/dist/spend.js ADDED
@@ -0,0 +1,185 @@
1
+ import { toWire, usdc } from '@0xdivyanshh/tab-money';
2
+ import { isRetryable, REFUSAL_GUIDANCE } from '@0xdivyanshh/tab-protocol';
3
+ import { TabInvalidError, TabProtocolError } from "./errors.js";
4
+ /**
5
+ * Spend, quote, and read state.
6
+ *
7
+ * The one design decision worth restating: **a refusal is a value.** `spend()`
8
+ * returns a discriminated result carrying the rule that fired, because refusal
9
+ * is the product demonstrating that underwriting works — not an error
10
+ * condition. Throwing would push every consumer into `try/catch` and make the
11
+ * Refusals view harder to build than the happy path.
12
+ */
13
+ /**
14
+ * A key that makes a retry safe.
15
+ *
16
+ * The gateway treats it as the hold id, so the same key cannot spend twice. It
17
+ * is generated HERE rather than in the gateway because only the caller can
18
+ * decide that two calls are the same intent — and it is returned on every
19
+ * outcome, including failures, so a caller retrying an unknown outcome can
20
+ * reuse it.
21
+ *
22
+ * `crypto.randomUUID` is available in Node 19+ and every modern browser, which
23
+ * keeps this package browser-safe with no dependency.
24
+ */
25
+ function newIdempotencyKey() {
26
+ return `h_${globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`;
27
+ }
28
+ /** Parse a wire amount, with a message that names the field. */
29
+ function amount(value, field) {
30
+ if (typeof value !== 'string') {
31
+ throw new TabProtocolError(`Expected "${field}" to be a decimal string amount, got ${typeof value}`, 200, value);
32
+ }
33
+ /*
34
+ * STRICT — exactly six decimals and an ASCII minus, which is what `toWire`
35
+ * emits and nothing else.
36
+ *
37
+ * `usdc()` alone is not enough here, and a test caught that: it is tolerant
38
+ * by design, accepting a U+2212 minus and padding a 4-decimal value. That
39
+ * tolerance is right for human input and wrong for a wire parser, because it
40
+ * makes silent data loss undetectable — the gateway once served `format()`
41
+ * output where a wire value belonged, `1.234567` went out as `"1.2345"`, and
42
+ * `usdc()` would have happily parsed it as `1234500`. Sixty-seven
43
+ * micro-USDC per value, no error, and a dashboard that cannot match
44
+ * HashScan.
45
+ *
46
+ * So the shape is checked before parsing, and a display string is rejected
47
+ * with a message that names the actual cause.
48
+ */
49
+ if (!/^-?\d+\.\d{6}$/.test(value)) {
50
+ throw new TabProtocolError(`"${field}" = ${JSON.stringify(value)} is not a wire amount. ` +
51
+ 'Exactly six decimals and an ASCII minus are required (`toWire`). Four decimals or a ' +
52
+ 'U+2212 minus means the server serialised a DISPLAY string instead of a wire value, ' +
53
+ 'which is lossy — do not "fix" this by loosening the check.', 200, value);
54
+ }
55
+ try {
56
+ return usdc(value);
57
+ }
58
+ catch (error) {
59
+ throw new TabProtocolError(`"${field}" = ${JSON.stringify(value)} has the right shape but did not parse`, 200, error);
60
+ }
61
+ }
62
+ export async function spend(client, request) {
63
+ if (!request.tab)
64
+ throw new TabInvalidError('spend() needs a tab id');
65
+ if (!request.url)
66
+ throw new TabInvalidError('spend() needs a seller url');
67
+ if (request.max !== undefined && request.max <= 0n) {
68
+ // A zero or negative max is a programming error, not a refusal: there is no
69
+ // spend to underwrite, so the rail has nothing to decide.
70
+ throw new TabInvalidError(`spend() needs a positive max, got ${request.max}`);
71
+ }
72
+ const idempotencyKey = request.idempotencyKey ?? newIdempotencyKey();
73
+ const body = await client.request('POST', '/v1/spend', {
74
+ tab: request.tab,
75
+ url: request.url,
76
+ // Absent means "accept the seller's quote"; the gateway reads it from the
77
+ // 402. Sending an explicit null would instead be a value the gateway must
78
+ // interpret, so the key is simply left off.
79
+ ...(request.max !== undefined ? { max: toWire(request.max) } : {}),
80
+ idempotencyKey,
81
+ ...(request.init ? { init: request.init } : {}),
82
+ });
83
+ if (body.refused) {
84
+ const rule = body.refused.rule;
85
+ return {
86
+ outcome: 'refused',
87
+ rule,
88
+ reason: body.refused.reason,
89
+ /*
90
+ * Guidance comes from `@0xdivyanshh/tab-protocol`, not from the server's message.
91
+ *
92
+ * The rule code is the contract; the prose is not. Taking guidance from
93
+ * the wire would let a server version change what an agent does next
94
+ * without any consumer noticing.
95
+ */
96
+ guidance: REFUSAL_GUIDANCE[rule] ?? 'Surface this to the operator.',
97
+ retryable: isRetryable(rule),
98
+ ...(body.refused.evidence ? { evidence: body.refused.evidence } : {}),
99
+ };
100
+ }
101
+ if (body.paid) {
102
+ return {
103
+ outcome: 'paid',
104
+ amount: amount(body.paid.amount, 'paid.amount'),
105
+ seller: body.paid.seller,
106
+ holdId: body.paid.holdId,
107
+ receiptSeq: body.paid.receiptSeq,
108
+ body: body.body ?? null,
109
+ elapsedMs: body.paid.elapsedMs,
110
+ };
111
+ }
112
+ if (body.failed) {
113
+ return { outcome: 'failed', reason: body.failed.reason, holdId: body.failed.holdId };
114
+ }
115
+ /*
116
+ * Infrastructure failure reported through the top-level `error` field.
117
+ *
118
+ * Returned as `failed` rather than thrown, carrying the idempotency key —
119
+ * the caller needs that key to retry safely, and an exception would discard
120
+ * the one piece of information that makes the retry safe.
121
+ */
122
+ if (body.error) {
123
+ return { outcome: 'failed', reason: body.error, holdId: idempotencyKey };
124
+ }
125
+ throw new TabProtocolError('The gateway returned neither paid, refused, nor failed', 200, body);
126
+ }
127
+ /**
128
+ * What could this tab afford right now, without reserving anything.
129
+ *
130
+ * Derived from `state()` rather than a dedicated endpoint, and deliberately so:
131
+ * a quote that took a hold would leave a hold that never commits, which is
132
+ * exactly the shape the reconciler exists to clean up. This reserves nothing
133
+ * and therefore guarantees nothing — between a quote and a spend, another call
134
+ * on the same tab can take the headroom.
135
+ */
136
+ export async function quote(client, params) {
137
+ const s = await state(client, params.tab);
138
+ if (params.max > s.perCallCap) {
139
+ return {
140
+ tab: s.tab,
141
+ affordable: false,
142
+ available: s.available,
143
+ ceiling: s.ceiling,
144
+ perCallCap: s.perCallCap,
145
+ wouldRefuse: 'PER_CALL_CAP',
146
+ reason: `${toWire(params.max)} exceeds the per-call cap of ${toWire(s.perCallCap)}`,
147
+ };
148
+ }
149
+ if (params.max > s.available) {
150
+ return {
151
+ tab: s.tab,
152
+ affordable: false,
153
+ available: s.available,
154
+ ceiling: s.ceiling,
155
+ perCallCap: s.perCallCap,
156
+ wouldRefuse: 'CEILING_EXCEEDED',
157
+ reason: `${toWire(params.max)} exceeds the available ${toWire(s.available)} under a ceiling of ${toWire(s.ceiling)}`,
158
+ };
159
+ }
160
+ return {
161
+ tab: s.tab,
162
+ affordable: true,
163
+ available: s.available,
164
+ ceiling: s.ceiling,
165
+ perCallCap: s.perCallCap,
166
+ };
167
+ }
168
+ export async function state(client, tab) {
169
+ if (!tab)
170
+ throw new TabInvalidError('state() needs a tab id');
171
+ const body = await client.request('GET', `/v1/tabs/${tab}`);
172
+ return {
173
+ tab: String(body['tab'] ?? tab),
174
+ balance: amount(body['balance'], 'balance'),
175
+ outstanding: amount(body['outstanding'], 'outstanding'),
176
+ holds: amount(body['holds'], 'holds'),
177
+ available: amount(body['available'], 'available'),
178
+ ceiling: amount(body['ceiling'], 'ceiling'),
179
+ perCallCap: amount(body['perCallCap'], 'perCallCap'),
180
+ window: Number(body['window'] ?? 0),
181
+ entries: Number(body['entries'] ?? 0),
182
+ };
183
+ }
184
+ export { amount as parseAmount };
185
+ //# sourceMappingURL=spend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend.js","sourceRoot":"","sources":["../src/spend.ts"],"names":[],"mappings":"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"}