@v3finance/core-sdk 0.1.0 → 0.2.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 (217) hide show
  1. package/CHANGELOG.md +397 -7
  2. package/README.md +215 -72
  3. package/dist/cjs/client.d.ts +67 -22
  4. package/dist/cjs/client.d.ts.map +1 -1
  5. package/dist/cjs/client.js +38 -2
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/core/errors.d.ts +30 -10
  8. package/dist/cjs/core/errors.d.ts.map +1 -1
  9. package/dist/cjs/core/errors.js +47 -15
  10. package/dist/cjs/core/errors.js.map +1 -1
  11. package/dist/cjs/core/gated.d.ts +21 -3
  12. package/dist/cjs/core/gated.d.ts.map +1 -1
  13. package/dist/cjs/core/gated.js +12 -3
  14. package/dist/cjs/core/gated.js.map +1 -1
  15. package/dist/cjs/core/http.d.ts +12 -8
  16. package/dist/cjs/core/http.d.ts.map +1 -1
  17. package/dist/cjs/core/http.js +130 -40
  18. package/dist/cjs/core/http.js.map +1 -1
  19. package/dist/cjs/core/pagination.d.ts +28 -1
  20. package/dist/cjs/core/pagination.d.ts.map +1 -1
  21. package/dist/cjs/core/pagination.js +36 -1
  22. package/dist/cjs/core/pagination.js.map +1 -1
  23. package/dist/cjs/generated/operations.js +1 -1
  24. package/dist/cjs/generated/types.d.ts +948 -172
  25. package/dist/cjs/generated/types.d.ts.map +1 -1
  26. package/dist/cjs/index.d.ts +32 -8
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +12 -0
  29. package/dist/cjs/index.js.map +1 -1
  30. package/dist/cjs/resources/addresses.d.ts +22 -8
  31. package/dist/cjs/resources/addresses.d.ts.map +1 -1
  32. package/dist/cjs/resources/addresses.js +7 -1
  33. package/dist/cjs/resources/addresses.js.map +1 -1
  34. package/dist/cjs/resources/admin.d.ts +150 -25
  35. package/dist/cjs/resources/admin.d.ts.map +1 -1
  36. package/dist/cjs/resources/admin.js +94 -23
  37. package/dist/cjs/resources/admin.js.map +1 -1
  38. package/dist/cjs/resources/approvals.d.ts +46 -5
  39. package/dist/cjs/resources/approvals.d.ts.map +1 -1
  40. package/dist/cjs/resources/approvals.js +27 -4
  41. package/dist/cjs/resources/approvals.js.map +1 -1
  42. package/dist/cjs/resources/balances.d.ts +152 -16
  43. package/dist/cjs/resources/balances.d.ts.map +1 -1
  44. package/dist/cjs/resources/balances.js +9 -5
  45. package/dist/cjs/resources/balances.js.map +1 -1
  46. package/dist/cjs/resources/categories.d.ts +93 -21
  47. package/dist/cjs/resources/categories.d.ts.map +1 -1
  48. package/dist/cjs/resources/categories.js +65 -18
  49. package/dist/cjs/resources/categories.js.map +1 -1
  50. package/dist/cjs/resources/counterparties.d.ts +26 -3
  51. package/dist/cjs/resources/counterparties.d.ts.map +1 -1
  52. package/dist/cjs/resources/counterparties.js +17 -0
  53. package/dist/cjs/resources/counterparties.js.map +1 -1
  54. package/dist/cjs/resources/folders.d.ts +51 -10
  55. package/dist/cjs/resources/folders.d.ts.map +1 -1
  56. package/dist/cjs/resources/folders.js +36 -5
  57. package/dist/cjs/resources/folders.js.map +1 -1
  58. package/dist/cjs/resources/history.d.ts +20 -2
  59. package/dist/cjs/resources/history.d.ts.map +1 -1
  60. package/dist/cjs/resources/history.js.map +1 -1
  61. package/dist/cjs/resources/me.d.ts +27 -4
  62. package/dist/cjs/resources/me.d.ts.map +1 -1
  63. package/dist/cjs/resources/me.js +22 -1
  64. package/dist/cjs/resources/me.js.map +1 -1
  65. package/dist/cjs/resources/members.d.ts +19 -4
  66. package/dist/cjs/resources/members.d.ts.map +1 -1
  67. package/dist/cjs/resources/members.js +15 -6
  68. package/dist/cjs/resources/members.js.map +1 -1
  69. package/dist/cjs/resources/operations.d.ts +64 -8
  70. package/dist/cjs/resources/operations.d.ts.map +1 -1
  71. package/dist/cjs/resources/operations.js +26 -2
  72. package/dist/cjs/resources/operations.js.map +1 -1
  73. package/dist/cjs/resources/registries.d.ts +87 -9
  74. package/dist/cjs/resources/registries.d.ts.map +1 -1
  75. package/dist/cjs/resources/registries.js +54 -11
  76. package/dist/cjs/resources/registries.js.map +1 -1
  77. package/dist/cjs/resources/reports.d.ts +300 -41
  78. package/dist/cjs/resources/reports.d.ts.map +1 -1
  79. package/dist/cjs/resources/reports.js +135 -7
  80. package/dist/cjs/resources/reports.js.map +1 -1
  81. package/dist/cjs/resources/sharing.d.ts +61 -10
  82. package/dist/cjs/resources/sharing.d.ts.map +1 -1
  83. package/dist/cjs/resources/sharing.js +51 -4
  84. package/dist/cjs/resources/sharing.js.map +1 -1
  85. package/dist/cjs/resources/transfers.d.ts +26 -4
  86. package/dist/cjs/resources/transfers.d.ts.map +1 -1
  87. package/dist/cjs/resources/transfers.js +23 -1
  88. package/dist/cjs/resources/transfers.js.map +1 -1
  89. package/dist/cjs/resources/types.d.ts +20 -0
  90. package/dist/cjs/resources/types.d.ts.map +1 -1
  91. package/dist/cjs/units.d.ts.map +1 -1
  92. package/dist/cjs/units.js +18 -2
  93. package/dist/cjs/units.js.map +1 -1
  94. package/dist/cjs/version.d.ts +1 -1
  95. package/dist/cjs/version.js +1 -1
  96. package/dist/esm/client.d.ts +67 -22
  97. package/dist/esm/client.d.ts.map +1 -1
  98. package/dist/esm/client.js +38 -2
  99. package/dist/esm/client.js.map +1 -1
  100. package/dist/esm/core/errors.d.ts +30 -10
  101. package/dist/esm/core/errors.d.ts.map +1 -1
  102. package/dist/esm/core/errors.js +47 -15
  103. package/dist/esm/core/errors.js.map +1 -1
  104. package/dist/esm/core/gated.d.ts +21 -3
  105. package/dist/esm/core/gated.d.ts.map +1 -1
  106. package/dist/esm/core/gated.js +12 -3
  107. package/dist/esm/core/gated.js.map +1 -1
  108. package/dist/esm/core/http.d.ts +12 -8
  109. package/dist/esm/core/http.d.ts.map +1 -1
  110. package/dist/esm/core/http.js +131 -41
  111. package/dist/esm/core/http.js.map +1 -1
  112. package/dist/esm/core/pagination.d.ts +28 -1
  113. package/dist/esm/core/pagination.d.ts.map +1 -1
  114. package/dist/esm/core/pagination.js +36 -1
  115. package/dist/esm/core/pagination.js.map +1 -1
  116. package/dist/esm/generated/operations.js +1 -1
  117. package/dist/esm/generated/types.d.ts +948 -172
  118. package/dist/esm/generated/types.d.ts.map +1 -1
  119. package/dist/esm/index.d.ts +32 -8
  120. package/dist/esm/index.d.ts.map +1 -1
  121. package/dist/esm/index.js +17 -5
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/resources/addresses.d.ts +22 -8
  124. package/dist/esm/resources/addresses.d.ts.map +1 -1
  125. package/dist/esm/resources/addresses.js +7 -1
  126. package/dist/esm/resources/addresses.js.map +1 -1
  127. package/dist/esm/resources/admin.d.ts +150 -25
  128. package/dist/esm/resources/admin.d.ts.map +1 -1
  129. package/dist/esm/resources/admin.js +94 -23
  130. package/dist/esm/resources/admin.js.map +1 -1
  131. package/dist/esm/resources/approvals.d.ts +46 -5
  132. package/dist/esm/resources/approvals.d.ts.map +1 -1
  133. package/dist/esm/resources/approvals.js +27 -4
  134. package/dist/esm/resources/approvals.js.map +1 -1
  135. package/dist/esm/resources/balances.d.ts +152 -16
  136. package/dist/esm/resources/balances.d.ts.map +1 -1
  137. package/dist/esm/resources/balances.js +9 -5
  138. package/dist/esm/resources/balances.js.map +1 -1
  139. package/dist/esm/resources/categories.d.ts +93 -21
  140. package/dist/esm/resources/categories.d.ts.map +1 -1
  141. package/dist/esm/resources/categories.js +65 -18
  142. package/dist/esm/resources/categories.js.map +1 -1
  143. package/dist/esm/resources/counterparties.d.ts +26 -3
  144. package/dist/esm/resources/counterparties.d.ts.map +1 -1
  145. package/dist/esm/resources/counterparties.js +17 -0
  146. package/dist/esm/resources/counterparties.js.map +1 -1
  147. package/dist/esm/resources/folders.d.ts +51 -10
  148. package/dist/esm/resources/folders.d.ts.map +1 -1
  149. package/dist/esm/resources/folders.js +36 -5
  150. package/dist/esm/resources/folders.js.map +1 -1
  151. package/dist/esm/resources/history.d.ts +20 -2
  152. package/dist/esm/resources/history.d.ts.map +1 -1
  153. package/dist/esm/resources/history.js.map +1 -1
  154. package/dist/esm/resources/me.d.ts +27 -4
  155. package/dist/esm/resources/me.d.ts.map +1 -1
  156. package/dist/esm/resources/me.js +22 -1
  157. package/dist/esm/resources/me.js.map +1 -1
  158. package/dist/esm/resources/members.d.ts +19 -4
  159. package/dist/esm/resources/members.d.ts.map +1 -1
  160. package/dist/esm/resources/members.js +15 -6
  161. package/dist/esm/resources/members.js.map +1 -1
  162. package/dist/esm/resources/operations.d.ts +64 -8
  163. package/dist/esm/resources/operations.d.ts.map +1 -1
  164. package/dist/esm/resources/operations.js +26 -2
  165. package/dist/esm/resources/operations.js.map +1 -1
  166. package/dist/esm/resources/registries.d.ts +87 -9
  167. package/dist/esm/resources/registries.d.ts.map +1 -1
  168. package/dist/esm/resources/registries.js +54 -11
  169. package/dist/esm/resources/registries.js.map +1 -1
  170. package/dist/esm/resources/reports.d.ts +300 -41
  171. package/dist/esm/resources/reports.d.ts.map +1 -1
  172. package/dist/esm/resources/reports.js +135 -7
  173. package/dist/esm/resources/reports.js.map +1 -1
  174. package/dist/esm/resources/sharing.d.ts +61 -10
  175. package/dist/esm/resources/sharing.d.ts.map +1 -1
  176. package/dist/esm/resources/sharing.js +51 -4
  177. package/dist/esm/resources/sharing.js.map +1 -1
  178. package/dist/esm/resources/transfers.d.ts +26 -4
  179. package/dist/esm/resources/transfers.d.ts.map +1 -1
  180. package/dist/esm/resources/transfers.js +23 -1
  181. package/dist/esm/resources/transfers.js.map +1 -1
  182. package/dist/esm/resources/types.d.ts +20 -0
  183. package/dist/esm/resources/types.d.ts.map +1 -1
  184. package/dist/esm/units.d.ts.map +1 -1
  185. package/dist/esm/units.js +18 -2
  186. package/dist/esm/units.js.map +1 -1
  187. package/dist/esm/version.d.ts +1 -1
  188. package/dist/esm/version.js +1 -1
  189. package/docs/concepts.md +191 -0
  190. package/docs/errors.md +471 -0
  191. package/package.json +7 -6
  192. package/src/client.ts +89 -25
  193. package/src/core/errors.ts +49 -15
  194. package/src/core/gated.ts +22 -4
  195. package/src/core/http.ts +156 -46
  196. package/src/core/pagination.ts +38 -1
  197. package/src/generated/operations.ts +1 -1
  198. package/src/generated/types.ts +983 -173
  199. package/src/index.ts +64 -7
  200. package/src/resources/addresses.ts +23 -8
  201. package/src/resources/admin.ts +186 -33
  202. package/src/resources/approvals.ts +55 -9
  203. package/src/resources/balances.ts +186 -14
  204. package/src/resources/categories.ts +125 -30
  205. package/src/resources/counterparties.ts +23 -6
  206. package/src/resources/folders.ts +74 -18
  207. package/src/resources/history.ts +27 -2
  208. package/src/resources/me.ts +30 -5
  209. package/src/resources/members.ts +26 -7
  210. package/src/resources/operations.ts +73 -8
  211. package/src/resources/registries.ts +90 -11
  212. package/src/resources/reports.ts +348 -67
  213. package/src/resources/sharing.ts +71 -18
  214. package/src/resources/transfers.ts +33 -5
  215. package/src/resources/types.ts +20 -0
  216. package/src/units.ts +19 -2
  217. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -3,11 +3,398 @@
3
3
  All notable changes to this package are documented in this file. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.2.0 — 2026-11-11
7
+
8
+ The API corrected its own document between 0.1.0 and this release:
9
+ [v3-finance/policy-engine#13](https://github.com/v3-finance/policy-engine/pull/13) brought the
10
+ response schemas in line with what the handlers send and tied the two together with tests on the
11
+ API's side. Every answer this package had been describing by hand, because the document was wrong
12
+ about it, is generated again — and a number of generated shapes moved as a result. Those moves are
13
+ listed under **Breaking** below; read that part before upgrading.
14
+
15
+ ### Added
16
+
17
+ - `docs/errors.md` — every error code the API can return, grouped by domain, with what causes it
18
+ and what a client should do about it, plus the codes that are easy to mishandle and the ones
19
+ declared in the server's types but never actually returned.
20
+ - `registries.evaluate(input, { explain: true })` — the per-policy trace of a decision: for every
21
+ enabled policy the person has for the action, whether it applied and, if not, which selector it
22
+ failed on. The API already read `?explain=true` but did not declare it, and a typed client
23
+ should not invent parameters; it is declared now. All four decisions carry `trace[]` then, a
24
+ bare `deny` included. Without the flag the field is absent, which is why `EvaluationResult`
25
+ keeps it optional.
26
+ - One schema per report type — `TransactionActivityReport`, `CounterpartyActivityReport`,
27
+ `CounterpartyGroupActivityReport`, `AssetMovementReport`, `AddressActivityReport`,
28
+ `AccountStatementReport`, `OperationsExceptionsReport`, `ApprovalActivityReport`,
29
+ `BalancePositionReport`, next to the existing `CategoryAnalytics` — and `AnyReport`, the union of
30
+ the ten. With them come the row types they are built from (`ByCounterpartyAddressRow`,
31
+ `ByGroupCounterpartyRow`, `ByCounterpartyBriefRow`, `StatementTokenRow`, `ReportAccountBalance`).
32
+ - Named request bodies for the reporting resource: `CreateReportTemplateBody`,
33
+ `PatchReportTemplateBody`, `CreateReportSnapshotBody`, `CreateReportExportBody` and
34
+ `ReportDrilldownBody`.
35
+ - Three snapshot schemas instead of one: `ReportSnapshot` (what creating one answers, figures under
36
+ `result`), `ReportSnapshotDetail` (one snapshot, figures under `resultJson`) and
37
+ `ReportSnapshotListItem`, plus `ReportSnapshotList` for the listing.
38
+ - Ledger and balances: `LedgerPosting`, `LedgerEntryToken`, `LedgerEntriesPage`,
39
+ `BalancesAsOfTotal`, `BalancesAsOfAccount`, and `BalancesAsOfWithAccounts` — what
40
+ `balances.asOf()` answers when no summary was asked for.
41
+ - Sharing: `ParticipantVia` (where a person's access comes from, as a union discriminated by
42
+ `kind`), `ShareBatchItem` (`ShareBatchGrantedItem | ShareBatchRejectedItem`, discriminated by
43
+ `ok`), `ShareBatchAllRejected` (the body of the all-refused 400) and `ShareGrantCreated`.
44
+ - Fields the API has been sending and now declares, so they are reachable through the types:
45
+ `usdValue`, `decisionPolicyVersionId`, `decisionCounterpartyGroupsJson`, `counterpartyId` and
46
+ `idempotencyKey` on `TransactionDetail`; `currentVersionId` on `Policy`, `PolicyDetail`,
47
+ `PolicyWithRules` and the rows of `VaultPoliciesPage`; `policyVersionId` on `PolicyTrace`;
48
+ `policyId` on `PolicyApprover`; `revokedAt` and `revokedById` on `AddressAccessGrant` and
49
+ `VisibilityGrant` (which also gains `updatedAt`); `groupId` on the rows of `VaultPoliciesPage`;
50
+ and `_count` on `AddressGroup` and `AddressGroupDetail` — an ORM counter that equals
51
+ `memberCount`, declared only because it arrives; read `memberCount`.
52
+ One of these changed behaviour as well as type: `POST /policies` used to answer
53
+ `currentVersionId: null` although the first version had already been taken, and now answers its
54
+ id.
55
+
56
+ ### Changed
57
+
58
+ - Spec snapshot refreshed from the live API twice since 0.1.0.
59
+
60
+ The first refresh (API commit `ebd03eb`) moved no type. The envelope no longer drops the
61
+ machine-readable keys a handler sets beside `error`: it carries them inside `details`, so
62
+ `error.details.field` names the category field that failed validation (it is an explicit `null`
63
+ when the error is not about one field) and `error.details.stepIndex` the composite step that was
64
+ denied, counted from 0. Against the 0.1.0 snapshot that was 306 changed descriptions in
65
+ `openapi/spec.json` and 6 changed examples — the invitation token example stopped looking like a
66
+ live secret — and two lines in `src/generated/`.
67
+
68
+ The second (API commit `165504b`, v3-finance/policy-engine#13) is the one this release is about.
69
+ The document now names 262 schemas, up from 228: 34 added, none removed, and 30 of the existing
70
+ ones changed shape. 72 operations under `/vaults/{code}` now declare a 403 or a 404 they did not
71
+ before; that changes no type — it documents refusals those endpoints already gave, the
72
+ vault-membership gate first among them.
73
+ - `docs/errors.md` told callers those two keys were stripped and had to be parsed out of `message`.
74
+ That was accurate when it was written and is not any more. Corrected with it:
75
+ - an unmatched path and an unhandled exception now answer with the envelope as JSON, so a
76
+ non-JSON error body usually means the edge or a proxy answered rather than the API. Not always,
77
+ and the document now says so: a malformed or empty body sent under a JSON content type is
78
+ refused by the framework's validator with a 400 `text/plain`, which is raised as an exception
79
+ carrying its own finished response and so never passes through the envelope layer. No resource
80
+ method of this package can produce one;
81
+ - a validation failure does not reach a client as the flat `{error: "…"}` string the document
82
+ showed. The handler builds that shape, the envelope layer repackages it, and what arrives is
83
+ the ordinary envelope with a status-derived `BAD_REQUEST`;
84
+ - there is nothing diagnostic left in `message` to forward on a signing-service failure or a
85
+ failed report — but only the report one is a constant (`Report generation failed`). The
86
+ signing-service message is a template carrying the upstream status (`Wallet Service returned
87
+ 400`), on address creation as well as on transfers; what is withheld is the service's own
88
+ response body, which goes to the worker log.
89
+ - `reports.run(type, …)` answers with the schema of THAT report. `ReportResult` used to map nine of
90
+ the ten types to one loose `ActivityReport` in which every block was optional, so
91
+ `run('balance_position', …)` offered a `summary` that report never has and no `totalsByAsset`,
92
+ which is all it has. Each type now maps to its own schema; `runUnified(type, …)` narrows the same
93
+ way, and `runTemplate()` answers `AnyReport`, because a template's type is not known to the
94
+ compiler.
95
+ - Every request body of the reporting resource is typed. `createTemplate`, `updateTemplate`,
96
+ `createSnapshot` and `createExport` took `unknown` because the document declared those bodies
97
+ inline and there was nothing to import; it names them now. `queryOperations` takes
98
+ `DrilldownQuery`, which is `ReportDrilldownBody` with its two free-form bags (`filters`,
99
+ `drilldown`) replaced by the keys the endpoint actually reads — the generated type alone would
100
+ let a misspelled filter compile and make a figure disagree with its own breakdown — and answers
101
+ `DrilldownResponse`.
102
+ - `me.updateProfile()` is typed `UserProfile`, as it was, and the type is now TRUE. The PATCH
103
+ handler used to return the database row without `needsEmail`, so the answer read `undefined`
104
+ where a `boolean` was promised — on the flag that gates the "add your real email" prompt. The
105
+ API sends it on both paths now.
106
+ - Names this package exports that the document also has — `LedgerPosting`, `ReportSnapshotDetail`,
107
+ `ReportSnapshotListItem` — are strict aliases of the generated schemas, as are `LedgerEntryItem`
108
+ (of `LedgerEntry`) and `PolicyRow` (of `PolicyTableItem`). Only `ReportSnapshotListItem` existed
109
+ in 0.1.0, as a hand-written `Pick` of the same eleven fields.
110
+ - `ReportType` is taken from the document's own enum rather than spelled out. Same ten values.
111
+ - `AssignCategoryBody.values` is optional, as it is to the API, which defaults it to `{}`. A
112
+ category with required fields still needs them filled.
113
+ - `sharing.share()` and `folders.grant()` are typed with `ShareGrantCreated`. It is the same union
114
+ as before, `AddressAccessGrant | ShareBatchResult`, under the name the document gives it.
115
+ - `balances.entries({ addressId })` narrows for a non-administrator too. The API used to ignore
116
+ the filter for members and answer with entries across all of their addresses; it now applies it
117
+ within the member's own addresses, and somebody else's address answers an empty page rather than
118
+ a 403. Nothing changed on this side — the same call returns less.
119
+ - `balances.entries({ entryType })` is typed with `LedgerEntryType`: the six kinds the journal
120
+ reports — `genesis` among them, the opening correction written when the ledger was first
121
+ backfilled — or the stored spelling of the three that are renamed on the way out. That is the
122
+ FILTER; an entry itself still carries a `string`, as the document declares.
123
+
124
+ #### Breaking
125
+
126
+ Type-level only: no method was renamed or removed, no exported name was dropped, and an existing
127
+ call sends the same request. What changed is what the compiler believes about answers, so an
128
+ upgrade can surface compile errors in code that reads them — in nearly every case at a spot that
129
+ was already wrong at runtime: a field that read `undefined`, a string treated as a number.
130
+
131
+ **A field changed type**
132
+
133
+ - `Comparison.incomingUsdChangePercent`, `outgoingUsdChangePercent`,
134
+ `grossTurnoverUsdChangePercent`, `operationsCountChangePercent` and
135
+ `averageOperationUsdChangePercent` — `number | null` → `string | null`.
136
+ - `Observation.changePercent` — `number | null` → `string | null`.
137
+
138
+ These six are decimal STRINGS on the wire (`"12.5"`, `"-40"`) and always were; the document said
139
+ `number`. Arithmetic or `toFixed()` on them compiled and operated on a string. Put them through
140
+ `parseDecimal`, or `Number()` if a float is acceptable for display.
141
+ - `ReportMeta.dataCutoff` — `string | null` → `{ timestamp: string, blockNumber: number | null }`.
142
+ - `ReportMeta.schemaVersion` — `string | number | null` → `string`.
143
+ - `CategoryAnalytics.report` — no longer `ReportMeta` but the five fields that report really
144
+ carries (`type`, `generatedAt`, `from`, `to`, `timezone`).
145
+ - `ActivityReport` — an interface whose twelve blocks were all optional → a deprecated UNION of
146
+ the nine per-type schemas. Reading `report.summary` off it no longer compiles, because not every
147
+ member has one. Use the schema of the report you asked for, or `AnyReport`. Narrow a union by a
148
+ block only that report has (`'totalsByAsset' in report`): `report.type` will not do it, since
149
+ the document types that field as the same ten-value enum on every member.
150
+ - `reports.runTemplate()` — `ActivityReport` → `AnyReport`, which adds `CategoryAnalytics`.
151
+ - `reports.run()` and `reports.runUnified()` — `ActivityReport` → the schema of the report asked
152
+ for. Reading a block that report does not have stops compiling: `.approvals` on anything but
153
+ `approval_activity`, `.summary` on `account_statement`. Those reads returned `undefined` before.
154
+
155
+ **A parameter stopped accepting any string**
156
+
157
+ - `ReportFilters`: `dateBasis`, `groupBy`, `comparison`, `counterpartyClassification`,
158
+ `folderClassification`, `breakdownBy` and `agg` are the enums the document declares. `groupBy:
159
+ 'quarter'` used to compile and answer 400.
160
+ - `reports.run('address_activity', …)` requires exactly one of `addressId` and `folderId`. Neither,
161
+ or both, was a 400 — delivered as `REPORT_INVALID_PERIOD`, which points at the dates.
162
+ - `balances.entries({ entryType })` — `string` → the union of entry types.
163
+ - `reports.snapshot()` — `ReportSnapshot` → `ReportSnapshotDetail`. There is no `result` on it: the
164
+ figures are under `resultJson`, where the detail endpoint has always put them. `result` exists
165
+ only on what `createSnapshot()` answers.
166
+ - `BalancesAsOf.totals` and `.accounts` — arrays of untyped records → `BalancesAsOfTotal[]` and
167
+ `BalancesAsOfAccount[]`. `accounts` is also OPTIONAL in the schema now (absent under
168
+ `summary: true`); the three overloads of `balances.asOf()` still tell the two cases apart, so
169
+ `(await balances.asOf(at)).accounts` needs no check.
170
+ - `LedgerEntry.postings[]` — the fields the document used to promise beyond the two ids (`amount`,
171
+ `amountDecimal`, `direction`, `token`, `account`) are gone, replaced by what arrives:
172
+ `LedgerPosting`, with the figure in `amountRawSigned`/`amountDecimalSigned`.
173
+ `LedgerEntry.token` is `LedgerEntryToken | null` instead of an untyped record, and
174
+ `operationType` is `'transfer' | 'deposit' | null`.
175
+ - `Participant.via` — an array of untyped records → `ParticipantVia[]`.
176
+ - `CategoryAccessScope` — `unknown` → `'owner_only' | 'vault_members'`, which is what
177
+ `viewScope`, `assignScope` and `editScope` take on `categories.create()`/`update()` and what a
178
+ category carries. The API still ACCEPTS `restricted` on input as an alias of `owner_only`, stores
179
+ it as `owner_only` and never answers with it; the type leaves it out on purpose, so a caller
180
+ that sends it stops compiling. Send `owner_only`.
181
+
182
+ **A field was removed from a type — it never arrived**
183
+
184
+ - `ReportTemplate.createdById` → the field is `ownerId`. `description` is declared, and
185
+ `parametersJson` is never `null`.
186
+ - `ByAddressOwnerRow.ownerId` and `.owner` → the field is `userId`, and the row also carries
187
+ `addressesCount`, `incomingByAsset` and `outgoingByAsset`.
188
+ - `AssetAmount.contractAddress`. It is on `ByAssetRow`, not on an amount.
189
+ - `PolicyTableItem.conditionJson` and `.approvalJson` — neither policy listing selects them
190
+ (`admin.policies.list`, `admin.policyGroups.policies`), so a row cannot tell you whether a policy
191
+ is conditional. Load the policy itself for that.
192
+ - `_count.limits` on the rows of `VaultPoliciesPage`.
193
+
194
+ **A field became required or lost `null`**
195
+
196
+ Harmless when reading, a compile error when BUILDING one of these — a fixture or a mock: the six
197
+ status counters, `activeDays`, `firstOperationAt` and `lastOperationAt` on `ReportSummary`; every
198
+ field of `ReportMeta` (all are required now, `from`, `to`, `timezone` and `generatedById` are never
199
+ `null`, and `type`, `dateBasis`, `baseCurrency` and `classificationMode` are enums); `tokenId`,
200
+ `assetCode` and `network` on `AssetAmount` and `ByAssetRow`; `severity` (`'info' | 'warning'`) and
201
+ `drilldownFilters` on `Observation`; most of `ReportSnapshot`, `result` included; `vaultId`,
202
+ `requestedAt`, `storageKey`, `errorCode` and `errorMessage` on `ReportExport`; `updatedAt` on
203
+ `ReportTemplate`; `metric.agg` on `CategoryAnalytics`; `LedgerEntry.reference`; and the fields
204
+ listed under Added. `ShareBatchResult.results[]` is a discriminated union now, so an item must be
205
+ consistent with its `ok`.
206
+
207
+ **A request body that was `unknown` is checked**
208
+
209
+ `createTemplate`, `updateTemplate`, `createSnapshot` and `createExport`, as above; and
210
+ `queryOperations`, whose body went from `unknown` to `DrilldownQuery` and whose answer from
211
+ `unknown` to `DrilldownResponse`. A body the API would have refused with a 400 no longer compiles.
212
+
213
+ `parameters` on the first three is typed as a free-form record, and it is NOT a query string — the
214
+ API hands it to the report builder as it is, parsing nothing and defaulting nothing. So the
215
+ obvious move, spreading the filters of a live report into it, is safe only with two conditions:
216
+
217
+ - **`categoryField` is ignored there.** A live report parses `'ticker:EUR'` into a filter; a
218
+ template or snapshot does not, and answers 200 with figures for EVERY value — a frozen report
219
+ that silently is not the one on screen. Send the parsed form instead:
220
+ `categoryFieldValues: { ticker: ['EUR'] }`. This is a defect of the API, reported there; the
221
+ package documents it rather than hides it.
222
+ - **`timezone` has no default there.** The live endpoints fall back to `UTC`; pass it explicitly.
223
+
224
+ Multi-value filters must be arrays (a comma-separated string is not split), and because the type
225
+ is an index signature a value typed as the `ReportFilters` interface needs a spread
226
+ (`parameters: { ...filters }`).
227
+
228
+ **Unchanged in type, corrected in meaning**
229
+
230
+ - `AssetAmount.amount` is still a `string`, and its description no longer says "raw smallest
231
+ units". It is a DECIMAL string in units of the asset (`"1.5"`), signed only in the `netFlow*`
232
+ blocks. Code that passed it to `amount()` was already failing at runtime on the first fractional
233
+ value; use `parseDecimal`. The same holds for the `byAsset` rows and the balances of
234
+ `asset_movement`, `account_statement` and `balance_position`. The one raw figure in a report is
235
+ `balanceRaw` in the opening and closing balances of `address_activity`.
236
+ - `BalancesAsOfTotal.available` and `.reserved` are DECIMAL too — duplicates of
237
+ `availableDecimal`/`reservedDecimal` — while the fields of the same names on `balances.list()`
238
+ are raw. Raw figures of a replay exist only per account, in `accounts[].balance`.
239
+ - The percentage fields that are new to the document with the per-type schemas —
240
+ `turnoverShareUsd`, `shareOfGroupTurnoverUsd`, `shareOfUserActivityUsd`, the `…RatePercent`
241
+ fields of `approval_activity`, the two shares in `balance_position.concentration` — are decimal
242
+ strings as well.
243
+
244
+ ### Fixed
245
+
246
+ Nineteen of the fixes below NARROW a type that used to be `string`, `unknown` or a loose
247
+ `Record`. Code that passed a value the API rejects now fails to compile instead of failing at
248
+ runtime with a 400 — which is the point, but it means an upgrade can surface new compile errors.
249
+ None of them renames or removes an exported name.
250
+
251
+ **Calls that could not be made at all**
252
+
253
+ - `reports.run('balance_position', …)` accepted no parameters. Its entry declared
254
+ `Record<string, never>`, and that index signature applies to the fields of `ReportFilters` too
255
+ once the two are intersected, collapsing every one of them to `never`. The only argument that
256
+ compiled was `{}` — so the report could not be asked for a date, which is the whole point of a
257
+ position report. All six parameters the handler reads now compile.
258
+ - The `idempotency-key` header is no longer sent. The server has never read it, which by itself
259
+ bought nothing; in a browser it cost the entire request, because the API's CORS preflight allows
260
+ exactly `Content-Type` and `Authorization`, so a money call carrying the header was never sent
261
+ and surfaced as a network error no retry explained. The `idempotencyKey` option stays, accepted
262
+ and deprecated: the body field is what protects a transfer.
263
+ - `units.format()` threw `RangeError` for any token with 0 or 1 decimals (`minimumFractionDigits`
264
+ defaulted to 2 above a `maximumFractionDigits` of `decimals`). The decimals ceiling was also 36
265
+ while the API and the package's own generated type allow 38, so `registries.createToken` could
266
+ register a token that `amount()` then refused.
267
+
268
+ **Answers that were read from the wrong place**
269
+
270
+ - `balances.entries()` typed postings from the document, which declared `amount`,
271
+ `amountDecimal`, `direction`, `token` and `account`. The handler sends none of them — the money
272
+ field is `amountRawSigned` — so `BigInt(posting.amount)` threw on the only endpoint that exposes
273
+ the ledger. The document has been corrected, and the type is the generated `LedgerPosting`.
274
+ - `reports.snapshot()` promised `result`; the detail handler returns the stored row, so the field
275
+ is `resultJson`. The numbers were unreachable through the type and `result` read `undefined`
276
+ every time. The document now has a schema for each answer: `snapshot()` is typed with
277
+ `ReportSnapshotDetail`, and `createSnapshot()` keeps `ReportSnapshot` with `result` — that
278
+ handler does map it.
279
+ - `balances.asOf()` exposed 2 of its 8 parameters. Opening the rest meant opening `summary`, and
280
+ with `summary` the handler omits `accounts` altogether. Three overloads tell the cases apart —
281
+ `BalancesAsOfWithAccounts` without a summary, `BalancesAsOfSummary` with one, the union when the
282
+ flag is decided at runtime — so the type never promises an array that will not be there. The
283
+ document agrees now: `accounts` is optional on `BalancesAsOf`.
284
+
285
+ **Values the API rejects no longer compile**
286
+
287
+ - `approvals.list({status})` took any string, but approval statuses are UPPERCASE while transfer
288
+ statuses are lowercase and both appear on one screen; `'pending'` compiled and got a 400.
289
+ - `history.list({status})` took any string, and the package's own exported `TransactionStatus`
290
+ includes `submitted`, which that filter does not accept — the SDK was suggesting a value that
291
+ returns 400.
292
+ - `balances.list({purpose})` knew 2 of the 4 ledger purposes.
293
+ - `OperationType`, `OperationKind` and `PendingApproval` were hand-written aliases widened to
294
+ `string`, and an explicit re-export shadows the generated schema of the same name — so
295
+ `OperationType` reached consumers as `string` rather than `'deposit' | 'transfer'`, and a typo
296
+ in an operation type was caught only by the API. All three are strict aliases now, and
297
+ `test/nameShadowing.test.ts` fails if one widens again.
298
+ - Request bodies that were `unknown` on `categories.addField`/`updateField`/`addApprover`/
299
+ `addGrant`, `admin.invitations.acceptDebug`, `vaults.buildDerivationPath`/`parseDerivationPath`
300
+ now carry their schemas. Response types were filled in only where the handler was read and
301
+ matched.
302
+
303
+ **Transport**
304
+
305
+ - `timeoutMs` and `signal` stopped applying once the response headers arrived: the timer was
306
+ cleared around `fetch`, and the body streamed after. A server that answered and then stalled
307
+ mid-body left a request hanging with nothing to end it. Both now stay armed until the body is in
308
+ hand, and a body read that fails is no longer swallowed into `data: undefined` typed as the
309
+ shape the caller asked for.
310
+ - A non-JSON error body put the proxy's whole HTML page into `message` (measured at 2.3 KB), and
311
+ `request()` and `fetchRaw()` disagreed about the same 502. They now answer alike, HTML is
312
+ suppressed and a short text body is kept.
313
+ - `Gated.status` reported 201 when the server had answered 200.
314
+ - A sub-second timeout rendered as "within 0s".
315
+
316
+ **Documentation that no longer matched the API**: the shape of a validation error, the claim that
317
+ a non-JSON error body can no longer come from the API, `CATEGORY_NOT_FOUND`'s actual paths,
318
+ `REPORT_VAULT_NOT_FOUND` (unreachable — all seven in-handler vault 404s in the reports router are
319
+ dead code), `details.param` on `REPORT_INVALID_PARAMS`, `SUBJECT_REQUIRED` across its three
320
+ different answers, four of nine observation codes, the missing `OPERATION_RELATION_INVALID`, the
321
+ transport codes (`NETWORK_ERROR`, `TIMEOUT`, `ABORTED`, `PARSE_ERROR`) that were named nowhere,
322
+ the count of endpoints left out, the `replayed` branch that only `transfers.send` can reach, and
323
+ the keyset-pagination claim (11 list endpoints are cursored, 18 return `items` alone, 8 a bare
324
+ array).
325
+
326
+ - `admin.users.setRole` took any string for the platform role. The spec declares
327
+ `enum: ["admin", "user"]`, so this is a narrowing the contract already backed — and worth the
328
+ compile error on a privilege path, where `string` meant finding out from a 400 after sending a
329
+ role change. The response keeps `role: string`: the spec declares it bare, and narrowing an
330
+ answer we do not control would be a guess. The type is exported as `PlatformRole`.
331
+
332
+ - `approvals.vote` returned `unknown`. It is typed from `RequestVoteResult`, which the spec
333
+ declares as a component but never `$ref`s — the operation inlines its 200 instead. The two were
334
+ compared property by property and are the same schema, down to identical `required` sets; the
335
+ only difference is an `additionalProperties: false` that does not reach the TypeScript shape.
336
+ Worth checking rather than assuming, because a component nothing references usually means the
337
+ opposite — an orphan left behind, which is what the `Address` schema turned out to be.
338
+ - `deleteSnapshot` and `deleteTemplate` returned `unknown` for a body the spec pins as
339
+ `{deleted: true}`.
340
+
341
+ **Guards added**, because none of the above was caught by a green CI: `generate:check` compares
342
+ only the generated files against the snapshot, and the surface test only checks that paths are
343
+ reachable. New metatests cover name shadowing, query parameters missing from a method's type,
344
+ request bodies left as `unknown`, and the completeness of `docs/errors.md`.
345
+
346
+ Two more came with the corrected document. `test/reportResults.test.ts` holds every method of the
347
+ reporting resource to the schema the API declares for the operation it calls — the check that
348
+ would have caught nine report types sharing one catch-all. And the body test now has a second
349
+ list beside the untyped one: bodies typed by hand OVER a named schema, each held to that schema's
350
+ fields. It has one entry, `queryOperations`; the list of untyped bodies is empty.
351
+
352
+ `test/transport.test.ts` pins the transport fixes by BEHAVIOUR, which none of the metatests can
353
+ reach: they are all about *when* something happens — when the timer is cleared, when the body is
354
+ read, which status is reported — and no type describes that. Its stub aborts its body stream when
355
+ the signal fires, the way a real `fetch` does, and that detail is the whole test: a stub that
356
+ ignores the signal passes even against the broken client, because nothing can interrupt a body
357
+ nobody wired up. Each case was checked by reverting the fix — restoring the old timer placement
358
+ turns four of them red with "test timed out", which is the production symptom exactly.
359
+
360
+ **Seven places where the API's document disagreed with its own handlers** turned up while this
361
+ release was being prepared. hono-openapi does not validate answers against the document, so
362
+ nothing here could fix them — only describe the answer by hand and say why.
363
+ v3-finance/policy-engine#13 fixed all seven at the source, and the hand-written descriptions have
364
+ been dropped in favour of the generated types:
365
+
366
+ | What was wrong | What it meant in 0.1.0 | Now |
367
+ |---|---|---|
368
+ | Ledger postings declared five fields that never arrive and none of the six that do | `posting.amount` was `undefined`; `BigInt(posting.amount)` threw | generated `LedgerPosting`, figure in `amountRawSigned` |
369
+ | One schema for a snapshot, though creating one answers `result` and reading one `resultJson` | `snapshot(id).result` read `undefined`; the figures were unreachable through the type | three schemas; `snapshot()` answers `ReportSnapshotDetail` |
370
+ | `accounts` required on `BalancesAsOf`, though absent under `summary=true` | nothing yet — `asOf()` did not offer `summary` | optional in the schema; the overloads of `asOf()` say which call gets it |
371
+ | `AssetAmount.amount` described as raw smallest units | `amount(x.amount, decimals)` threw on the first fractional value | described as the decimal string it is |
372
+ | The all-refused batch grant answers 400 with `{results}`, declared as an error envelope | the per-subject reasons were in `error.details`, which nothing described | declared as a second form of that 400, `ShareBatchAllRejected`. The SDK still throws on it, body in `details` |
373
+ | `usdValue`, `decisionPolicyVersionId`, `currentVersionId` sent but not declared | unreachable through the types | declared; see Added |
374
+ | `PATCH /me/profile` declared `needsEmail` and did not send it | `updateProfile()` answered `needsEmail: undefined` | the HANDLER was fixed, not the document: the flag is sent |
375
+
376
+ What remains hand-written is hand-written because the document has nothing to offer, not because
377
+ it is wrong: query parameters (the generator emits schemas only, never `parameters`), the few
378
+ answers the API still declares inline without a name (`AmlScore`, the status-history row), and the
379
+ unions it carries only as a bare `string` — `TransactionStatus`, `ApprovalRequestStatus`,
380
+ `LedgerEntryType`. One answer stays untyped for the first of those reasons: the group access
381
+ summary of `admin.policyGroups.access`.
382
+
383
+ - `codeFromStatus` now uses the API's own name for every status the API names: 401 is
384
+ `UNAUTHENTICATED` (was `UNAUTHORIZED`), 422 is `UNPROCESSABLE_ENTITY` (was `UNPROCESSABLE`), 503
385
+ is `SERVICE_UNAVAILABLE` (was `UNAVAILABLE`), and 413/501 are now named at all. Previously a
386
+ non-JSON response gave a code the server never uses for that condition, so a client branching on
387
+ the server's name missed it. 502 and 504 keep the SDK's own names, because the API has no
388
+ envelope for them — they arrive from the edge.
389
+ The test covering this mapping asserted only the statuses that already agreed; it now covers all
390
+ of them.
391
+
6
392
  ## 0.1.0 — 2026-11-10
7
393
 
8
- First release. The whole API is covered — 171 operations grouped into resources, with the
9
- administrative surface marked — and a test asserts that every endpoint is reachable, so one added
10
- to the API shows up as a failure rather than as a gap nobody noticed.
394
+ First release. Every endpoint a client calls is covered — 157 of the API's 171 operations, grouped
395
+ into resources, with the administrative surface marked; the fourteen exceptions are listed under
396
+ Known limitations. A test asserts that each of the rest is reachable, so one added to the API shows
397
+ up as a failure rather than as a gap nobody noticed.
11
398
 
12
399
  ### Added
13
400
 
@@ -17,8 +404,8 @@ to the API shows up as a failure rather than as a gap nobody noticed.
17
404
  - Injected authentication: `getAuthHeaders`, `onTokenRotated`, `onUnauthorized`. Works with a
18
405
  browser cookie, a native cookie jar or a bearer token; the package stores no credentials.
19
406
  - `CoreError` with a `code` that is always present and a `kind` separating HTTP, network, timeout,
20
- aborted and parse failures. Three real response shapes are parsed, including the validation
21
- failure that arrives as a plain string.
407
+ aborted and parse failures. Three real response shapes are parsed, including a flat
408
+ `{error: "…"}` string — which is what a non-JSON error body is read as.
22
409
  - Money as smallest-unit strings with BigInt arithmetic (`amount`, `parseDecimal`, `format`, `add`,
23
410
  `sub`, `compare`). There is no `toNumber`, on purpose: it would be a convenient door into the
24
411
  precision loss the module exists to prevent.
@@ -40,5 +427,8 @@ to the API shows up as a failure rather than as a gap nobody noticed.
40
427
  - The `assets` filter on reports is accepted by the API and silently ignored by it, so it is marked
41
428
  deprecated here. Use `tokenIds`.
42
429
  - `runTemplate` cannot override a template's stored filters: the endpoint accepts no body.
43
- - `/welcome` and `/webhooks/wallet` are not exposed — a browser landing page and the wallet
44
- service's inbound channel respectively.
430
+ - 14 of the 171 operations are not exposed. `/welcome` and `/webhooks/wallet` are a browser landing
431
+ page and the wallet service's inbound channel; the other 12 are the `/address-groups/*` paths,
432
+ which are the older prefix of the same endpoints the folders resource serves under
433
+ `/address-folders` — one entity, two prefixes, and exposing both would only raise the question of
434
+ which to call.