@calimero-network/mero-js 26.10.0 → 27.0.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 (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12767
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12767
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -686
  62. package/dist/admin-api/admin-client.js +0 -1225
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2041
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. package/dist/warrant/warrant.js +0 -202
package/README.md CHANGED
@@ -1,21 +1,20 @@
1
- # Mero.js — Pure JavaScript SDK for Calimero
1
+ # Mero.js — the JavaScript client for Calimero 1.0
2
2
 
3
- A lightweight, universal JavaScript/TypeScript SDK for [Calimero](https://calimero-network.github.io/core/).
4
- Authenticate to a node, drive the admin API, execute WASM methods over JSON-RPC,
5
- and stream real-time events — from the browser, Node, Deno, Bun, or edge
6
- runtimes. Zero runtime dependencies; built entirely on Web Platform APIs
7
- (`fetch`, `WebSocket`, `AbortController`).
3
+ The JavaScript/TypeScript client for [Calimero](https://calimero-network.github.io/core/)
4
+ 1.0, the confidential cloud. `CalimeroClient` verifies a node's TDX attestation,
5
+ seals every request to it end to end (Noise NK, `calimero/sealed-http/v2`), signs
6
+ people in with WebAuthn passkeys and machines with API keys, and calls apps that
7
+ each run on their own replicated log. It runs in the browser and Node 18+, and is
8
+ byte-compatible with core's `calimero-sealed` and `calimero-client`.
8
9
 
9
- ## Documentation
10
+ > **Version 27 is the Calimero 1.0 client only.** The v1 SDK for merod 0.x nodes
11
+ > (`MeroJs`, the admin and auth API clients, JSON-RPC, contexts and namespaces)
12
+ > is in 26.x. See [Migrating from v1](https://calimero-network.github.io/mero-js/reference/migrating-from-v1/).
10
13
 
11
- Full documentation is published at
12
- **<https://calimero-network.github.io/mero-js/>** — quickstart, guides, and the
13
- complete API reference (`MeroJs`, admin API, auth API, RPC, events, cloud,
14
- capabilities, and the error model). The source lives under [`docs/`](docs/).
14
+ ## Documentation
15
15
 
16
- > **Not React?** This is a framework-agnostic SDK. React bindings live in the
17
- > separate [`@calimero-network/mero-react`](https://github.com/calimero-network/mero-react)
18
- > package.
16
+ **<https://calimero-network.github.io/mero-js/>**: quickstart, guides, and the
17
+ reference for every method and the route it calls. The source is under [`docs/`](docs/).
19
18
 
20
19
  ## Installation
21
20
 
@@ -23,309 +22,95 @@ capabilities, and the error model). The source lives under [`docs/`](docs/).
23
22
  npm install @calimero-network/mero-js
24
23
  ```
25
24
 
26
- Requires Node.js 18+ (native `fetch`) or any modern browser. The package ships
27
- ESM, CommonJS, and a minified browser build, is side-effect-free (tree-shakeable),
28
- and includes its own TypeScript declarations.
25
+ The package ships ESM, CommonJS and a minified browser build with its own
26
+ TypeScript declarations. Its one dependency, `@phala/dcap-qvl`, verifies TDX
27
+ quotes and is loaded on first use.
29
28
 
30
29
  ## Quick start
31
30
 
32
- ```typescript
33
- import { createMeroJs, HTTPError, RpcError } from '@calimero-network/mero-js';
34
-
35
- const sdk = createMeroJs({
36
- baseUrl: 'http://localhost:2528',
37
- // timeoutMs defaults to 10000; pass a tokenStore to persist auth
38
- });
39
-
40
- // Authenticate (username/password against the node's auth service)
41
- await sdk.authenticate({ username: 'dev', password: 'dev' });
42
-
43
- // Admin API — list this node's contexts
44
- const { contexts } = await sdk.admin.getContexts(); // ContextWithGroup[]
45
- console.log(`found ${contexts.length} contexts`);
46
-
47
- // Execute a context method over JSON-RPC
48
- try {
49
- const value = await sdk.rpc.execute<string>({
50
- contextId: contexts[0].id,
51
- method: 'get_value',
52
- argsJson: { key: 'greeting' },
53
- });
54
- console.log(value);
55
- } catch (err) {
56
- if (err instanceof RpcError) console.error(`contract error ${err.code}: ${err.message}`);
57
- else if (err instanceof HTTPError) console.error(`HTTP ${err.status}: ${err.statusText}`);
58
- else throw err;
59
- }
60
-
61
- // Stream real-time context events (SSE)
62
- sdk.events.on('event', (e) => console.log(e.contextId, e.type, e.data));
63
- await sdk.events.connect();
64
- await sdk.events.subscribe([contexts[0].id]);
65
-
66
- // Tear down open connections when done
67
- sdk.close();
68
- ```
69
-
70
- `sdk.auth`, `sdk.admin`, `sdk.rpc`, `sdk.events` (SSE) and `sdk.ws` (experimental
71
- WebSocket) are all reachable from the one `MeroJs` instance. See the
72
- [quickstart](https://calimero-network.github.io/mero-js/get-started/quickstart/)
73
- and [authentication guide](https://calimero-network.github.io/mero-js/get-started/authentication/)
74
- for token persistence and the refresh lifecycle.
75
-
76
- ## Error model
77
-
78
- Every failure surfaces as a typed error:
79
-
80
- - **`HTTPError`** — a non-2xx response. Network failures, timeouts, and
81
- cancellations are also wrapped as `HTTPError` with **`status: 0`** (the
82
- underlying `AbortError`/`TimeoutError` is not propagated).
83
- - **`AuthRevokedError`** (extends `HTTPError`) — the refresh token was reused or
84
- revoked; the session is over. The SDK has already cleared its tokens.
85
- - **`RpcError`** — the WASM contract returned a JSON-RPC error (`code`, `message`,
86
- optional `type`/`data`).
87
-
88
- `classifyError(err)` turns any of these into `{ kind, retryable, message }`.
89
- `kind` is read off the status (`'conflict'` for a 409, `'unavailable'` for a
90
- 503, `'unreachable'` for status 0, and so on). `retryable` says whether the same
91
- request could work later. `message` is the node's own words. To follow an
92
- invitation, use `sdk.admin.redeemInvitation`: a join that failed but landed
93
- comes back as `already-member` rather than as an error.
94
-
95
- `401`s are handled internally: the SDK refreshes the token and retries once, so
96
- you only see a `401` if the refresh itself fails. Full details in the
97
- [error model reference](https://calimero-network.github.io/mero-js/reference/error-model/).
98
-
99
- ## Writing without a node
100
-
101
- The quickstart above assumes you have a node and a credential on it. A browser
102
- tab, a phone, or an agent has neither — the runtime cannot compile the app's WASM
103
- against materialized state, and never received the scope key that seals the
104
- deltas. So there is a second connection shape: sign in to **Calimero Cloud**, and
105
- write through an attested TEE **relay** using
106
- [delegated execution](https://calimero-network.github.io/core/protocol/delegated-authorship/).
107
-
108
- You sign a *warrant* locally — your consent for one specific intent, once — the
109
- relay runs the method as **your** principal, and the resulting change is
110
- attributed to your account and device. Your signing key never leaves the
111
- process; only signatures do.
112
-
113
- ```typescript
114
- import { connectCloud } from '@calimero-network/mero-js';
115
-
116
- const connection = await connectCloud({
117
- googleIdToken, // or a stored sessionToken
118
- authorAccount, // your account, hex
119
- authorProof, // your AccountProof<DeviceCert>, hex borsh
120
- deviceSecret, // your device's ed25519 seed, hex — never transmitted
121
- });
31
+ ```ts
32
+ import { CalimeroClient, browserPasskey } from '@calimero-network/mero-js';
122
33
 
123
- const { rootHash, returns } = await connection.execute(contextId, 'set', {
124
- key: 'greeting',
125
- value: 'hello',
34
+ const client = new CalimeroClient({
35
+ baseUrl: 'https://eu1.cloud.calimero.network',
36
+ // Trust the signed mero-tee release the node names, from this one up.
37
+ policy: { release: { minReleaseVersion: '2.3.109' } },
126
38
  });
127
- ```
128
-
129
- `connectCloud` replaces the **node-URL prompt**, not the authentication: it asks
130
- the cloud which namespaces the account owns and which relay serves them, then
131
- builds the relay client. `sdk.cloud` exposes the same cloud API from an existing
132
- `MeroJs` instance for apps that have both a node and a cloud account, and
133
- `RelayClient` is available directly for a relay you were told about out of band.
134
-
135
- One precondition is not yours to satisfy: the node must relay. A fleet node
136
- admitted as a `RelayTee` (the namespace's TEE admission policy set with
137
- `mode: 'relay'`) relays by role; a `ReadOnlyTee` is a replica and never relays;
138
- and a node too old to report a role needs an admin of the namespace to grant it
139
- `CAN_AUTHOR_ON_BEHALF`. `connectCloud` picks a relay on the cloud's `canExecute`,
140
- which folds that rule in, and otherwise throws saying which of those is missing —
141
- naming the account that needs the grant when that is the fix.
142
-
143
- A keyholder can also **create** a context through a relay:
144
- `relay.createContext({ groupId, applicationId, initArgs })` signs a creation
145
- warrant (checked against *your* `CAN_CREATE_CONTEXT`, not the relay's) and
146
- returns the new `contextId`.
147
-
148
- And it can **govern** through one: `relay.govern({ groupId, op })` signs a
149
- governance warrant over one op, such as `memberAddedOp(account, 'Member')` or
150
- `groupCreatedOp({ ... })`, and every peer applies it under *your* rights.
151
-
152
- See the [cloud client](https://calimero-network.github.io/mero-js/reference/cloud/),
153
- [relay client](https://calimero-network.github.io/mero-js/reference/relay/) and
154
- [connectCloud](https://calimero-network.github.io/mero-js/reference/connect-cloud/)
155
- references — including why a warrant nonce must never restart.
156
-
157
- ## Account (delegated) layer
158
-
159
- `connectCloud` is the shape for an account that already belongs somewhere. The
160
- account layer is everything around it for an account that owns nothing yet: it
161
- has a device the wallet certified and no relay, and it has to found a namespace
162
- or be invited into one before it can write. It is framework-free — plain TS over
163
- `sessionStorage`/`localStorage` — and `@calimero-network/mero-react` re-exports
164
- every name below unchanged (its hooks are built on it), so an app may import
165
- them from either package.
166
-
167
- - **Session** — `DelegatedCredential` (what the wallet certified: account,
168
- certificate, device secret) and `DelegatedAccountSession` (that plus the relay
169
- to write through, `null` until the account is a member of something).
170
- `readDelegatedSession` / `saveDelegatedSession` / `clearDelegatedSession`,
171
- `readDelegatedCredential` / `saveDelegatedCredential` /
172
- `clearDelegatedCredential`; `readRelayMap`, `rememberRelay`,
173
- `relayForContext`, `knownRelays`, `listDelegatedContexts`,
174
- `listDelegatedNamespaces`, `carryExecutorAccount`.
175
- - **The relay's node key** — `learnRelayNodeKey` (attests the relay: its signed
176
- mero-tee release from the cloud mirror, Intel's chain, every register; retries
177
- the transport, refuses a bad quote), `attemptRelayNodeKey`,
178
- `resolveRelayNodeKey`, `pinRelayNodeKey`, `readPinnedRelayNodeKey`,
179
- `RELAY_NODE_KEY_RETRY_MS`. `@phala/dcap-qvl` is loaded on the first real quote
180
- and is external to the bundles: a consumer that never meets a real relay never
181
- downloads the verifier.
182
- - **Client** — `buildDelegatedClient(session, contextId)`: a `MeroClient` over
183
- the relay transport, or `null` while there is no relay (a logged-in state, not
184
- a broken one). `forgetMethodKinds`, `persistedNonces`, `markContextNonceSpent`.
185
- - **Admin, one shape on both transports** — `createAccountAdmin({ session,
186
- read, app })` returns an `AdminApiClient` whose writes are governance and
187
- creation warrants signed by the account and whose reads go through the relay;
188
- `createNodeAdmin({ admin, app })` is the same `AdminApiClient` for a node, with
189
- `createNamespace` installing the package first. Refusals: `NoRelayError`,
190
- `NotForAccountError`, `InvitationNotClaimableError`.
191
- - **Founding and creating** — `foundDelegatedNamespace`,
192
- `createDelegatedContext`, `createDelegatedPrivateContext`,
193
- `delegatedGovernance`, `governGroup`, `governRoot`, `namespaceOfGroup`,
194
- `rememberGroupNamespace`; `HA_REFUSAL_MESSAGES`,
195
- `HA_ACCOUNT_NOT_LINKED_MESSAGE` for the cloud's HA answers.
196
- - **Which application** — `applicationIdForBundle`, `selectLatestBundle`,
197
- `fetchRegistryBundles`, `resolveApplicationIdFromRegistry`,
198
- `latestPublishedVersion` (the registry's answer for a package).
199
- - **Joining from an invitation** — `resolveRelayFromInvitation` (which relay
200
- admits, from the invitation's signed admitters and the cloud), `joinWithNode`,
201
- `bootstrapFromInvitation` (both steps, as a result rather than a throw),
202
- `joinAsAccount` (the same, switching the session onto the relay that admitted),
203
- `normaliseAccount`.
204
-
205
- ```typescript
206
- import {
207
- beginDeviceEnrolment, readEnrolmentCallback, completeDeviceEnrolment,
208
- saveDelegatedSession, learnRelayNodeKey, pinRelayNodeKey, buildDelegatedClient,
209
- createAccountAdmin, joinAsAccount, resolveApplicationIdFromRegistry, type DelegatedAccountSession,
210
- } from '@calimero-network/mero-js';
211
-
212
- // 1. Enrol: the wallet certifies a device key this app generated and keeps.
213
- // (first visit) beginDeviceEnrolment({ walletUrl, devicePublicKey, kemPublicKey, returnTo });
214
- // (on return)
215
- const callback = readEnrolmentCallback(); // null on an ordinary load
216
- const enrolled = await completeDeviceEnrolment({ ...callback, devicePublicKey, kemPublicKey });
217
- let session: DelegatedAccountSession = {
218
- account: enrolled.account, credential: enrolled.credential, deviceSecret, relayUrl: null,
219
- };
220
- saveDelegatedSession(session);
221
-
222
- // 2. Found a namespace, or join one. A new account has no relay; either gives it one.
223
- const registryUrl = 'https://apps.calimero.network';
224
- const app = await resolveApplicationIdFromRegistry(registryUrl, 'com.example.kv');
225
- const admin = createAccountAdmin(
226
- { session, read: null, app: { packageName: 'com.example.kv', packageVersion: app.version, registryUrl } },
227
- { join: (namespaceId, invitation) => joinAsAccount(session, namespaceId, invitation, {
228
- onJoined: (next) => { session = next; saveDelegatedSession(next); },
229
- }) },
230
- );
231
- const { namespaceId } = await admin.createNamespace({ applicationId: app.applicationId, name: 'my-notes' });
232
- // or: await admin.joinNamespace(namespaceId, { invitation });
233
39
 
234
- // 3. Attest the relay before trusting a session on it, then write through it.
235
- const nodeKey = await learnRelayNodeKey(session.relayUrl!);
236
- if (nodeKey) pinRelayNodeKey(session.relayUrl!, nodeKey);
237
- const client = buildDelegatedClient(session, null)!;
238
- const { contextId } = await admin.createContext({ groupId: namespaceId, applicationId: app.applicationId, serviceName: 'kv' });
239
- await client.execute(contextId, 'set', { key: 'greeting', value: 'hello' });
40
+ await client.signUp(browserPasskey(), 'alice'); // or signIn(browserPasskey())
41
+ await client.installPackage('com.example.kv', '1.0.0'); // signed .mpk from the registry
42
+ const app = await client.createApp('kv', { package: 'com.example.kv', version: '1.0.0' }, { confirmMethods: ['clear'] });
240
43
 
241
- // 4. Invite another account; they claim it with `admin.joinNamespace` on their side.
242
- const { invitation } = await admin.createNamespaceInvitation(namespaceId);
44
+ await client.call(app, 'set', { key: 'a', value: '1' });
45
+ const { result } = await client.view(app, 'get', { key: 'a' });
46
+ await client.callConfirmed(app, 'clear', {}, browserPasskey()); // passkey consent
47
+ for await (const event of client.events(app)) console.log(event);
48
+ await client.upgradeApp(app, { version: '1.1.0' }, browserPasskey()); // the owner approves
243
49
  ```
244
50
 
245
- `createNamespace` founds through the relay and enables HA on the cloud for the
246
- account; `createContext` signs a creation warrant naming the bundle service it
247
- is for; `createNamespaceInvitation` names the relays that admit, so an invitee
248
- with no node of their own can be admitted by one of them. The `read` client is
249
- `null` until the relay's node key is attested, which is why the session is
250
- built in that order.
51
+ A development cluster (`merod local`, `merod run --dev`) sends mock quotes,
52
+ which pass only with `policy: { allowMock: true }`. Nothing is read from the
53
+ environment.
251
54
 
252
- ## Private agents
55
+ ## What the client checks
253
56
 
254
- A private agent runs in a TDX VM (mero-tee's agent image) that several owners' agents share, each named by an agent id. Its gate proves which image runs and takes each agent's secrets only sealed to it, from its owner. These functions are that owner's side; whoever relays the requests (mdma included) sees only quotes and ciphertext.
57
+ Before it sends anything to a node, as core's Rust client does (`Measurements::Release`):
255
58
 
256
- ```ts
257
- import { verify as dcapVerify, getCollateral } from '@phala/dcap-qvl';
258
- import {
259
- agentQuoteVerifier, attestAgent, generateOwnerKey, provisionAgent, releaseAgent, verifySignedAgentPolicy,
260
- } from '@calimero-network/mero-js';
261
-
262
- // The mero-kms release's agent allowlist, checked against its Sigstore signature.
263
- const policy = await verifySignedAgentPolicy(policyJson, bundleJson, { profile: 'locked-read-only' });
264
- const verifier = agentQuoteVerifier(policy, { dcapVerify, fetchCollateral: (quote) => getCollateral(PCCS_URL, quote) });
265
-
266
- const owner = await generateOwnerKey(); // keep owner.secret: only it can change or release the agent
267
- const agent = await attestAgent({ gateUrl, agentId: 'a-1f2e', verifier });
268
- await provisionAgent({ gateUrl, agent, ownerSecret: owner.secret, verifier, secrets: { NEAR_AI_API_KEY: key } });
269
- // later: await releaseAgent({ gateUrl, agent: await attestAgent({ gateUrl, agentId, verifier }), ownerSecret, verifier });
270
- ```
59
+ 1. The node names the mero-tee release it runs. A release below `minReleaseVersion` is refused before anything is fetched.
60
+ 2. The release's `published-mrtds.json` is fetched and its Sigstore signature is checked against the mero-tee node release workflow.
61
+ By default it comes from the `cloud.calimero.network` mirror; `releaseSource: 'github'` reads the GitHub release instead (Node only: GitHub sends no CORS headers).
62
+ 3. The quote is checked against Intel's collateral with `@phala/dcap-qvl` (Intel's PCS unless you give `fetchCollateral`): the signature chain and the TCB status.
63
+ 4. A debug TD is refused.
64
+ 5. The registers must be the release's `locked-read-only` image; a debug profile is trusted only with `allowDebugProfile`.
65
+ 6. The quote's report data binds the client's nonce and the transport key the sealed channel opens to.
271
66
 
272
- - `attestAgent` returns the agent's keys only once its quote verifies: genuine, from an image in the policy, not a debug TD, for your nonce, and committing to the agent id asked about and to the keys the gate names.
273
- - `provisionAgent` refuses an agent another key claimed before sending anything. The first bundle for an unclaimed agent claims it for your key, and a fresh quote must then name your key as owner.
274
- - Secrets are sealed with RFC 9180 HPKE in Auth mode (X25519, HKDF-SHA256, ChaCha20-Poly1305) to the gate's in-memory key, for that agent id only.
275
- - `releaseAgent` stops the agent and erases its slot; a fresh quote must name no owner.
67
+ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
276
68
 
277
- ## Lower-level HTTP client
69
+ ## The API
278
70
 
279
- If you need raw HTTP against a node (without the `MeroJs` facade), the SDK
280
- exports composable clients — `createBrowserHttpClient`, `createNodeHttpClient`,
281
- `createUniversalHttpClient`, `createHttpClient` — plus `withRetry`,
282
- `combineSignals`, and `createTimeoutSignal`:
71
+ - **Credentials**: `signUp`, `signIn`, `refresh`, `signOut`, `signOutEverywhere`, `useSession`, `useApiKey`, `me`, `usage`, `addPasskey`, `labelPasskey`, `removePasskey`, `createApiKey`, `listApiKeys`, `revokeApiKey`.
72
+ - **Packages**: `installPackage`, `uploadPackage`, `packages`, `package`, `uninstallPackage`; `uploadBundle` (raw WASM, development clusters only).
73
+ - **Apps**: `createApp`, `listApps`, `getApp`, `deleteApp`, `setAccess`, `setAppMetadata`, `appStorage`, `appAbi`, `upgradeApp`, `transferApp`.
74
+ - **Calls**: `call`, `callConfirmed`, `view`, `events`. A call always carries an idempotency key, and is sent again with it while the node answers `503`.
75
+ - **Blobs and presence**: `uploadBlob`, `downloadBlob`, `listBlobs`, `blobInfo`, `deleteBlob`; `publishPresence`, `presence`, `presenceStream`.
76
+ - **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite`.
77
+ - **Owner decisions** (passkey approval): `upgradeOrg`, `upgradeRun`, `retryUpgradeRun`, `approveRelease`, `approveOutbound`.
78
+ - **Any route**: `get`, `post`, `put`, `delete`; `postConfirmed` and `deleteConfirmed` answer a `412 CONFIRM_REQUIRED` with a passkey.
283
79
 
284
- ```typescript
285
- import { createBrowserHttpClient } from '@calimero-network/mero-js';
80
+ Refusals are typed (`LoginRequired`, `NotFound`, `ConfirmRequired`, `ConfirmCancelled`,
81
+ `ArgsTooLarge`, `IdempotencyMismatch`, `AppError`, `TrapError`, `UnavailableError`),
82
+ all extending `CalimeroError` with the node's `status` and `code`. See the
83
+ [error model](https://calimero-network.github.io/mero-js/reference/error-model/).
286
84
 
287
- const http = createBrowserHttpClient({
288
- baseUrl: 'https://node.example.com',
289
- getAuthToken: async () => localStorage.getItem('access_token') ?? undefined,
290
- });
291
-
292
- const data = await http.get<{ message: string }>('/api/hello');
293
- ```
294
-
295
- Methods (`get`/`post`/`put`/`delete`/`patch`/`head`/`request`) return parsed data
296
- directly and support per-call `{ signal, timeoutMs, parse }`. See the
297
- [HTTP transport guide](https://calimero-network.github.io/mero-js/guides/http-transport/)
298
- for options, parsing rules, retries, and cancellation.
85
+ The pieces it is built from are exported too: the sealed transport
86
+ (`createSealedFetch`), quote and signed-release verification
87
+ (`createQuoteVerifier`, `createSignedReleaseVerifier`, `trustSignedRelease`), and
88
+ the client for mero-tee's private agent gate (`attestAgent`, `provisionAgent`,
89
+ `releaseAgent`).
299
90
 
300
91
  ## Development
301
92
 
302
93
  ```bash
303
94
  pnpm install
304
95
  pnpm build
305
- pnpm test # unit tests (mocked, fast)
96
+ pnpm test:unit # unit tests, against a stand-in node over a real sealed channel
306
97
  pnpm lint
307
98
  pnpm typecheck
99
+ pnpm typecheck:consumer
308
100
  ```
309
101
 
310
- ### Testing & the contract gate
311
-
312
- The SDK mirrors core's HTTP wire types by hand, so two layers guard against drift:
102
+ The cluster tests (`tests/cloud-v2`) start three `merod run --dev` nodes and
103
+ run the client against them:
313
104
 
314
- - **Unit tests** (`pnpm test`) — mocked, fast, cover the client logic.
315
- - **E2E** (`pnpm test:e2e`) — runs the real SDK against a live `merod`. Point it
316
- at a node and provide credentials:
317
- ```bash
318
- NODE_URL=http://localhost:2528 MERO_E2E_USER=dev MERO_E2E_PASS=dev pnpm test:e2e
319
- ```
320
- In CI this runs both against a released `merod` (this repo) and against a
321
- PR-built `merod` (core's `sdk-e2e`).
105
+ ```bash
106
+ MEROD_BINARY=/path/to/merod KV_STORE_WASM=/path/to/kv_store.wasm pnpm test:cloud-v2
107
+ ```
322
108
 
323
- The e2e records every request as `METHOD /path` (see `tests/e2e/coverage-recorder.ts`).
324
- Core's route-coverage gate is **method-aware**: it fails if any admin route *or
325
- verb* ships without an SDK test — so e.g. `GET /blobs/:id` can't hide behind
326
- `DELETE /blobs/:id`. When you add an SDK method for a new endpoint, exercise it in
327
- the e2e (the sweep in `tests/e2e/coverage-sweep.test.ts` is the catch-all) so
328
- coverage stays complete.
109
+ Without the variables they use `../core/target/debug/merod` and
110
+ `../core/target/wasm32-unknown-unknown/app-release/kv_store.wasm` from a sibling
111
+ core checkout. `scripts/download-released-merod.sh` fetches the newest released
112
+ 1.0 `merod` and its kv-store fixture, which is what CI runs them against; core's
113
+ `e2e.yml` runs the same suite against core's own tree.
329
114
 
330
115
  ## License
331
116
 
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The check every cloud client runs before it sends anything: the node's quote
3
+ * must be genuine (or, in development, an explicitly allowed mock), from an
4
+ * image this client trusts, and its report data must bind this nonce, the
5
+ * expected app and the transport key the sealed channel will be opened to.
6
+ *
7
+ * Report data, as core's `calimero-cloud-client::attest` builds it:
8
+ * `nonce(32) || SHA-256("calimero.tee-attest.transport-key.v1" | app_hash | transport_key)`.
9
+ *
10
+ * A real quote is judged as the Rust client judges it (`attest::verify` with
11
+ * `Measurements::Release`): the node names the mero-tee release it runs; that
12
+ * release must be at least `minReleaseVersion`; its `published-mrtds.json` is
13
+ * fetched and its Sigstore signature checked against the node release workflow;
14
+ * the quote's signature chain and TCB status are checked against Intel's
15
+ * collateral (fetched here: the node sends none, and cannot forge it); the TD
16
+ * must not be a debug TD; and its registers must be the release's image for the
17
+ * trusted profile, `locked-read-only` unless a debug profile is explicitly
18
+ * allowed. Nothing here reads the environment: mock quotes pass only with
19
+ * `allowMock`.
20
+ */
21
+ import { type SignedNodeRelease } from '../sealed/release.js';
22
+ import { type VerifyTransportQuote } from '../sealed/sealed.js';
23
+ import { type DcapCollateral, type DcapVerify } from '../sealed/verify.js';
24
+ /** The mock quote format development clusters use. */
25
+ export declare const MOCK_QUOTE_HEADER = "MOCK_TDX_QUOTE_V1";
26
+ /** How the signed release a node names is trusted (core's `Measurements::Release`). */
27
+ export interface CloudReleasePolicy {
28
+ /**
29
+ * The oldest mero-tee release trusted, e.g. `2.3.109`. Required: every
30
+ * release ever signed stays validly signed, so without a floor a node could
31
+ * name an old release with a flaw since fixed.
32
+ */
33
+ minReleaseVersion: string;
34
+ /** The image profile trusted; defaults to `locked-read-only`. */
35
+ profile?: string;
36
+ /** Trust a debug profile. Its image is readable by the operator: testing only. */
37
+ allowDebugProfile?: boolean;
38
+ /**
39
+ * Where the release's signed measurements come from: `'github'` (the release
40
+ * assets, what core's Rust client reads; no CORS, so not in browsers), the
41
+ * base URL of a mirror (default {@link DEFAULT_RELEASE_MIRROR}), or a function.
42
+ * Untrusted either way: the Sigstore signature is checked here.
43
+ */
44
+ releaseSource?: 'github' | string | ((version: string) => Promise<SignedNodeRelease>);
45
+ /** DCAP verification; defaults to `verify` from `@phala/dcap-qvl`, loaded on first use. */
46
+ dcapVerify?: DcapVerify;
47
+ /**
48
+ * Intel's collateral for a quote; defaults to `@phala/dcap-qvl`'s
49
+ * `getCollateralFromPcs` (Intel's PCS). Point it at a PCCS a browser can reach
50
+ * if Intel's does not answer the page.
51
+ */
52
+ fetchCollateral?: (quote: Uint8Array) => Promise<DcapCollateral>;
53
+ /** Override the release signer (defaults to the mero-tee node release workflow). */
54
+ identity?: string;
55
+ issuer?: string;
56
+ /** The current time in milliseconds; defaults to `Date.now`. For tests. */
57
+ now?: () => number;
58
+ }
59
+ export interface CloudPolicy {
60
+ /** Accept mock quotes. Development clusters only; never set from the environment. */
61
+ allowMock?: boolean;
62
+ /** Trust real quotes from the signed release the node runs. */
63
+ release?: CloudReleasePolicy;
64
+ /**
65
+ * Verify a real quote yourself instead (e.g. `createQuoteVerifier` with
66
+ * pinned measurements). Takes precedence over `release`.
67
+ */
68
+ verifyQuote?: VerifyTransportQuote;
69
+ /** The app bundle hash (hex) the quote must bind, when talking to one app. */
70
+ appHash?: string;
71
+ }
72
+ export interface AttestationDoc {
73
+ quote: string;
74
+ transportKey: string;
75
+ appHash: string;
76
+ nonce: string;
77
+ release: {
78
+ version: string;
79
+ profile: string;
80
+ };
81
+ cluster: string;
82
+ node: number;
83
+ mock: boolean;
84
+ }
85
+ /**
86
+ * The release a node names, checked against the floor before anything is
87
+ * fetched. Accepts `2.3.109` and the tag form `mero-tee-v2.3.109`.
88
+ */
89
+ export declare function claimedRelease(doc: Pick<AttestationDoc, 'release'>, minReleaseVersion: string): string;
90
+ /** Fetch the node's attestation and return its transport key once it checks out. */
91
+ export declare function fetchAttestedKey(baseUrl: string, policy: CloudPolicy, fetchImpl?: typeof fetch): Promise<Uint8Array>;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The check every cloud client runs before it sends anything: the node's quote
3
+ * must be genuine (or, in development, an explicitly allowed mock), from an
4
+ * image this client trusts, and its report data must bind this nonce, the
5
+ * expected app and the transport key the sealed channel will be opened to.
6
+ *
7
+ * Report data, as core's `calimero-cloud-client::attest` builds it:
8
+ * `nonce(32) || SHA-256("calimero.tee-attest.transport-key.v1" | app_hash | transport_key)`.
9
+ *
10
+ * A real quote is judged as the Rust client judges it (`attest::verify` with
11
+ * `Measurements::Release`): the node names the mero-tee release it runs; that
12
+ * release must be at least `minReleaseVersion`; its `published-mrtds.json` is
13
+ * fetched and its Sigstore signature checked against the node release workflow;
14
+ * the quote's signature chain and TCB status are checked against Intel's
15
+ * collateral (fetched here: the node sends none, and cannot forge it); the TD
16
+ * must not be a debug TD; and its registers must be the release's image for the
17
+ * trusted profile, `locked-read-only` unless a debug profile is explicitly
18
+ * allowed. Nothing here reads the environment: mock quotes pass only with
19
+ * `allowMock`.
20
+ */
21
+ import { fromHex, hex } from '../crypto/internal.js';
22
+ import { DEFAULT_RELEASE_MIRROR, cloudNodeReleaseUrl, compareReleaseVersions, fetchGithubNodeRelease, fetchNodeRelease, releaseVersion, trustSignedRelease, } from '../sealed/release.js';
23
+ import { transportKeyBinding } from '../sealed/sealed.js';
24
+ import { PRODUCTION_PROFILE, createQuoteVerifier } from '../sealed/verify.js';
25
+ /** The mock quote format development clusters use. */
26
+ export const MOCK_QUOTE_HEADER = 'MOCK_TDX_QUOTE_V1';
27
+ function base64ToBytes(b64) {
28
+ const bin = atob(b64);
29
+ const out = new Uint8Array(bin.length);
30
+ for (let i = 0; i < bin.length; i++)
31
+ out[i] = bin.charCodeAt(i);
32
+ return out;
33
+ }
34
+ function equal(a, b) {
35
+ if (a.length !== b.length)
36
+ return false;
37
+ let diff = 0;
38
+ for (let i = 0; i < a.length; i++)
39
+ diff |= a[i] ^ b[i];
40
+ return diff === 0;
41
+ }
42
+ let dcapModule;
43
+ /** A function out of `@phala/dcap-qvl`, whatever shape the bundler gave the (CommonJS) module. */
44
+ async function dcap(name) {
45
+ dcapModule ?? (dcapModule = import('@phala/dcap-qvl'));
46
+ const ns = await dcapModule;
47
+ const f = typeof ns[name] === 'function' ? ns[name] : ns.default?.[name];
48
+ if (typeof f !== 'function')
49
+ throw new TypeError(`@phala/dcap-qvl loaded without ${name}`);
50
+ return f;
51
+ }
52
+ /**
53
+ * The release a node names, checked against the floor before anything is
54
+ * fetched. Accepts `2.3.109` and the tag form `mero-tee-v2.3.109`.
55
+ */
56
+ export function claimedRelease(doc, minReleaseVersion) {
57
+ const floor = releaseVersion(minReleaseVersion, 'minReleaseVersion');
58
+ const raw = String(doc.release?.version ?? '').trim().replace(/^mero-tee-v/, '').replace(/^v/, '');
59
+ const version = releaseVersion(raw, 'The release the node names');
60
+ if (compareReleaseVersions(version, floor) < 0) {
61
+ throw new Error(`mero-tee release ${version} is older than the minimum ${floor}`);
62
+ }
63
+ return version;
64
+ }
65
+ async function releaseFor(version, policy, fetchImpl) {
66
+ const source = policy.releaseSource;
67
+ if (typeof source === 'function')
68
+ return source(version);
69
+ if (source === 'github')
70
+ return fetchGithubNodeRelease(version, { fetch: fetchImpl });
71
+ return fetchNodeRelease(cloudNodeReleaseUrl(source ?? DEFAULT_RELEASE_MIRROR, version), { fetch: fetchImpl });
72
+ }
73
+ /** Judge a real quote as core's `Measurements::Release` does. */
74
+ async function verifyRelease(doc, args, policy, fetchImpl) {
75
+ // Refuse what no release could admit before reaching for the network.
76
+ const version = claimedRelease(doc, policy.minReleaseVersion);
77
+ const trusted = await trustSignedRelease({
78
+ release: await releaseFor(version, policy, fetchImpl),
79
+ profile: policy.profile ?? PRODUCTION_PROFILE,
80
+ allowDebugProfile: policy.allowDebugProfile,
81
+ minReleaseVersion: policy.minReleaseVersion,
82
+ identity: policy.identity,
83
+ issuer: policy.issuer,
84
+ });
85
+ const fetchCollateral = policy.fetchCollateral ??
86
+ (async (quote) => (await dcap('getCollateralFromPcs'))(quote));
87
+ const verifier = createQuoteVerifier({
88
+ dcapVerify: policy.dcapVerify ?? (await dcap('verify')),
89
+ fetchCollateral,
90
+ now: policy.now,
91
+ ...trusted,
92
+ });
93
+ return verifier(args);
94
+ }
95
+ /** Fetch the node's attestation and return its transport key once it checks out. */
96
+ export async function fetchAttestedKey(baseUrl, policy, fetchImpl = (input, init) => fetch(input, init)) {
97
+ const nonce = hex(crypto.getRandomValues(new Uint8Array(32)));
98
+ const url = new URL(`${baseUrl.replace(/\/+$/, '')}/attestation`);
99
+ url.searchParams.set('nonce', nonce);
100
+ if (policy.appHash)
101
+ url.searchParams.set('appHash', policy.appHash);
102
+ const response = await fetchImpl(url.toString());
103
+ if (!response.ok)
104
+ throw new Error(`The node refused to attest: HTTP ${response.status}`);
105
+ const doc = (await response.json());
106
+ const transportKey = fromHex(doc.transportKey, 'transportKey', 32);
107
+ const inner = policy.appHash ? fromHex(policy.appHash, 'appHash', 32) : new Uint8Array(32);
108
+ const suffix = await transportKeyBinding(inner, transportKey);
109
+ const quote = base64ToBytes(doc.quote);
110
+ const header = new TextEncoder().encode(MOCK_QUOTE_HEADER);
111
+ if (equal(quote.slice(0, header.length), header)) {
112
+ if (!policy.allowMock) {
113
+ throw new Error('mock quote refused: this client only trusts real TEEs');
114
+ }
115
+ const report = quote.slice(header.length, header.length + 64);
116
+ if (!equal(report.slice(0, 32), fromHex(nonce, 'nonce', 32)) || !equal(report.slice(32), suffix)) {
117
+ throw new Error('the quote does not bind this transport key, app and nonce');
118
+ }
119
+ return transportKey;
120
+ }
121
+ const args = { quoteB64: doc.quote, nonce, reportDataSuffix: hex(suffix) };
122
+ let ok;
123
+ if (policy.verifyQuote) {
124
+ ok = await policy.verifyQuote(args);
125
+ }
126
+ else if (policy.release) {
127
+ ok = await verifyRelease(doc, args, policy.release, fetchImpl);
128
+ }
129
+ else {
130
+ throw new Error('real quote refused: this client trusts no TD image (set policy.release)');
131
+ }
132
+ if (!ok)
133
+ throw new Error('The attestation did not verify, so its transport key is not trusted');
134
+ return transportKey;
135
+ }