@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
@@ -1,5 +1,5 @@
1
1
  /** SDK version, sent in the `user-agent` header. */
2
- export declare const SDK_VERSION = "0.1.0";
2
+ export declare const SDK_VERSION = "0.2.0";
3
3
  /** Default API base URL. */
4
4
  export declare const DEFAULT_BASE_URL = "https://policy-engine-rest.ezig.workers.dev";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /** SDK version, sent in the `user-agent` header. */
2
- export const SDK_VERSION = '0.1.0';
2
+ export const SDK_VERSION = '0.2.0';
3
3
  /** Default API base URL. */
4
4
  export const DEFAULT_BASE_URL = 'https://policy-engine-rest.ezig.workers.dev';
5
5
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,191 @@
1
+ # Concepts
2
+
3
+ The API behind this SDK is a custody system: it holds addresses, moves money under rules, and
4
+ records who decided what. Most of the surprises in it come from five ideas. Knowing them makes the
5
+ rest of the package read as obvious; not knowing them is how an interface ends up telling someone
6
+ their money has left when it has not.
7
+
8
+ ## 1. A vault is the unit of everything
9
+
10
+ A vault owns addresses, counterparties, policies, categories and reports. Almost every path is
11
+ `/vaults/{code}/…`, and nothing crosses a vault boundary.
12
+
13
+ In the SDK that is `core.vault(code)`, created where it is used rather than stored:
14
+
15
+ ```ts
16
+ core.vault('alpha').addresses.list()
17
+ core.vault('beta').addresses.list()
18
+ ```
19
+
20
+ There is no "current vault". An administrator works with several at once, the active one changes at
21
+ runtime in both our clients, and a remembered scope would mean requests to the wrong vault after a
22
+ switch — a failure mode with no symptom until the figures are wrong.
23
+
24
+ ## 2. Two tiers of authorisation, and they do not substitute for each other
25
+
26
+ This is the distinction most often confused in this system.
27
+
28
+ | | **Policy** | **Grant** |
29
+ | --- | --- | --- |
30
+ | Answers | may this ACTION happen? | is there access to this OBJECT? |
31
+ | Input | action plus request context — amount, recipient, path, history | a pair: subject and resource |
32
+ | Nature | computed every time | a fact, stored as a row |
33
+ | Outcomes | three: allow / block / require approval | binary: yes or no |
34
+
35
+ They compose. Sending a transfer first needs the address right (a grant) and then clears the engine
36
+ (a policy). Removing either does not simplify anything — they answer different questions.
37
+
38
+ Practical consequences you will meet:
39
+
40
+ - the counterparty directory does **not** restrict where money can go. A transfer accepts any
41
+ address; the directory only attaches a name afterwards. Restricting recipients is a policy of the
42
+ form `block + not(...)`;
43
+ - policies are loaded only from the initiator's groups. An allow-list added to one group does not
44
+ apply to members of another, so a list meant for everyone has to exist in every transfer group;
45
+ - visibility is a grant too, and the default is deny. A member sees only themselves until someone
46
+ grants otherwise, which is why an empty directory usually means "no visibility grants yet" rather
47
+ than "no members".
48
+
49
+ ## 3. An operation may not happen — it may go for approval
50
+
51
+ Transfers, address creation, changes to the counterparty directory and gated labelling all pass
52
+ through the policy engine, and the engine has three outcomes, not two.
53
+
54
+ The API expresses this with the HTTP status, because **the response bodies are identical**:
55
+
56
+ | Status | Meaning |
57
+ | --- | --- |
58
+ | 201 | applied — it happened |
59
+ | 202 | pending — funds are RESERVED, nothing has moved, there is a request to sign |
60
+ | 200 | replayed — the same idempotency key; it already happened earlier |
61
+
62
+ The SDK turns that into `Gated`:
63
+
64
+ ```ts
65
+ switch (result.outcome) {
66
+ case 'applied': return toast('Transfer sent')
67
+ case 'pending': return toast('Awaiting signatures — funds are reserved')
68
+ case 'replayed': return toast('This transfer was already sent')
69
+ }
70
+ ```
71
+
72
+ `replayed` is kept apart from `applied` on purpose: a replay means the previous attempt reached the
73
+ server and only the answer was lost, so the person should be told "already sent" rather than being
74
+ told "sent" a second time.
75
+
76
+ **Only `transfers.send` can return it.** `idempotencyKey` exists on exactly one request body in the
77
+ whole API — the transfer — so it is the only place where a 200 means "this already happened". On
78
+ every other gated method a 200 is an ordinary success (updating a counterparty, for instance) and
79
+ is labelled `applied`; writing a `case 'replayed'` there is harmless but dead. The branch is worth
80
+ handling where it is reachable and worth knowing about where it is not, because "the key protects
81
+ the retry" is true of transfers and of nothing else: a timed-out counterparty change has to be
82
+ verified by reading the object back.
83
+
84
+ Two further rules about approvals:
85
+
86
+ - **who may vote is a snapshot.** It is taken when the request is created, from the policies as
87
+ they stood then. Editing a policy or a group afterwards does not change a request in flight —
88
+ deliberately, so that a change of rules cannot retroactively unlock money already reserved;
89
+ - **requirements are collected from every applicable policy**, not resolved to the strictest one.
90
+ That is how "the CFO AND compliance" is expressed, and it means adding a permissive policy beside
91
+ an existing one does not remove its signatures.
92
+
93
+ ## 4. Money is a string, and zero is not nothing
94
+
95
+ Every amount is raw smallest units in a string: `"96325670000"` is 96 325.67 USDT. `float64` loses
96
+ precision above 2⁵³, and balances here exceed that.
97
+
98
+ ```ts
99
+ import { amount, format, add, parseDecimal } from '@v3finance/core-sdk'
100
+
101
+ const balance = amount(address.balance ?? '0', 6, 'usdt')
102
+ format(balance, { withAsset: true }) // "96,325.67 USDT"
103
+ add(balance, parseDecimal('2.5', 6))
104
+ ```
105
+
106
+ There is no `toNumber`. It would be a convenient door into exactly the bug this layer exists to
107
+ prevent.
108
+
109
+ **Where it is decimal instead.** "Raw" above describes what the API ANSWERS with — addresses,
110
+ transfers, deposits, balances, the ledger. Three places are decimal, and one of them is an INPUT
111
+ where a mistake moves money:
112
+
113
+ - **the `amount` you send to `transfers.send` is DECIMAL** — `'1.5'` means one and a half USDT, and
114
+ the API scales it by the token's decimals. Sending the raw figure `'1500000'` there asks for a
115
+ million and a half. The same holds for `context.amount.value` in `registries.evaluate`;
116
+ - the figures inside a report are decimal strings in units of the asset — `"1.5"`, already scaled.
117
+ The one raw figure in a report is `balanceRaw`, in the opening and closing balances of
118
+ `address_activity`;
119
+ - the totals of `balances.asOf()` are decimal, although the same-named fields of `balances.list()`
120
+ are raw.
121
+
122
+ `amount()` would misread a decimal: it rejects `"1.5"` outright and takes `"15"` for fifteen
123
+ smallest units. Read decimals with `parseDecimal`, and convert before sending — never pass a raw
124
+ string where a decimal is expected, or the other way round.
125
+
126
+ **`balance: null` is not zero.** It means the address has no ledger account yet — nothing has ever
127
+ arrived. "No account" and "empty account" deserve different wording, and merging them produces an
128
+ interface that claims an address is empty when it has never been used.
129
+
130
+ Underneath, balances are derived from a double-entry ledger, which is the single source of truth:
131
+ no balance is stored on an address. A token with no ledger account simply does not appear in
132
+ `balances[]`, for the same reason.
133
+
134
+ The ledger is append-only. A correction is a reversing entry, never an edit.
135
+
136
+ ## 5. Shared access: roles out, flags in
137
+
138
+ An address or a folder can be shared. Outwards the right is a **role**, inwards it is flags:
139
+
140
+ - `viewer` — can see;
141
+ - `contributor` — can see and transfer.
142
+
143
+ There is no "manager" role, because that is the creator of the object: one person, not something a
144
+ grant hands out.
145
+
146
+ Granting is idempotent — repeating it for the same pair updates the rights and revives a revoked
147
+ grant — and revoking is soft and immediate.
148
+
149
+ A folder and an address group are **one entity** served under two prefixes; this package uses
150
+ `/address-folders`. Two rules protect it from leaking somebody else's address:
151
+
152
+ - only the creator changes a folder's contents, and only with their own addresses;
153
+ - an owner can always pull their own address out of someone else's folder.
154
+
155
+ ## Labelling and reports, in one paragraph each
156
+
157
+ **Operation categories** are a configurable schema: which categories exist and what fields they
158
+ have is visible to every member, while the *values* on operations are restricted and managing the
159
+ schema is administrator-only. A category key and a field's key and type are immutable once created,
160
+ because values, reports and snapshots refer to them; a field with history is archived, not deleted.
161
+ Whether editing values needs approval is a property of the category (`approvalScope`), not of the
162
+ policy engine — looking for it there is wasted time.
163
+
164
+ **Reports** come in ten types, and each answers with its own shape — a position report has no
165
+ turnover summary, an approval report counts requests rather than money — so `reports.run(type, …)`
166
+ is typed per report. They answer in their own error envelope with `REPORT_*` codes, which is
167
+ intentional. Sensitive field values are redacted per operation by the loader according to what the
168
+ caller may see: the value becomes `null` and its key appears in `redactedFields`. Access to an
169
+ operation and access to its sensitive labelling are different rights — the first comes with
170
+ `view`, the second only with `transfer`.
171
+
172
+ ## Authentication
173
+
174
+ One way in: a magic link by email. Signing up is possible only on an administrator's invitation, so
175
+ requesting a link for an uninvited address creates nothing.
176
+
177
+ The session travels in one of three ways, and the SDK supports all three because it stores none of
178
+ them — you supply headers, it hands two events back:
179
+
180
+ | Runtime | How |
181
+ | --- | --- |
182
+ | Browser | `credentials: 'include'`, cookie set by the server |
183
+ | React Native | your own cookie jar, returned from `getAuthHeaders` |
184
+ | Anywhere | `Authorization: Bearer <session-token>` |
185
+
186
+ In bearer mode `onTokenRotated` is **required**: the server extends a session through the
187
+ `set-auth-token` header, and a client that does not store the new token is signed out at the next
188
+ rotation.
189
+
190
+ There is no API key and no machine-to-machine flow today. An integration that needs to run without
191
+ a person signing in cannot authenticate yet.