@xochi/client 0.0.0-stage → 0.1.1

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 (125) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -2
  4. package/dist/cjs/claim.d.ts +33 -0
  5. package/dist/cjs/claim.js +44 -0
  6. package/dist/cjs/fee.d.ts +247 -0
  7. package/dist/cjs/fee.js +295 -0
  8. package/dist/cjs/index.d.ts +18 -0
  9. package/dist/cjs/index.js +33 -0
  10. package/dist/cjs/machine/claimInfo.d.ts +29 -0
  11. package/dist/cjs/machine/claimInfo.js +26 -0
  12. package/dist/cjs/machine/decisions.d.ts +452 -0
  13. package/dist/cjs/machine/decisions.js +665 -0
  14. package/dist/cjs/machine/deposit.d.ts +113 -0
  15. package/dist/cjs/machine/deposit.js +265 -0
  16. package/dist/cjs/machine/index.d.ts +23 -0
  17. package/dist/cjs/machine/index.js +53 -0
  18. package/dist/cjs/machine/machine.d.ts +409 -0
  19. package/dist/cjs/machine/machine.js +1615 -0
  20. package/dist/cjs/machine/phase.d.ts +24 -0
  21. package/dist/cjs/machine/phase.js +45 -0
  22. package/dist/cjs/machine/quoteChecks.d.ts +41 -0
  23. package/dist/cjs/machine/quoteChecks.js +82 -0
  24. package/dist/cjs/machine/receipt.d.ts +151 -0
  25. package/dist/cjs/machine/receipt.js +69 -0
  26. package/dist/cjs/machine/recipientEcho.d.ts +16 -0
  27. package/dist/cjs/machine/recipientEcho.js +27 -0
  28. package/dist/cjs/machine/solverPin.d.ts +110 -0
  29. package/dist/cjs/machine/solverPin.js +214 -0
  30. package/dist/cjs/machine/state.d.ts +167 -0
  31. package/dist/cjs/machine/state.js +31 -0
  32. package/dist/cjs/machine/transition.d.ts +15 -0
  33. package/dist/cjs/machine/transition.js +20 -0
  34. package/dist/cjs/machine/validateRequest.d.ts +15 -0
  35. package/dist/cjs/machine/validateRequest.js +127 -0
  36. package/dist/cjs/machine/view.d.ts +3 -0
  37. package/dist/cjs/machine/view.js +74 -0
  38. package/dist/cjs/machine/wire.d.ts +527 -0
  39. package/dist/cjs/machine/wire.js +280 -0
  40. package/dist/cjs/package.json +1 -0
  41. package/dist/cjs/shared/api.d.ts +9 -0
  42. package/dist/cjs/shared/api.js +5 -0
  43. package/dist/cjs/shared/chains.d.ts +243 -0
  44. package/dist/cjs/shared/chains.js +250 -0
  45. package/dist/cjs/shared/eip712.d.ts +44 -0
  46. package/dist/cjs/shared/eip712.js +64 -0
  47. package/dist/cjs/shared/index.d.ts +15 -0
  48. package/dist/cjs/shared/index.js +31 -0
  49. package/dist/cjs/shared/intentStream.d.ts +74 -0
  50. package/dist/cjs/shared/intentStream.js +59 -0
  51. package/dist/cjs/shared/intents.d.ts +88 -0
  52. package/dist/cjs/shared/intents.js +227 -0
  53. package/dist/cjs/shared/schemas.d.ts +928 -0
  54. package/dist/cjs/shared/schemas.js +524 -0
  55. package/dist/cjs/shared/stealth.d.ts +53 -0
  56. package/dist/cjs/shared/stealth.js +68 -0
  57. package/dist/cjs/shared/trading.d.ts +153 -0
  58. package/dist/cjs/shared/trading.js +185 -0
  59. package/dist/cjs/shared/types.d.ts +37 -0
  60. package/dist/cjs/shared/types.js +7 -0
  61. package/dist/cjs/shared/validators.d.ts +30 -0
  62. package/dist/cjs/shared/validators.js +79 -0
  63. package/dist/cjs/transport/client.d.ts +234 -0
  64. package/dist/cjs/transport/client.js +1456 -0
  65. package/dist/esm/claim.d.ts +33 -0
  66. package/dist/esm/claim.js +41 -0
  67. package/dist/esm/fee.d.ts +247 -0
  68. package/dist/esm/fee.js +285 -0
  69. package/dist/esm/index.d.ts +18 -0
  70. package/dist/esm/index.js +17 -0
  71. package/dist/esm/machine/claimInfo.d.ts +29 -0
  72. package/dist/esm/machine/claimInfo.js +23 -0
  73. package/dist/esm/machine/decisions.d.ts +452 -0
  74. package/dist/esm/machine/decisions.js +640 -0
  75. package/dist/esm/machine/deposit.d.ts +113 -0
  76. package/dist/esm/machine/deposit.js +260 -0
  77. package/dist/esm/machine/index.d.ts +23 -0
  78. package/dist/esm/machine/index.js +23 -0
  79. package/dist/esm/machine/machine.d.ts +409 -0
  80. package/dist/esm/machine/machine.js +1609 -0
  81. package/dist/esm/machine/phase.d.ts +24 -0
  82. package/dist/esm/machine/phase.js +42 -0
  83. package/dist/esm/machine/quoteChecks.d.ts +41 -0
  84. package/dist/esm/machine/quoteChecks.js +78 -0
  85. package/dist/esm/machine/receipt.d.ts +151 -0
  86. package/dist/esm/machine/receipt.js +64 -0
  87. package/dist/esm/machine/recipientEcho.d.ts +16 -0
  88. package/dist/esm/machine/recipientEcho.js +24 -0
  89. package/dist/esm/machine/solverPin.d.ts +110 -0
  90. package/dist/esm/machine/solverPin.js +210 -0
  91. package/dist/esm/machine/state.d.ts +167 -0
  92. package/dist/esm/machine/state.js +28 -0
  93. package/dist/esm/machine/transition.d.ts +15 -0
  94. package/dist/esm/machine/transition.js +17 -0
  95. package/dist/esm/machine/validateRequest.d.ts +15 -0
  96. package/dist/esm/machine/validateRequest.js +123 -0
  97. package/dist/esm/machine/view.d.ts +3 -0
  98. package/dist/esm/machine/view.js +71 -0
  99. package/dist/esm/machine/wire.d.ts +527 -0
  100. package/dist/esm/machine/wire.js +266 -0
  101. package/dist/esm/shared/api.d.ts +9 -0
  102. package/dist/esm/shared/api.js +4 -0
  103. package/dist/esm/shared/chains.d.ts +243 -0
  104. package/dist/esm/shared/chains.js +236 -0
  105. package/dist/esm/shared/eip712.d.ts +44 -0
  106. package/dist/esm/shared/eip712.js +61 -0
  107. package/dist/esm/shared/index.d.ts +15 -0
  108. package/dist/esm/shared/index.js +15 -0
  109. package/dist/esm/shared/intentStream.d.ts +74 -0
  110. package/dist/esm/shared/intentStream.js +55 -0
  111. package/dist/esm/shared/intents.d.ts +88 -0
  112. package/dist/esm/shared/intents.js +213 -0
  113. package/dist/esm/shared/schemas.d.ts +928 -0
  114. package/dist/esm/shared/schemas.js +521 -0
  115. package/dist/esm/shared/stealth.d.ts +53 -0
  116. package/dist/esm/shared/stealth.js +64 -0
  117. package/dist/esm/shared/trading.d.ts +153 -0
  118. package/dist/esm/shared/trading.js +182 -0
  119. package/dist/esm/shared/types.d.ts +37 -0
  120. package/dist/esm/shared/types.js +6 -0
  121. package/dist/esm/shared/validators.d.ts +30 -0
  122. package/dist/esm/shared/validators.js +74 -0
  123. package/dist/esm/transport/client.d.ts +234 -0
  124. package/dist/esm/transport/client.js +1447 -0
  125. package/package.json +61 -4
@@ -0,0 +1,295 @@
1
+ "use strict";
2
+ // Layered fee model -- the network-free reference for the Xochi `fee_breakdown`
3
+ // (SPEC.md Section 4). Byte/amount-parity with the server's authoritative
4
+ // `Riddler.Integrations.Xochi.FeePolicy.calculate_layered_fee/1`; the fee
5
+ // vectors in `@xochi/spec/fee/vectors.json` are generated from this module
6
+ // (`packages/spec/fee/generate.ts`) and re-checked by
7
+ // `packages/reference/test/fee.test.ts`, making Section 4 conformance-tested
8
+ // the same way the signing layer is.
9
+ //
10
+ // Two roles:
11
+ // - `computeFeeBreakdown(params)` -- predict the deterministic core of a
12
+ // breakdown (bps + token-unit amounts) from the published schedule, so a
13
+ // taker can cross-check a server quote instead of trusting it blindly.
14
+ // - `assertFeeBreakdownInvariants(fb)` -- structurally verify a server-returned
15
+ // breakdown reconciles (layers sum to the total, bps sum to `total_bps`, no
16
+ // discount crosses the solver floor). This moves real funds; a misreported
17
+ // split MUST fail loud, not settle.
18
+ //
19
+ // The `gas_floor` / `*_usd` fields are price-oracle-derived and added by the
20
+ // server's quote adapter -- they are outside this deterministic core and are
21
+ // validated only for shape (see `LayeredFeeBreakdownSchema`), never recomputed
22
+ // here.
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.LayeredFeeBreakdownSchema = exports.FEE_SCHEDULE = void 0;
25
+ exports.scoreToTier = scoreToTier;
26
+ exports.gateTier = gateTier;
27
+ exports.priceImpactBps = priceImpactBps;
28
+ exports.computeFeeBreakdown = computeFeeBreakdown;
29
+ exports.parseFeeBreakdown = parseFeeBreakdown;
30
+ exports.feeBreakdownReconcilable = feeBreakdownReconcilable;
31
+ exports.assertFeeBreakdownInvariants = assertFeeBreakdownInvariants;
32
+ const zod_1 = require("zod");
33
+ // Canonical fee schedule. Single source of truth: mirrors the module attributes
34
+ // of `Riddler.Integrations.Xochi.FeePolicy`. `@xochi/spec/fee/schedule.json`
35
+ // is a generated projection of this object (see `packages/spec/fee/generate.ts`).
36
+ // Headline total per tier = solver + venue + routing (no impact, non-ACP):
37
+ // stable 22/19/15/12/10, volatile 40/35/29/25/22.
38
+ exports.FEE_SCHEDULE = {
39
+ // Percentage of positive fill slippage (surplus) captured; the rest returns
40
+ // to the taker. Reported, not part of the fee-bps sum.
41
+ surplus_share_pct: 15,
42
+ // Trust score -> tier, highest boundary first (a score matches the first
43
+ // boundary it meets or exceeds).
44
+ tier_boundaries: [
45
+ { min_trust_score: 100, tier: "institutional" },
46
+ { min_trust_score: 75, tier: "premium" },
47
+ { min_trust_score: 50, tier: "verified" },
48
+ { min_trust_score: 25, tier: "trusted" },
49
+ { min_trust_score: 0, tier: "standard" },
50
+ ],
51
+ // Solver spread -- the never-discounted cost floor, identical at every tier.
52
+ solver_base_bps: { stable: 8, volatile: 18 },
53
+ // Venue (Xochi) cut -- tier-discounted.
54
+ venue_bps: {
55
+ standard: { stable: 6, volatile: 10 },
56
+ trusted: { stable: 5, volatile: 8 },
57
+ verified: { stable: 3, volatile: 5 },
58
+ premium: { stable: 2, volatile: 3 },
59
+ institutional: { stable: 1, volatile: 2 },
60
+ },
61
+ // Routing (Raxol) cut -- tier-discounted; waived entirely when the intent
62
+ // originates from ACP (the routing cut is already paid via the ACP job budget).
63
+ routing_bps: {
64
+ standard: { stable: 8, volatile: 12 },
65
+ trusted: { stable: 6, volatile: 9 },
66
+ verified: { stable: 4, volatile: 6 },
67
+ premium: { stable: 2, volatile: 4 },
68
+ institutional: { stable: 1, volatile: 2 },
69
+ },
70
+ // Price impact is a solver cost, not a fee: impact_bps = clamp(round(k *
71
+ // fill/liq), 0, cap). Zero on unknown/zero depth.
72
+ price_impact: { k: 50, cap_bps: 150, max_fill_fraction: 0.25 },
73
+ // Attestation proofs a tier requires; without them the tier is gated down to
74
+ // the highest qualifying one.
75
+ tier_attestation_requirements: {
76
+ standard: [],
77
+ trusted: [],
78
+ verified: ["compliance"],
79
+ premium: ["compliance", "non_membership"],
80
+ institutional: ["compliance", "non_membership"],
81
+ },
82
+ };
83
+ const BPS_DENOMINATOR = 10000n;
84
+ // Highest-privilege-first, for gating a tier down to the best one whose proof
85
+ // requirements are satisfied. Mirrors FeePolicy's @tier_gate_order.
86
+ const TIER_GATE_ORDER = ["institutional", "premium", "verified", "trusted", "standard"];
87
+ // Maps a trust score to its fee tier. Non-integer or negative scores fall back
88
+ // to `standard`, matching the server.
89
+ function scoreToTier(score) {
90
+ if (!Number.isInteger(score) || score < 0)
91
+ return "standard";
92
+ for (const { min_trust_score, tier } of exports.FEE_SCHEDULE.tier_boundaries) {
93
+ if (score >= min_trust_score)
94
+ return tier;
95
+ }
96
+ return "standard";
97
+ }
98
+ function isSubset(required, provided) {
99
+ return required.every((r) => provided.has(r));
100
+ }
101
+ // Gates a raw tier down to the highest tier whose attestation requirements are
102
+ // met by `provided`. A tier with no requirements is returned unchanged.
103
+ function gateTier(rawTier, provided) {
104
+ const providedSet = new Set(provided);
105
+ const required = exports.FEE_SCHEDULE.tier_attestation_requirements[rawTier];
106
+ if (isSubset(required, providedSet))
107
+ return rawTier;
108
+ for (const tier of TIER_GATE_ORDER) {
109
+ if (isSubset(exports.FEE_SCHEDULE.tier_attestation_requirements[tier], providedSet))
110
+ return tier;
111
+ }
112
+ return "standard";
113
+ }
114
+ // Price-impact in bps for `fillUsd` filled against `liquidityUsd` of solver
115
+ // inventory. Returns 0 when depth is unknown or non-positive (depth-blind => no
116
+ // impact term). impact_bps = clamp(round(k * fill/liq), 0, cap).
117
+ function priceImpactBps(fillUsd, liquidityUsd) {
118
+ if (typeof fillUsd !== "number" ||
119
+ typeof liquidityUsd !== "number" ||
120
+ !Number.isFinite(fillUsd) ||
121
+ !Number.isFinite(liquidityUsd) ||
122
+ liquidityUsd <= 0) {
123
+ return 0;
124
+ }
125
+ const { k, cap_bps } = exports.FEE_SCHEDULE.price_impact;
126
+ return Math.round(Math.min(cap_bps, Math.max(0, (k * fillUsd) / liquidityUsd)));
127
+ }
128
+ // Per-layer fee amount in token base units: floor(base * bps / 10000). Each
129
+ // layer floors independently, so the sum of layers can be a hair below
130
+ // floor(base * total_bps / 10000) -- that per-layer floor is the normative rule.
131
+ function layerAmount(base, bps) {
132
+ if (base <= 0n || bps <= 0)
133
+ return 0n;
134
+ return (base * BigInt(bps)) / BPS_DENOMINATOR;
135
+ }
136
+ function parseBaseAmount(input) {
137
+ if (typeof input === "bigint") {
138
+ if (input < 0n)
139
+ throw new Error(`fee: input_amount must be non-negative, got ${input}`);
140
+ return input;
141
+ }
142
+ if (!/^\d+$/.test(input)) {
143
+ throw new Error(`fee: input_amount must be a non-negative integer string, got ${JSON.stringify(input)}`);
144
+ }
145
+ return BigInt(input);
146
+ }
147
+ // Reference layered-fee computation, byte-parity with the server. Produces the
148
+ // deterministic core a taker can compare against a returned quote.
149
+ function computeFeeBreakdown(params) {
150
+ const assetClass = params.asset_class ?? "stable";
151
+ const tier = params.tier ?? gateTier(scoreToTier(params.trust_score ?? 0), params.attestations ?? []);
152
+ const solverBps = exports.FEE_SCHEDULE.solver_base_bps[assetClass];
153
+ const venueBps = exports.FEE_SCHEDULE.venue_bps[tier][assetClass];
154
+ const routingBps = params.acp_origin ? 0 : exports.FEE_SCHEDULE.routing_bps[tier][assetClass];
155
+ const impactBps = priceImpactBps(params.fill_usd ?? 0, params.available_liquidity_usd ?? null);
156
+ // Non-negative layers => the sum already dominates the solver floor; the clamp
157
+ // is a defensive invariant, not arithmetic that fires in practice.
158
+ const totalBps = Math.max(solverBps + venueBps + routingBps + impactBps, solverBps);
159
+ const base = parseBaseAmount(params.input_amount);
160
+ const solverFee = layerAmount(base, solverBps);
161
+ const impactFee = layerAmount(base, impactBps);
162
+ const venueFee = layerAmount(base, venueBps);
163
+ const routingFee = layerAmount(base, routingBps);
164
+ const totalFee = solverFee + impactFee + venueFee + routingFee;
165
+ return {
166
+ tier,
167
+ asset_class: assetClass,
168
+ solver_fee_bps: solverBps,
169
+ solver_fee: solverFee.toString(),
170
+ price_impact_bps: impactBps,
171
+ price_impact: impactFee.toString(),
172
+ venue_fee_bps: venueBps,
173
+ venue_fee: venueFee.toString(),
174
+ routing_fee_bps: routingBps,
175
+ routing_fee: routingFee.toString(),
176
+ total_bps: totalBps,
177
+ total_fee: totalFee.toString(),
178
+ surplus_share_pct: exports.FEE_SCHEDULE.surplus_share_pct,
179
+ };
180
+ }
181
+ // -- Wire validation (HTTP boundary) --
182
+ const bpsField = zod_1.z.number().int().nonnegative();
183
+ const amountField = zod_1.z
184
+ .string()
185
+ .regex(/^\d+$/, "must be a non-negative integer string (token base units)");
186
+ // The solver's `fee_breakdown` object as `/xochi/quote` serves it (SPEC.md
187
+ // Section 4; `LayeredFeeBreakdown` in `@xochi/spec/wire/openapi.json`).
188
+ // `gas_floor`/`total_usd` are price-oracle-derived and optional here so a
189
+ // pre-adapter core still parses; `.passthrough()` tolerates additive fields a
190
+ // newer server may send.
191
+ //
192
+ // Deliberately NOT the Worker's `FeeBreakdownSchema` (`shared/schemas.ts`),
193
+ // though both carry the same fifteen layer fields and the same optional
194
+ // `discount`. They are two contracts on two surfaces and neither accepts a
195
+ // superset of the other: the Worker's `/api/*` re-emission REQUIRES
196
+ // `gas_floor`/`total_usd`, closes `tier` to the five-tier enum and strips
197
+ // unknown keys, while this one validates the solver's own object (and the
198
+ // network-free core, which has no oracle envelope), pins every bps to a
199
+ // non-negative integer and `gas_floor.amount` to base units, and passes
200
+ // unknown keys through. Merging them would either make every pre-adapter core
201
+ // unparseable or relax the Worker contract the frontend renders against.
202
+ exports.LayeredFeeBreakdownSchema = zod_1.z
203
+ .object({
204
+ tier: zod_1.z.string(),
205
+ asset_class: zod_1.z.enum(["stable", "volatile"]),
206
+ gas_floor: zod_1.z.object({ amount: amountField, usd: zod_1.z.string() }).optional(),
207
+ solver_fee_bps: bpsField,
208
+ solver_fee: amountField,
209
+ price_impact_bps: bpsField,
210
+ price_impact: amountField,
211
+ venue_fee_bps: bpsField,
212
+ venue_fee: amountField,
213
+ routing_fee_bps: bpsField,
214
+ routing_fee: amountField,
215
+ total_bps: bpsField,
216
+ total_fee: amountField,
217
+ total_usd: zod_1.z.string().optional(),
218
+ surplus_share_pct: bpsField,
219
+ // Served only when the request carried `promo_discount_bps > 0` (xochi#584):
220
+ // which discount priced the quote and how much it took off the Standard
221
+ // venue + routing layers. Informational -- `venue_fee`/`routing_fee` are
222
+ // already net of it, so the reconciliation in `assertFeeBreakdownInvariants`
223
+ // neither reads nor subtracts it. Shape-only, like `gas_floor`: the promo
224
+ // input is worker-side and no fee vector carries it.
225
+ discount: zod_1.z
226
+ .object({ source: zod_1.z.enum(["tier", "promo"]), bps: bpsField, amount: amountField })
227
+ .optional(),
228
+ })
229
+ .passthrough();
230
+ // Parses + shape-validates a solver `fee_breakdown`. Throws (zod) on a
231
+ // malformed object -- callers at the HTTP boundary should not swallow this.
232
+ function parseFeeBreakdown(raw) {
233
+ return exports.LayeredFeeBreakdownSchema.parse(raw);
234
+ }
235
+ // The reconciliation fields of a decoded quote's `feeBreakdown` (the camelCase
236
+ // `FeeBreakdown` that `getQuote` and the machine hand back), so the client's own
237
+ // quote can go through `assertFeeBreakdownInvariants` without the wire object.
238
+ function feeBreakdownReconcilable(fb) {
239
+ return {
240
+ solver_fee_bps: fb.solverFeeBps,
241
+ price_impact_bps: fb.priceImpactBps,
242
+ venue_fee_bps: fb.venueFeeBps,
243
+ routing_fee_bps: fb.routingFeeBps,
244
+ total_bps: fb.totalBps,
245
+ solver_fee: fb.solverFee,
246
+ price_impact: fb.priceImpact,
247
+ venue_fee: fb.venueFee,
248
+ routing_fee: fb.routingFee,
249
+ total_fee: fb.totalFee,
250
+ };
251
+ }
252
+ // Structurally verifies a wire `fee_breakdown` reconciles. Throws with context
253
+ // on any violation; a server that misreports the split MUST fail loud here
254
+ // before funds move. Checks, in order:
255
+ // 1. bps layers sum to total_bps (with the solver-floor clamp),
256
+ // 2. total_bps >= solver_fee_bps (no discount crosses the floor),
257
+ // 3. amount layers sum to total_fee,
258
+ // 4. (optional) each layer amount == floor(base * bps / 10000).
259
+ function assertFeeBreakdownInvariants(fb, opts = {}) {
260
+ const bpsSum = fb.solver_fee_bps + fb.price_impact_bps + fb.venue_fee_bps + fb.routing_fee_bps;
261
+ const expectedTotalBps = Math.max(bpsSum, fb.solver_fee_bps);
262
+ if (fb.total_bps !== expectedTotalBps) {
263
+ throw new Error(`fee_breakdown: total_bps ${fb.total_bps} != clamped layer sum ${expectedTotalBps} ` +
264
+ `(solver ${fb.solver_fee_bps} + impact ${fb.price_impact_bps} + venue ${fb.venue_fee_bps} + routing ${fb.routing_fee_bps})`);
265
+ }
266
+ if (fb.total_bps < fb.solver_fee_bps) {
267
+ throw new Error(`fee_breakdown: total_bps ${fb.total_bps} below solver floor ${fb.solver_fee_bps}`);
268
+ }
269
+ const layers = {
270
+ solver: BigInt(fb.solver_fee),
271
+ price_impact: BigInt(fb.price_impact),
272
+ venue: BigInt(fb.venue_fee),
273
+ routing: BigInt(fb.routing_fee),
274
+ };
275
+ const feeSum = layers.solver + layers.price_impact + layers.venue + layers.routing;
276
+ const totalFee = BigInt(fb.total_fee);
277
+ if (feeSum !== totalFee) {
278
+ throw new Error(`fee_breakdown: layer amounts sum to ${feeSum} but total_fee is ${totalFee}`);
279
+ }
280
+ if (opts.input_amount !== undefined) {
281
+ const base = parseBaseAmount(opts.input_amount);
282
+ const checks = [
283
+ ["solver_fee", layers.solver, fb.solver_fee_bps],
284
+ ["price_impact", layers.price_impact, fb.price_impact_bps],
285
+ ["venue_fee", layers.venue, fb.venue_fee_bps],
286
+ ["routing_fee", layers.routing, fb.routing_fee_bps],
287
+ ];
288
+ for (const [name, amount, bps] of checks) {
289
+ const expected = layerAmount(base, bps);
290
+ if (amount !== expected) {
291
+ throw new Error(`fee_breakdown: ${name} ${amount} != floor(${base} * ${bps} / 10000) = ${expected}`);
292
+ }
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @xochi/client: a client for the Xochi trade API (`https://api.xochi.fi/api/*`).
3
+ *
4
+ * - The wire contract and shared vocabulary (chains, statuses, schemas).
5
+ * - The trade lifecycle as a pure state machine: `update(model, msg)`.
6
+ * - A transport over the Worker API: `createXochiClient`.
7
+ * - Pre-sign fund-safety checks, from `@xochi/signer`, run by the machine at
8
+ * quote ingest and exported for hosts that drive their own flow.
9
+ * - The layered fee reference (SPEC.md Section 4): the published schedule, the
10
+ * deterministic breakdown it implies, and the reconciliation check a served
11
+ * `fee_breakdown` must pass.
12
+ */
13
+ export * from "./shared/index.js";
14
+ export * from "./machine/index.js";
15
+ export * from "./transport/client.js";
16
+ export * from "./claim.js";
17
+ export * from "./fee.js";
18
+ export type { MethodPin, PinnedPullRecipients, PreparedClaim } from "@xochi/signer";
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * @xochi/client: a client for the Xochi trade API (`https://api.xochi.fi/api/*`).
4
+ *
5
+ * - The wire contract and shared vocabulary (chains, statuses, schemas).
6
+ * - The trade lifecycle as a pure state machine: `update(model, msg)`.
7
+ * - A transport over the Worker API: `createXochiClient`.
8
+ * - Pre-sign fund-safety checks, from `@xochi/signer`, run by the machine at
9
+ * quote ingest and exported for hosts that drive their own flow.
10
+ * - The layered fee reference (SPEC.md Section 4): the published schedule, the
11
+ * deterministic breakdown it implies, and the reconciliation check a served
12
+ * `fee_breakdown` must pass.
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ __exportStar(require("./shared/index.js"), exports);
30
+ __exportStar(require("./machine/index.js"), exports);
31
+ __exportStar(require("./transport/client.js"), exports);
32
+ __exportStar(require("./claim.js"), exports);
33
+ __exportStar(require("./fee.js"), exports);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The stealth claim-info gate, applied at quote ingest.
3
+ */
4
+ /**
5
+ * Why a served `claim_info` cannot be acted on, or null when it is fine.
6
+ *
7
+ * A stealth payout lands in a counterfactual SimpleAccount, and the FACTORY in
8
+ * `claim_info` is what decides who owns that account. The client follows the
9
+ * advertised factory on purpose (#302, so it and the solver derive the same
10
+ * address), which means an attacker who can influence the served value picks
11
+ * where the money goes and both sides agree on it.
12
+ *
13
+ * This is the last check before that value is trusted: the factory must be one
14
+ * this build ships, and the salt must be the documented 0. Note the client
15
+ * cannot verify the stealth ADDRESS itself at quote time (the solver picks the
16
+ * ephemeral key, so the address is only derivable once announced), which is
17
+ * exactly why the factory has to carry the weight.
18
+ */
19
+ export type ClaimInfoRejection = {
20
+ code: "untrusted_factory";
21
+ message: string;
22
+ } | {
23
+ code: "unexpected_salt";
24
+ message: string;
25
+ };
26
+ export declare function claimInfoError(claimInfo: {
27
+ factoryAddress?: string;
28
+ salt?: number;
29
+ } | undefined | null, chainId: number | undefined | null): ClaimInfoRejection | null;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * The stealth claim-info gate, applied at quote ingest.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.claimInfoError = claimInfoError;
7
+ const index_js_1 = require("../shared/index.js");
8
+ function claimInfoError(claimInfo, chainId) {
9
+ if (!claimInfo)
10
+ return null; // Not a stealth quote; nothing to check.
11
+ if (!(0, index_js_1.isTrustedStealthFactory)(claimInfo.factoryAddress, chainId)) {
12
+ return {
13
+ code: "untrusted_factory",
14
+ message: "This quote settles through an account factory this app does not recognize",
15
+ };
16
+ }
17
+ // Salt is the other half of the CREATE2 input: moving it relocates the
18
+ // account exactly as changing the factory does.
19
+ if (claimInfo.salt !== undefined && claimInfo.salt !== index_js_1.STEALTH_ACCOUNT_SALT) {
20
+ return {
21
+ code: "unexpected_salt",
22
+ message: "This quote settles to an unexpected account slot",
23
+ };
24
+ }
25
+ return null;
26
+ }