@v3finance/core-sdk 0.2.0 → 0.2.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.
- package/CHANGELOG.md +44 -0
- package/README.md +5 -2
- package/dist/cjs/core/gated.d.ts +9 -3
- package/dist/cjs/core/gated.d.ts.map +1 -1
- package/dist/cjs/core/gated.js +9 -3
- package/dist/cjs/core/gated.js.map +1 -1
- package/dist/cjs/core/http.d.ts.map +1 -1
- package/dist/cjs/core/http.js +17 -3
- package/dist/cjs/core/http.js.map +1 -1
- package/dist/cjs/generated/operations.js +1 -1
- package/dist/cjs/generated/types.d.ts +17 -7
- package/dist/cjs/generated/types.d.ts.map +1 -1
- package/dist/cjs/resources/addresses.d.ts +4 -3
- package/dist/cjs/resources/addresses.d.ts.map +1 -1
- package/dist/cjs/resources/addresses.js +4 -3
- package/dist/cjs/resources/addresses.js.map +1 -1
- package/dist/cjs/resources/transfers.d.ts +18 -2
- package/dist/cjs/resources/transfers.d.ts.map +1 -1
- package/dist/cjs/resources/transfers.js +18 -2
- package/dist/cjs/resources/transfers.js.map +1 -1
- package/dist/cjs/resources/types.d.ts +0 -4
- package/dist/cjs/resources/types.d.ts.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/core/gated.d.ts +9 -3
- package/dist/esm/core/gated.d.ts.map +1 -1
- package/dist/esm/core/gated.js +9 -3
- package/dist/esm/core/gated.js.map +1 -1
- package/dist/esm/core/http.d.ts.map +1 -1
- package/dist/esm/core/http.js +17 -3
- package/dist/esm/core/http.js.map +1 -1
- package/dist/esm/generated/operations.js +1 -1
- package/dist/esm/generated/types.d.ts +17 -7
- package/dist/esm/generated/types.d.ts.map +1 -1
- package/dist/esm/resources/addresses.d.ts +4 -3
- package/dist/esm/resources/addresses.d.ts.map +1 -1
- package/dist/esm/resources/addresses.js +4 -3
- package/dist/esm/resources/addresses.js.map +1 -1
- package/dist/esm/resources/transfers.d.ts +18 -2
- package/dist/esm/resources/transfers.d.ts.map +1 -1
- package/dist/esm/resources/transfers.js +18 -2
- package/dist/esm/resources/transfers.js.map +1 -1
- package/dist/esm/resources/types.d.ts +0 -4
- package/dist/esm/resources/types.d.ts.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/concepts.md +6 -4
- package/docs/errors.md +25 -5
- package/package.json +1 -1
- package/src/core/gated.ts +9 -3
- package/src/core/http.ts +17 -3
- package/src/generated/operations.ts +1 -1
- package/src/generated/types.ts +18 -7
- package/src/resources/addresses.ts +4 -3
- package/src/resources/transfers.ts +18 -2
- package/src/resources/types.ts +0 -4
- package/src/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfers.js","sourceRoot":"","sources":["../../../src/resources/transfers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAc,MAAM,kBAAkB,CAAA;AAgBtD
|
|
1
|
+
{"version":3,"file":"transfers.js","sourceRoot":"","sources":["../../../src/resources/transfers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAc,MAAM,kBAAkB,CAAA;AAgBtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,iBAAiB;IACnB,KAAK,CAAY;IACjB,KAAK,CAAQ;IAEtB,YAAY,IAAgB,EAAE,SAAiB;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,kBAAkB,GAAG,CAAC,SAAS,CAAC,YAAY,CAAA;IAC3D,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CACR,KAAyB,EACzB,OAAO,GAAiD,EAAE;QAE1D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAA0B,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE;YACnF,IAAI,EAAE,KAAK;YACX,GAAG,CAAC,KAAK,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC;YACvF,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YACnE,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;SAC7E,CAAC,CAAA;QACF,OAAO,OAAO,CAAmD,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE;YACrF,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;IACJ,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,UAAkB;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IACzF,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,UAAkB,EAAE,KAAgB;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CACvB,MAAM,EACN,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,EAC5C,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAA;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,UAAkB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IAC5F,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,SAAS,CACP,UAAkB,EAClB,KAAK,GAA0B,EAAE;QAEjC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CACvB,MAAM,EACN,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,EAC5C,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAA;IACH,CAAC;CACF"}
|
|
@@ -34,10 +34,6 @@ import type { AddressDetail as GeneratedAddressDetail, AddressListItem, CreateTr
|
|
|
34
34
|
* the two must not be merged, because "no account" and "empty account"
|
|
35
35
|
* deserve different wording. Amounts are raw smallest units; put them through
|
|
36
36
|
* the helpers in `units.ts` rather than `Number`.
|
|
37
|
-
*
|
|
38
|
-
* One field is thinner than the type admits: `sharing` is optional because
|
|
39
|
-
* `PATCH /addresses/{id}` reuses this schema WITHOUT it (the document says so
|
|
40
|
-
* itself, on the property). Listings and the detail endpoint do send it.
|
|
41
37
|
*/
|
|
42
38
|
export type Address = AddressListItem;
|
|
43
39
|
/** A single address with its sharing and access details. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resources/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,aAAa,IAAI,sBAAsB,EACvC,eAAe,EACf,uBAAuB,EACvB,WAAW,IAAI,oBAAoB,EACnC,UAAU,EACX,MAAM,uBAAuB,CAAA;AAE9B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resources/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,aAAa,IAAI,sBAAsB,EACvC,eAAe,EACf,uBAAuB,EACvB,WAAW,IAAI,oBAAoB,EACnC,UAAU,EACX,MAAM,uBAAuB,CAAA;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,OAAO,GAAG,eAAe,CAAA;AAErC,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,sBAAsB,CAAA;AAElD,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,uBAAuB,CAAC,aAAa,CAAC,CAAA;AAEhE,+CAA+C;AAC/C,MAAM,MAAM,WAAW,GAAG,oBAAoB,CAAA;AAE9C,4DAA4D;AAC5D,MAAM,MAAM,UAAU,GAAG,UAAU,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB,mBAAmB,GACnB,SAAS,GACT,WAAW,GACX,WAAW,GACX,QAAQ,GACR,WAAW,GACX,UAAU,CAAA"}
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** SDK version, sent in the `user-agent` header. */
|
|
2
|
-
export declare const SDK_VERSION = "0.2.
|
|
2
|
+
export declare const SDK_VERSION = "0.2.1";
|
|
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
|
package/dist/esm/version.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** SDK version, sent in the `user-agent` header. */
|
|
2
|
-
export const SDK_VERSION = '0.2.
|
|
2
|
+
export const SDK_VERSION = '0.2.1';
|
|
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
|
package/docs/concepts.md
CHANGED
|
@@ -57,7 +57,7 @@ The API expresses this with the HTTP status, because **the response bodies are i
|
|
|
57
57
|
| --- | --- |
|
|
58
58
|
| 201 | applied — it happened |
|
|
59
59
|
| 202 | pending — funds are RESERVED, nothing has moved, there is a request to sign |
|
|
60
|
-
| 200 | replayed — the same idempotency key;
|
|
60
|
+
| 200 | replayed — the same idempotency key; the transfer already existed, in whatever state it is now |
|
|
61
61
|
|
|
62
62
|
The SDK turns that into `Gated`:
|
|
63
63
|
|
|
@@ -65,13 +65,15 @@ The SDK turns that into `Gated`:
|
|
|
65
65
|
switch (result.outcome) {
|
|
66
66
|
case 'applied': return toast('Transfer sent')
|
|
67
67
|
case 'pending': return toast('Awaiting signatures — funds are reserved')
|
|
68
|
-
case 'replayed': return toast(
|
|
68
|
+
case 'replayed': return toast(describeStatus(result.data.transaction.status))
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
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
|
|
74
|
-
|
|
73
|
+
server and only the answer was lost — not that it succeeded. The body is that transfer as it stands
|
|
74
|
+
now, so `replayed` must not be shown as "sent": `data.transaction.status` says whether it went out
|
|
75
|
+
(`confirmed`, or `submitted`), is waiting for signatures (`awaiting_approval`), is still executing
|
|
76
|
+
(`pending`) or did not go out (`failed`, `rejected`, `cancelled`).
|
|
75
77
|
|
|
76
78
|
**Only `transfers.send` can return it.** `idempotencyKey` exists on exactly one request body in the
|
|
77
79
|
whole API — the transfer — so it is the only place where a 200 means "this already happened". On
|
package/docs/errors.md
CHANGED
|
@@ -181,6 +181,16 @@ Two sets of uppercase strings travel inside **successful** bodies and will misle
|
|
|
181
181
|
|
|
182
182
|
Address creation that requires signatures is **not** an error: the server answers **202** with `{status:"awaiting_approval", approvalRequestId, approvalThreshold}`. Nothing is thrown. (`POLICY_REQUIRES_APPROVAL` exists in the server's type but is never sent — see [Declared but never returned](#declared-but-never-returned).)
|
|
183
183
|
|
|
184
|
+
Creating an address **into a folder** (`groupId`) can succeed without the folder, and that is not an error either. The right to the folder is checked before anything is created — a refusal there is `GROUP_CREATE_FORBIDDEN` above — but the address is put into the folder only after the signing service and monitoring, and the right is checked again at that moment. If that fails, the address still exists and the answer is still **201**, with `warnings: [{code, message}]` in the body:
|
|
185
|
+
|
|
186
|
+
| Code | Meaning |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `FOLDER_NOT_FOUND` | The folder was deleted. |
|
|
189
|
+
| `FOLDER_FORBIDDEN` | The address's creator may no longer add addresses to the folder. |
|
|
190
|
+
| `FOLDER_WRITE_FAILED` | Writing the membership failed. |
|
|
191
|
+
|
|
192
|
+
The same three arrive in `applyWarnings` of a **200** vote that applied an approved `create_address`: there the folder is checked when the request is applied, not when it was filed, and the request's history gets a `folder_skipped` event. Nothing is thrown in either case, so read the field. The address is fine; do not show it inside the folder.
|
|
193
|
+
|
|
184
194
|
## Grants and sharing
|
|
185
195
|
|
|
186
196
|
Address shares, folder shares, member-visibility grants. All grant `POST`s are idempotent: repeating a pair changes the rights, and a revoked grant is reactivated. There is no `GRANT_EXISTS` anywhere.
|
|
@@ -385,7 +395,17 @@ A risk threshold in a policy is expressed as a **level**, not a numeric score, a
|
|
|
385
395
|
|
|
386
396
|
`/api/auth/*` is the auth library's own namespace. It answers with a flat `{code, message}` that the API's envelope does not rewrite; `CoreError` reads it, so `code` is populated, but the values come from that library and are not part of this API's contract. Do not branch on a fixed list there — branch on `status`, and show `message`.
|
|
387
397
|
|
|
388
|
-
|
|
398
|
+
One of the library's codes is named here anyway, because the API itself refers to it: `FAILED_TO_GET_SESSION`. The library raises it when it fails to read or renew a session — a sign-out or a revocation deleting the session in the middle of a renewal is one way to get there. The API retries the session lookup once on it; on the API's own paths a failure that survives the retry arrives as `INTERNAL_ERROR`, and only `/api/auth/*` can hand you `FAILED_TO_GET_SESSION` itself, in the library's flat form. Treat it by `status`, like the rest of that namespace.
|
|
399
|
+
|
|
400
|
+
Three refusals the API puts **in front of** the library are its own: they arrive in the API's envelope, and their codes can be branched on.
|
|
401
|
+
|
|
402
|
+
| Code | Status | When | What to do |
|
|
403
|
+
|---|---|---|---|
|
|
404
|
+
| `ADMIN_CANNOT_APPROVE_WEB_LOGIN` | 403 | `POST /api/auth/device/approve` by a platform administrator. Approving a web login gives the holder of the code a new session of the approving user, and a platform administrator's session is not handed to the web version at all. | Terminal. Approve from an account that is not a platform administrator. |
|
|
405
|
+
| `WEB_SESSION_CANNOT_APPROVE` | 403 | The same approval sent with a session that was itself obtained through a web login. Only a session signed in another way may approve, so one leaked web token cannot keep minting new ones. | Approve from the mobile app. |
|
|
406
|
+
| `WEB_SESSION_CANNOT_MANAGE_SESSIONS` | 403 | A web-login session called `list-sessions`, `revoke-session`, `revoke-sessions` or `revoke-other-sessions`. Signing out (`sign-out`) is not affected. | Hide session management in a web client; it is done from the mobile app. |
|
|
407
|
+
|
|
408
|
+
Rate limiting outside `/api/auth/*` is not implemented in the API itself. A 429 there comes from the platform in front of it, usually without a JSON body, which means `code: "RATE_LIMITED"` and no `details`. Inside `/api/auth/*` a 429 can also come from the library's own limiter, as a bare `{message}`; and requesting a web-login code (`/api/auth/device/code`) is limited by the API itself, with a 429 in the API's envelope and `code: "RATE_LIMITED"`. Whichever answered, back off — for `X-Retry-After` seconds when the header is there — and retry.
|
|
389
409
|
|
|
390
410
|
## Easily confused
|
|
391
411
|
|
|
@@ -438,15 +458,15 @@ try {
|
|
|
438
458
|
} else throw e
|
|
439
459
|
}
|
|
440
460
|
|
|
441
|
-
if (result.outcome === '
|
|
442
|
-
else if (result.outcome === '
|
|
443
|
-
else
|
|
461
|
+
if (result.outcome === 'pending') showAwaitingApproval(result.data)
|
|
462
|
+
else if (result.outcome === 'applied') showSent(result.data)
|
|
463
|
+
else showByStatus(result.data.transaction.status) // replayed: NOT necessarily sent — see below
|
|
444
464
|
```
|
|
445
465
|
|
|
446
466
|
Rules that follow from how the server handles the key:
|
|
447
467
|
|
|
448
468
|
- **The key is reserved before any external call.** A replay is therefore recognised even if the first attempt died mid-broadcast. The server answers **200** and does not broadcast again; the SDK labels that `outcome: 'replayed'`.
|
|
449
|
-
-
|
|
469
|
+
- **`replayed` does not mean "sent".** It means "a transfer with this key already exists": the earlier attempt did reach the server and only its answer was lost. `data` is that transfer as it stands **now**, and what happened to it is in `data.transaction.status` — `confirmed` (or `submitted`): it left; `awaiting_approval`: it is waiting for signatures, funds reserved; `pending`: it is still executing; `failed`, `rejected` or `cancelled`: it did not leave. Tell the person that, not "sent".
|
|
450
470
|
- **A new key after a `failed` or `rejected` transfer.** The old key is bound to that row, and reusing it returns the dead transfer instead of sending a new one.
|
|
451
471
|
- **No key sent, and a timeout?** Do **not** retry. Fetch the transfer list or the operation history first and establish whether the transfer exists. Make the SDK's own callers always pass a key so this situation cannot arise.
|
|
452
472
|
- **`network` and `aborted` carry the same uncertainty as `timeout`** on money paths. The request may have been received and executed; only the response was lost. Treat all three identically: same key, or verify first.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v3finance/core-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The official TypeScript SDK for the V3 Finance Core API — custodial addresses, transfers under policy control, approvals, shared access, labelling and reports",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/core/gated.ts
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* frozen.
|
|
16
16
|
*
|
|
17
17
|
* `replayed` is kept separate from `applied` on purpose. A replay means the
|
|
18
|
-
* previous attempt DID reach the server and only the answer was lost
|
|
19
|
-
*
|
|
18
|
+
* previous attempt DID reach the server and only the answer was lost — not
|
|
19
|
+
* that it succeeded. The body is that operation as it stands now: a replayed
|
|
20
|
+
* transfer may have gone out, be waiting for signatures, be still executing
|
|
21
|
+
* or have failed, and only its own status says which.
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
export type GatedOutcome = 'applied' | 'pending' | 'replayed'
|
|
@@ -74,7 +76,11 @@ export function toGated<TApplied, TPending>(
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Narrowing: `applied` or `replayed`. For `applied` the operation ran now.
|
|
81
|
+
* For `replayed` it only existed already — whether money moved is in the
|
|
82
|
+
* body's own status, not in this check.
|
|
83
|
+
*/
|
|
78
84
|
export function isApplied<A, P>(
|
|
79
85
|
r: Gated<A, P>,
|
|
80
86
|
): r is Extract<Gated<A, P>, { outcome: 'applied' | 'replayed' }> {
|
package/src/core/http.ts
CHANGED
|
@@ -157,13 +157,27 @@ export class HttpClient {
|
|
|
157
157
|
constructor(options: ClientOptions & { baseUrl: string }) {
|
|
158
158
|
this.baseUrl = options.baseUrl.replace(/\/+$/, '')
|
|
159
159
|
this.#options = options
|
|
160
|
-
const
|
|
161
|
-
if (typeof
|
|
160
|
+
const custom = options.fetch
|
|
161
|
+
if (typeof (custom ?? globalThis.fetch) !== 'function') {
|
|
162
162
|
throw new Error(
|
|
163
163
|
'@v3finance/core-sdk: fetch is unavailable in this runtime — pass one to the client options.',
|
|
164
164
|
)
|
|
165
165
|
}
|
|
166
|
-
this.#fetch
|
|
166
|
+
// NEVER store the function and call it as `this.#fetch(…)`. A private
|
|
167
|
+
// field call passes the client as the receiver, and a browser's `fetch`
|
|
168
|
+
// checks its receiver: anything but the window is
|
|
169
|
+
// `TypeError: Illegal invocation`, thrown before a single byte leaves.
|
|
170
|
+
// Node and React Native do not check, which is how every request from a
|
|
171
|
+
// web page failed while the whole test suite passed — and since a
|
|
172
|
+
// `TypeError` out of `fetch` is what a dropped connection looks like, the
|
|
173
|
+
// failure surfaced as a "network error" on a perfectly good network.
|
|
174
|
+
//
|
|
175
|
+
// The wrapper calls with no receiver at all. Without a custom fetch the
|
|
176
|
+
// global one is looked up per call rather than captured here, so whatever
|
|
177
|
+
// patches it later (instrumentation, a mock server) is still honoured.
|
|
178
|
+
this.#fetch = custom
|
|
179
|
+
? (input, init) => custom(input, init)
|
|
180
|
+
: (input, init) => globalThis.fetch(input, init)
|
|
167
181
|
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
168
182
|
}
|
|
169
183
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// the API does not slip in unclassified.
|
|
10
10
|
//
|
|
11
11
|
// Operations: 171 · admin: 40 · both: 1 · member: 127 · service: 3
|
|
12
|
-
// Spec sha256:
|
|
12
|
+
// Spec sha256: 017bae30075b232ab4efdcfb7785335020be3060b46e3569b1cd887fcf037597
|
|
13
13
|
|
|
14
14
|
export type Audience = 'member' | 'admin' | 'service' | 'both' | 'unknown'
|
|
15
15
|
|
package/src/generated/types.ts
CHANGED
|
@@ -250,8 +250,8 @@ export interface AddressDetail {
|
|
|
250
250
|
tokenId: string | null
|
|
251
251
|
/** format: date-time */
|
|
252
252
|
createdAt: string
|
|
253
|
-
/** Сводка совместного доступа. Приходит в
|
|
254
|
-
sharing
|
|
253
|
+
/** Сводка совместного доступа. Приходит всегда: в списке, ответе PATCH и детали адреса */
|
|
254
|
+
sharing: SharingSummary
|
|
255
255
|
/** Код токена адреса (из segmentsJson + asset route) */
|
|
256
256
|
asset: string | null
|
|
257
257
|
assetName: string | null
|
|
@@ -491,8 +491,8 @@ export interface AddressListItem {
|
|
|
491
491
|
assetSymbol: string | null
|
|
492
492
|
/** Баланс в decimal (по token.decimals). NULL — ledger-счёта ещё нет */
|
|
493
493
|
balanceDecimal: string | null
|
|
494
|
-
/** Сводка совместного доступа. Приходит в
|
|
495
|
-
sharing
|
|
494
|
+
/** Сводка совместного доступа. Приходит всегда: в списке, ответе PATCH и детали адреса */
|
|
495
|
+
sharing: SharingSummary
|
|
496
496
|
/** Владелец адреса (создатель) с UI-метаданными */
|
|
497
497
|
owner: MemberCard | null
|
|
498
498
|
access: AddressAccess
|
|
@@ -791,11 +791,11 @@ export interface ApprovalRequestDetail {
|
|
|
791
791
|
|
|
792
792
|
export interface ApprovalRequestEvent {
|
|
793
793
|
id: string
|
|
794
|
-
/** created|voted|approved|rejected|cancelled|applied|apply_failed|stale */
|
|
794
|
+
/** created|voted|approved|rejected|cancelled|applied|apply_failed|stale|folder_skipped. folder_skipped идёт ПОСЛЕ applied: адрес по заявке create_address создан, но в папку, выбранную при подаче, не положен (папку удалили или у инициатора отозвали право) — причина в comment */
|
|
795
795
|
type: string
|
|
796
796
|
actorId: string | null
|
|
797
797
|
decision: string | null
|
|
798
|
-
/**
|
|
798
|
+
/** У voted — пояснение ПОДПИСАНТА; у stale, apply_failed и folder_skipped — системное пояснение для человека. У события applied всегда null — id созданного объекта лежит в resultId, чтобы клиент не напечатал его как комментарий */
|
|
799
799
|
comment: string | null
|
|
800
800
|
/** Только у applied: id созданного/изменённого объекта */
|
|
801
801
|
resultId: string | null
|
|
@@ -1328,7 +1328,7 @@ export interface CreateAddressBody {
|
|
|
1328
1328
|
* length: 1..16
|
|
1329
1329
|
*/
|
|
1330
1330
|
emoji?: string
|
|
1331
|
-
/** Создать адрес В группу адресов (нужно право create_address на группе). Новый адрес — member группы, доступен её
|
|
1331
|
+
/** Создать адрес В группу адресов (нужно право create_address на группе). Новый адрес — member группы, доступен её участникам. Если создание ушло на подпись (202), папка сохраняется в заявке, а право на неё перепроверяется при применении: отозвали — адрес создаётся без папки, с оговоркой applyWarnings в ответе голосования. При прямом создании (201) то же — оговорка `warnings` в теле ответа */
|
|
1332
1332
|
groupId?: string
|
|
1333
1333
|
}
|
|
1334
1334
|
|
|
@@ -1354,6 +1354,15 @@ export interface CreateAddressResult {
|
|
|
1354
1354
|
label: string | null
|
|
1355
1355
|
description: string | null
|
|
1356
1356
|
}
|
|
1357
|
+
/** Создано, но не всё: адрес есть, а в папку из `groupId` не положен. Право на папку роут проверяет до создания (иначе 403), а запись членства — уже после Wallet Service и мониторинга; право могли отозвать в этом окне или запись могла не пройти. Есть только когда непусто; не показывать адрес в папке, если оговорка пришла */
|
|
1358
|
+
warnings?: FolderSkipWarning[]
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
export interface FolderSkipWarning {
|
|
1362
|
+
/** FOLDER_NOT_FOUND — папку удалили; FOLDER_FORBIDDEN — у создателя адреса больше нет права заводить в неё адреса; FOLDER_WRITE_FAILED — сбой записи */
|
|
1363
|
+
code: "FOLDER_NOT_FOUND" | "FOLDER_FORBIDDEN" | "FOLDER_WRITE_FAILED"
|
|
1364
|
+
/** Готовая фраза для человека */
|
|
1365
|
+
message: string
|
|
1357
1366
|
}
|
|
1358
1367
|
|
|
1359
1368
|
export interface CreateCompositeBody {
|
|
@@ -3203,6 +3212,8 @@ export interface RequestVoteResult {
|
|
|
3203
3212
|
resolvedResultId: string | null
|
|
3204
3213
|
/** Только для композитных заявок: прогресс по шагам */
|
|
3205
3214
|
stepsProgress?: StepProgress[]
|
|
3215
|
+
/** Применено, но не всё: адрес по заявке create_address создан (resolvedResultId), а в папку, выбранную при подаче, не положен. Право на папку проверяется при ПРИМЕНЕНИИ, а не при подаче. Есть только когда непусто; в истории заявки — событие folder_skipped */
|
|
3216
|
+
applyWarnings?: FolderSkipWarning[]
|
|
3206
3217
|
}
|
|
3207
3218
|
|
|
3208
3219
|
export interface StepProgress {
|
|
@@ -73,9 +73,10 @@ export class AddressesResource {
|
|
|
73
73
|
/**
|
|
74
74
|
* Metadata only: this touches neither money nor the derivation path.
|
|
75
75
|
*
|
|
76
|
-
* Answers with the
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* Answers with the same row as the listing — `owner`, `access` and
|
|
77
|
+
* `sharing` included — so it can replace the address in a list without a
|
|
78
|
+
* refetch. Recent operations and `shares`, which `get()` carries, are not
|
|
79
|
+
* in it.
|
|
79
80
|
*/
|
|
80
81
|
update(addressId: string, input: PatchAddressBody): Promise<Address> {
|
|
81
82
|
return this.#http.request<Address>('PATCH', `${this.#base}/${seg(addressId)}`, { body: input })
|
|
@@ -23,10 +23,20 @@ export interface VoteInput {
|
|
|
23
23
|
*
|
|
24
24
|
* applied (201) — executed, the money left;
|
|
25
25
|
* pending (202) — awaiting signatures, funds RESERVED but not sent;
|
|
26
|
-
* replayed (200) — a
|
|
26
|
+
* replayed (200) — a transfer with this `idempotencyKey` already existed,
|
|
27
|
+
* and this is that transfer as it stands NOW.
|
|
27
28
|
*
|
|
28
29
|
* They cannot be told apart by the body. Showing "sent" on `pending` tells
|
|
29
30
|
* the person money has gone when it is frozen.
|
|
31
|
+
*
|
|
32
|
+
* `replayed` does not mean "sent" either. It says only that the request was
|
|
33
|
+
* seen before; what became of it is in `data.transaction.status`:
|
|
34
|
+
*
|
|
35
|
+
* confirmed, submitted — it left;
|
|
36
|
+
* awaiting_approval — waiting for signatures, funds reserved;
|
|
37
|
+
* pending — still executing;
|
|
38
|
+
* failed, rejected,
|
|
39
|
+
* cancelled — it did not leave.
|
|
30
40
|
*/
|
|
31
41
|
export class TransfersResource {
|
|
32
42
|
readonly #http: HttpClient
|
|
@@ -37,7 +47,13 @@ export class TransfersResource {
|
|
|
37
47
|
this.#base = `/api/v1/vaults/${seg(vaultCode)}/transfers`
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Sends a transfer. The timeout is long: the path calls the wallet service.
|
|
52
|
+
*
|
|
53
|
+
* On `outcome: 'replayed'` read `data.transaction.status` before telling
|
|
54
|
+
* anyone anything — a replay of an attempt that failed, or that is still
|
|
55
|
+
* waiting for signatures, comes back the same way as one that went out.
|
|
56
|
+
*/
|
|
41
57
|
async send(
|
|
42
58
|
input: CreateTransferBody,
|
|
43
59
|
options: { signal?: AbortSignal; timeoutMs?: number } = {},
|
package/src/resources/types.ts
CHANGED
|
@@ -42,10 +42,6 @@ import type {
|
|
|
42
42
|
* the two must not be merged, because "no account" and "empty account"
|
|
43
43
|
* deserve different wording. Amounts are raw smallest units; put them through
|
|
44
44
|
* the helpers in `units.ts` rather than `Number`.
|
|
45
|
-
*
|
|
46
|
-
* One field is thinner than the type admits: `sharing` is optional because
|
|
47
|
-
* `PATCH /addresses/{id}` reuses this schema WITHOUT it (the document says so
|
|
48
|
-
* itself, on the property). Listings and the detail endpoint do send it.
|
|
49
45
|
*/
|
|
50
46
|
export type Address = AddressListItem
|
|
51
47
|
|
package/src/version.ts
CHANGED