@opendatalabs/vana-sdk 4.4.0 → 4.5.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.
- package/README.md +176 -0
- package/dist/connect/account-origin.cjs +72 -0
- package/dist/connect/account-origin.cjs.map +1 -0
- package/dist/connect/account-origin.d.ts +92 -0
- package/dist/connect/account-origin.js +47 -0
- package/dist/connect/account-origin.js.map +1 -0
- package/dist/connect/account-origin.test.d.ts +1 -0
- package/dist/connect/chrome.cjs +284 -0
- package/dist/connect/chrome.cjs.map +1 -0
- package/dist/connect/chrome.d.ts +42 -0
- package/dist/connect/chrome.js +256 -0
- package/dist/connect/chrome.js.map +1 -0
- package/dist/connect/http-transports.cjs +100 -0
- package/dist/connect/http-transports.cjs.map +1 -0
- package/dist/connect/http-transports.d.ts +77 -0
- package/dist/connect/http-transports.js +75 -0
- package/dist/connect/http-transports.js.map +1 -0
- package/dist/connect/http-transports.test.d.ts +1 -0
- package/dist/connect/launcher.cjs +232 -0
- package/dist/connect/launcher.cjs.map +1 -0
- package/dist/connect/launcher.d.ts +129 -0
- package/dist/connect/launcher.js +220 -0
- package/dist/connect/launcher.js.map +1 -0
- package/dist/connect/launcher.test.d.ts +1 -0
- package/dist/connect/protocol.cjs +259 -0
- package/dist/connect/protocol.cjs.map +1 -0
- package/dist/connect/protocol.d.ts +331 -0
- package/dist/connect/protocol.js +215 -0
- package/dist/connect/protocol.js.map +1 -0
- package/dist/connect/protocol.test.d.ts +1 -0
- package/dist/connect.cjs +55 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.ts +35 -0
- package/dist/connect.js +34 -0
- package/dist/connect.js.map +1 -0
- package/dist/direct/connect-flow-sheet.test.d.ts +1 -0
- package/dist/direct/connect-flow.cjs +211 -1
- package/dist/direct/connect-flow.cjs.map +1 -1
- package/dist/direct/connect-flow.d.ts +75 -0
- package/dist/direct/connect-flow.js +213 -1
- package/dist/direct/connect-flow.js.map +1 -1
- package/dist/direct/connect-handler.cjs +493 -0
- package/dist/direct/connect-handler.cjs.map +1 -0
- package/dist/direct/connect-handler.d.ts +246 -0
- package/dist/direct/connect-handler.js +476 -0
- package/dist/direct/connect-handler.js.map +1 -0
- package/dist/direct/connect-handler.test.d.ts +1 -0
- package/dist/direct/controller.cjs +86 -17
- package/dist/direct/controller.cjs.map +1 -1
- package/dist/direct/controller.d.ts +101 -6
- package/dist/direct/controller.js +86 -16
- package/dist/direct/controller.js.map +1 -1
- package/dist/direct/endpoints.cjs +5 -2
- package/dist/direct/endpoints.cjs.map +1 -1
- package/dist/direct/endpoints.js +5 -2
- package/dist/direct/endpoints.js.map +1 -1
- package/dist/direct/request-authorization.cjs +65 -0
- package/dist/direct/request-authorization.cjs.map +1 -0
- package/dist/direct/request-authorization.d.ts +65 -0
- package/dist/direct/request-authorization.js +39 -0
- package/dist/direct/request-authorization.js.map +1 -0
- package/dist/direct/request-authorization.test.d.ts +1 -0
- package/dist/direct/types.cjs.map +1 -1
- package/dist/direct/types.d.ts +6 -0
- package/dist/direct/types.js.map +1 -1
- package/dist/direct/use-direct-vana-connect.cjs +18 -1
- package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
- package/dist/direct/use-direct-vana-connect.js +18 -1
- package/dist/direct/use-direct-vana-connect.js.map +1 -1
- package/dist/direct/use-vana-connect.cjs +84 -0
- package/dist/direct/use-vana-connect.cjs.map +1 -0
- package/dist/direct/use-vana-connect.d.ts +59 -0
- package/dist/direct/use-vana-connect.js +64 -0
- package/dist/direct/use-vana-connect.js.map +1 -0
- package/dist/direct/use-vana-connect.test.d.ts +1 -0
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +104 -0
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +112 -0
- package/dist/index.node.cjs.map +3 -3
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +104 -0
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/fixtures/pdpp-scope-keys.json +133 -0
- package/dist/protocol/pdpp-scopes.cjs +134 -0
- package/dist/protocol/pdpp-scopes.cjs.map +1 -0
- package/dist/protocol/pdpp-scopes.d.ts +123 -0
- package/dist/protocol/pdpp-scopes.js +103 -0
- package/dist/protocol/pdpp-scopes.js.map +1 -0
- package/dist/protocol/pdpp-scopes.test.d.ts +1 -0
- package/dist/protocol/scopes.cjs +17 -0
- package/dist/protocol/scopes.cjs.map +1 -1
- package/dist/protocol/scopes.d.ts +1 -0
- package/dist/protocol/scopes.js +18 -0
- package/dist/protocol/scopes.js.map +1 -1
- package/dist/react.cjs +10 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +4 -1
- package/dist/react.js +11 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +61 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +16 -4
- package/dist/server.js +67 -3
- package/dist/server.js.map +1 -1
- package/package.json +11 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { privateKeyToAccount } from "viem/accounts";
|
|
2
|
+
import { hexToBytes } from "viem";
|
|
2
3
|
import { parseScope } from "../protocol/scopes.js";
|
|
3
4
|
import { parseScopeEntry } from "../protocol/scope-actions.js";
|
|
5
|
+
import {
|
|
6
|
+
PDPP_SCOPE_NAMESPACE,
|
|
7
|
+
grantRequestScopes,
|
|
8
|
+
isPdppScopeKey
|
|
9
|
+
} from "../protocol/pdpp-scopes.js";
|
|
4
10
|
import { createEscrowGatewayClient } from "../protocol/escrow.js";
|
|
5
11
|
import { createGatewayClient } from "../protocol/gateway.js";
|
|
6
12
|
import {
|
|
@@ -26,9 +32,24 @@ import {
|
|
|
26
32
|
import {
|
|
27
33
|
requestTestCredit as requestTestCreditFromAccount
|
|
28
34
|
} from "./test-credit.js";
|
|
35
|
+
import { signRequestReadAuthorization } from "./request-authorization.js";
|
|
29
36
|
import {
|
|
30
37
|
readPersonalServerData
|
|
31
38
|
} from "./personal-server-read.js";
|
|
39
|
+
const DIRECT_CONNECT_CAPABILITIES = [
|
|
40
|
+
"authorizeRequestRead",
|
|
41
|
+
"getEndpoints",
|
|
42
|
+
"deriveSecret",
|
|
43
|
+
"getEnv"
|
|
44
|
+
];
|
|
45
|
+
function missingConnectCapabilities(controller) {
|
|
46
|
+
return DIRECT_CONNECT_CAPABILITIES.filter(
|
|
47
|
+
(name) => typeof controller[name] !== "function"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function isConnectCapableController(controller) {
|
|
51
|
+
return missingConnectCapabilities(controller).length === 0;
|
|
52
|
+
}
|
|
32
53
|
function isHexPrivateKey(value) {
|
|
33
54
|
return /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
34
55
|
}
|
|
@@ -42,11 +63,24 @@ function createDirectDataController(config) {
|
|
|
42
63
|
"appPrivateKey must be a 0x-prefixed 32-byte hex string"
|
|
43
64
|
);
|
|
44
65
|
}
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
const scopes = grantRequestScopes({
|
|
67
|
+
grants: config.grants,
|
|
68
|
+
scopes: config.scopes
|
|
69
|
+
});
|
|
70
|
+
if (scopes.length === 0) {
|
|
71
|
+
throw new DirectConfigError(
|
|
72
|
+
"At least one scope or PDPP grant stream is required"
|
|
73
|
+
);
|
|
47
74
|
}
|
|
48
|
-
for (const entry of
|
|
49
|
-
|
|
75
|
+
for (const entry of scopes) {
|
|
76
|
+
const scope = parseScopeEntry(entry).scope;
|
|
77
|
+
parseScope(scope);
|
|
78
|
+
if (scope.startsWith(`${PDPP_SCOPE_NAMESPACE}.`) && !isPdppScopeKey(scope)) {
|
|
79
|
+
throw new DirectConfigError(
|
|
80
|
+
`"${entry}" is in the reserved "${PDPP_SCOPE_NAMESPACE}." namespace but is not a PDPP scope key. Request PDPP data with grants: [{ source_id, streams }].`,
|
|
81
|
+
{ entry }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
50
84
|
}
|
|
51
85
|
const env = config.env ?? "production";
|
|
52
86
|
const network = config.network ?? getDirectDefaultNetwork(env);
|
|
@@ -55,7 +89,8 @@ function createDirectDataController(config) {
|
|
|
55
89
|
const endpoints = {
|
|
56
90
|
...defaultEndpoints,
|
|
57
91
|
...config.endpoints,
|
|
58
|
-
chainId
|
|
92
|
+
chainId,
|
|
93
|
+
accountOrigin: config.endpoints?.accountOrigin ?? defaultEndpoints.accountOrigin
|
|
59
94
|
};
|
|
60
95
|
const account = privateKeyToAccount(privateKey);
|
|
61
96
|
const signMessage = (message) => account.signMessage({ message });
|
|
@@ -85,8 +120,8 @@ function createDirectDataController(config) {
|
|
|
85
120
|
let gateway = config.gateway;
|
|
86
121
|
async function resolveGrantUnion(owner, options) {
|
|
87
122
|
const requestedOnly = {
|
|
88
|
-
...unionGrantScopes([],
|
|
89
|
-
scopes: [...
|
|
123
|
+
...unionGrantScopes([], scopes, options.removeScopes),
|
|
124
|
+
scopes: [...scopes]
|
|
90
125
|
};
|
|
91
126
|
if (!options.merge) {
|
|
92
127
|
return { status: "disabled", ...requestedOnly };
|
|
@@ -100,7 +135,7 @@ function createDirectDataController(config) {
|
|
|
100
135
|
gateway,
|
|
101
136
|
owner,
|
|
102
137
|
appAddress: account.address,
|
|
103
|
-
scopes
|
|
138
|
+
scopes,
|
|
104
139
|
removeScopes: options.removeScopes
|
|
105
140
|
});
|
|
106
141
|
} catch (error) {
|
|
@@ -126,7 +161,7 @@ function createDirectDataController(config) {
|
|
|
126
161
|
},
|
|
127
162
|
async createAccessRequest(input) {
|
|
128
163
|
if (input.questions !== void 0) {
|
|
129
|
-
validateAccessRequestQuestions(input.questions,
|
|
164
|
+
validateAccessRequestQuestions(input.questions, scopes);
|
|
130
165
|
}
|
|
131
166
|
const removeScopes = input.removeScopes ?? [];
|
|
132
167
|
for (const entry of removeScopes) {
|
|
@@ -165,10 +200,10 @@ function createDirectDataController(config) {
|
|
|
165
200
|
requestTestCredit,
|
|
166
201
|
async readAllApprovedData(input) {
|
|
167
202
|
const status = await requireReadReady(input.requestId);
|
|
168
|
-
const
|
|
203
|
+
const scopes2 = approvedScopes(status);
|
|
169
204
|
const results = {};
|
|
170
205
|
const errors = {};
|
|
171
|
-
for (const scope of
|
|
206
|
+
for (const scope of scopes2) {
|
|
172
207
|
try {
|
|
173
208
|
results[scope] = await readScope(status, scope);
|
|
174
209
|
} catch (error) {
|
|
@@ -179,6 +214,38 @@ function createDirectDataController(config) {
|
|
|
179
214
|
await acknowledgeQuietly(input.requestId);
|
|
180
215
|
}
|
|
181
216
|
return { results, errors };
|
|
217
|
+
},
|
|
218
|
+
authorizeRequestRead(requestId) {
|
|
219
|
+
return signRequestReadAuthorization({
|
|
220
|
+
requestId,
|
|
221
|
+
appAddress: account.address,
|
|
222
|
+
signMessage
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
getEndpoints() {
|
|
226
|
+
return { ...endpoints };
|
|
227
|
+
},
|
|
228
|
+
getEnv() {
|
|
229
|
+
return env;
|
|
230
|
+
},
|
|
231
|
+
async deriveSecret(purpose) {
|
|
232
|
+
const ikm = await globalThis.crypto.subtle.importKey(
|
|
233
|
+
"raw",
|
|
234
|
+
new Uint8Array(hexToBytes(privateKey)),
|
|
235
|
+
"HKDF",
|
|
236
|
+
false,
|
|
237
|
+
["deriveBits"]
|
|
238
|
+
);
|
|
239
|
+
return globalThis.crypto.subtle.deriveBits(
|
|
240
|
+
{
|
|
241
|
+
name: "HKDF",
|
|
242
|
+
hash: "SHA-256",
|
|
243
|
+
salt: new TextEncoder().encode("vana-sdk/derived-secret/v1"),
|
|
244
|
+
info: new TextEncoder().encode(purpose)
|
|
245
|
+
},
|
|
246
|
+
ikm,
|
|
247
|
+
256
|
|
248
|
+
);
|
|
182
249
|
}
|
|
183
250
|
};
|
|
184
251
|
async function requireReadReady(requestId) {
|
|
@@ -202,12 +269,12 @@ function createDirectDataController(config) {
|
|
|
202
269
|
return status.scope ? [status.scope] : [];
|
|
203
270
|
}
|
|
204
271
|
function resolveRequestedScope(status, requested) {
|
|
205
|
-
const
|
|
206
|
-
if (requested === void 0) return
|
|
207
|
-
if (!
|
|
272
|
+
const scopes2 = approvedScopes(status);
|
|
273
|
+
if (requested === void 0) return scopes2[0];
|
|
274
|
+
if (!scopes2.includes(requested)) {
|
|
208
275
|
throw new ScopeNotApprovedError(
|
|
209
276
|
`Scope "${requested}" is not approved on this request`,
|
|
210
|
-
{ requestedScope: requested, approvedScopes:
|
|
277
|
+
{ requestedScope: requested, approvedScopes: scopes2 }
|
|
211
278
|
);
|
|
212
279
|
}
|
|
213
280
|
return requested;
|
|
@@ -260,6 +327,9 @@ function createDirectDataController(config) {
|
|
|
260
327
|
}
|
|
261
328
|
}
|
|
262
329
|
export {
|
|
263
|
-
|
|
330
|
+
DIRECT_CONNECT_CAPABILITIES,
|
|
331
|
+
createDirectDataController,
|
|
332
|
+
isConnectCapableController,
|
|
333
|
+
missingConnectCapabilities
|
|
264
334
|
};
|
|
265
335
|
//# sourceMappingURL=controller.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { parseScopeEntry } from \"../protocol/scope-actions\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { createGatewayClient } from \"../protocol/gateway\";\nimport {\n mergeWithLiveGrant,\n unionGrantScopes,\n type GrantUnionGateway,\n} from \"./grant-union\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n validateAccessRequestQuestions,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport {\n AccessNotApprovedError,\n DirectConfigError,\n EscrowBalanceTooLowError,\n ScopeNotApprovedError,\n} from \"./errors\";\nimport {\n requestTestCredit as requestTestCreditFromAccount,\n type TestCreditResult,\n} from \"./test-credit\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestGrantUnion,\n AccessRequestQuestion,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectPaymentResponseMetadata,\n DirectServiceEndpoints,\n ForegroundDelivery,\n MultiScopeDataResult,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /**\n * Grant scope entries to request. At least one required.\n *\n * Each entry is `[operation:]scope` (see `parseScopeEntry`): a bare entry\n * such as `\"icloud_notes.notes\"` requests read, and `\"write:coach.weekly\"`\n * requests write. The entries are carried through to the access request\n * verbatim and become the grant's `scopes`, so a request can mix both\n * (`[\"oura.sleep\", \"coach.weekly\", \"write:coach.weekly\"]`).\n *\n * The scope part must be a concrete `{source}.{category}[.{subcategory}]`\n * scope: this flow reads approved scopes back one by one, so wildcard\n * patterns (`chatgpt.*`, `write:chatgpt.*`) are not accepted here for\n * either operation.\n */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /**\n * Gateway the controller reads the owner's live grant from when\n * `createAccessRequest` is given an `owner` (see\n * {@link DirectDataController.createAccessRequest}). Only public reads are\n * used: `listGrantsByUser` and `getBuilder`. Defaults to a gateway client at\n * `endpoints.escrowGatewayUrl`.\n */\n gateway?: GrantUnionGateway;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n /**\n * Moksha test credit (testnet only).\n *\n * @remarks\n * With `autoTopUp: true` on `network: \"moksha\"`, a read refused because the\n * app's escrow is too low ({@link EscrowBalanceTooLowError}) asks Vana Account\n * for test credit, waits for it, and retries that read once. The refused\n * read charged nothing, so the retry cannot pay twice. Ignored on mainnet.\n */\n testCredit?: {\n autoTopUp?: boolean;\n /** Vana Account origin. Defaults to `https://account.vana.org`. */\n baseUrl?: string;\n pollIntervalMs?: number;\n timeoutMs?: number;\n /** `fetch` for the Vana Account calls. Defaults to `globalThis.fetch`. */\n fetchFn?: typeof fetch;\n };\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL and optional create retry key.\n * @returns The request id, HTTPS approval URL, and — for a pending deep Direct\n * request on mobile — an optional HTTPS `mobileContinuationUrl`.\n */\n createAccessRequest(input: {\n returnUrl: string;\n /** Optional foreground mobile delivery callback. */\n foregroundDelivery?: ForegroundDelivery;\n /**\n * Derivative questions to carry on the request (1 to 4). Each question\n * asks the user's Personal Server to compute its `derivedScope` from\n * `sourceScopes` the app never reads; the derived scope must also appear\n * in the controller's `scopes` as a bare read entry. Validated eagerly\n * against the configured scopes before the request is sent. See\n * {@link AccessRequestQuestion}.\n */\n questions?: AccessRequestQuestion[];\n /**\n * The data owner, when the app already knows who will approve (for\n * example from an earlier approval). With an owner, the controller reads\n * the owner's live grant for this app and includes its still-granted\n * scopes, so the new request extends the grant instead of replacing it.\n *\n * Without an owner nothing is read: the person is often anonymous until\n * they approve, and the Vana Web approval page signs the union of the\n * live grant and the request for whoever approves.\n */\n owner?: string;\n /**\n * Merge the owner's live grant into the request scopes. Defaults to\n * `true`; has no effect without `owner`. Pass `false` to send the\n * configured scopes verbatim.\n */\n mergeLiveGrant?: boolean;\n /**\n * Scope entries the app gives up from the owner's live grant. Removed\n * from the merged scopes here and sent on the request so the approval\n * page leaves them out of its own union. Matched verbatim\n * (`write:coach.weekly` and `coach.weekly` are separate entries).\n */\n removeScopes?: string[];\n /**\n * Stable retry key when the caller retries after an uncertain response.\n * Each create without one gets its own generated key.\n */\n idempotencyKey?: string;\n }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches shape-validated but unauthenticated\n * {@link DirectPaymentResponseMetadata} under `payment` when the Personal\n * Server returns it. After a successful read, the controller acknowledges\n * the DCR so Vana Web can close/redirect the approval tab.\n *\n * A request can approve several scopes. This reads **one** of them — `scope`\n * when given, otherwise the first approved scope. Use\n * {@link DirectDataController.readAllApprovedData} to read them all.\n *\n * Acknowledging moves the DCR to `completed`, which is terminal and no longer\n * read-ready. To read several scopes with your own loop, pass\n * `acknowledge: false` on every call but the last.\n *\n * @param input - The `dcr_*` request id, the optional `scope` to read, and an\n * optional `acknowledge` flag (default `true`).\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>>;\n\n /**\n * Read every scope the user approved on a request.\n *\n * @remarks\n * Reads the scopes in approval order, then acknowledges the DCR **once**,\n * after the last read — acknowledging earlier would move the request to\n * `completed` and make the remaining scopes unreadable.\n *\n * Each scope is a separate Personal Server read that settles its own\n * `data_access` fee from escrow, so reading N scopes costs N times a\n * single-scope read. The one-off registration fee is charged per grant, not\n * per scope.\n *\n * A scope that fails does not abort the rest: successes land in `results` and\n * failures in `errors`, because the fees for earlier scopes are already spent.\n * If any scope fails the request is left unacknowledged, so the scopes that\n * failed stay retryable — read them with `readApprovedData({ scope })` and\n * acknowledge on the last one.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ results, errors }`, both keyed by scope.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n */\n readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>>;\n /**\n * Ask Vana Account for Moksha test credit for this app's escrow and wait\n * until it is usable. Moksha only.\n *\n * @throws {@link DirectConfigError} on mainnet.\n * @throws TestCreditError if the request fails or does not finalize in time.\n */\n requestTestCredit(): Promise<TestCreditResult>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key is missing or malformed, when\n * `scopes` is empty, or when no escrow contract can be resolved.\n * @throws InvalidScopeEntryError when a `scopes` entry does not fit the\n * `[operation:]scope` grammar (an unknown operation prefix such as `delete:`).\n * @throws ZodError when the scope part of an entry is not a valid scope.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction. Each\n // element is a grant scope entry (`[operation:]scope`), so the operation\n // prefix is stripped first and only the scope part is checked against the\n // scope grammar — `write:coach.weekly` is a valid write-grant request, and\n // an unknown operation (`delete:x`) throws rather than being taken as read.\n // The entries themselves are passed through to the access request verbatim,\n // prefix included.\n for (const entry of config.scopes) {\n parseScope(parseScopeEntry(entry).scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n env,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n let gateway: GrantUnionGateway | undefined = config.gateway;\n\n // Read the owner's live grant and union it with the configured scopes. A\n // failed read never blocks the request: it goes out with the configured\n // scopes, and the approval page's own union still keeps the live grant.\n async function resolveGrantUnion(\n owner: string | undefined,\n options: { merge: boolean; removeScopes: string[] },\n ): Promise<AccessRequestGrantUnion> {\n // Without a merged live grant the request carries the configured scopes\n // verbatim (duplicates and order included), so bodies and their\n // signatures match SDKs that predate the grant union.\n const requestedOnly = {\n ...unionGrantScopes([], config.scopes, options.removeScopes),\n scopes: [...config.scopes],\n };\n if (!options.merge) {\n return { status: \"disabled\", ...requestedOnly };\n }\n if (!owner) {\n return { status: \"owner_unknown\", ...requestedOnly };\n }\n gateway ??= createGatewayClient(endpoints.escrowGatewayUrl);\n try {\n return await mergeWithLiveGrant({\n gateway,\n owner,\n appAddress: account.address,\n scopes: config.scopes,\n removeScopes: options.removeScopes,\n });\n } catch (error) {\n return {\n status: \"unavailable\",\n reason: error instanceof Error ? error.message : String(error),\n ...requestedOnly,\n };\n }\n }\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n // Fail fast even with an injected client: a malformed question would be\n // rejected by the access-request service anyway, after a signed round\n // trip.\n if (input.questions !== undefined) {\n validateAccessRequestQuestions(input.questions, config.scopes);\n }\n const removeScopes = input.removeScopes ?? [];\n for (const entry of removeScopes) {\n parseScopeEntry(entry);\n }\n const grantUnion = await resolveGrantUnion(input.owner, {\n merge: input.mergeLiveGrant !== false,\n removeScopes,\n });\n const created = await accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: grantUnion.scopes,\n ...(removeScopes.length > 0 ? { removeScopes } : {}),\n returnUrl: input.returnUrl,\n network,\n ...(input.foregroundDelivery !== undefined\n ? { foregroundDelivery: input.foregroundDelivery }\n : {}),\n ...(input.questions !== undefined\n ? { questions: input.questions }\n : {}),\n ...(input.idempotencyKey !== undefined\n ? { idempotencyKey: input.idempotencyKey }\n : {}),\n });\n return { ...created, scopes: grantUnion.scopes, grantUnion };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scope = resolveRequestedScope(status, input.scope);\n\n const result = await readScope<T>(status, scope);\n if (input.acknowledge !== false) {\n await acknowledgeQuietly(input.requestId);\n }\n return result;\n },\n\n requestTestCredit,\n\n async readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scopes = approvedScopes(status);\n\n const results: Record<string, ApprovedDataResult<T>> = {};\n const errors: Record<string, Error> = {};\n // Sequential, not parallel: each read settles its own escrow payment and\n // the default nonce source is process-local, so concurrent reads would\n // race on the payment nonce.\n for (const scope of scopes) {\n try {\n results[scope] = await readScope<T>(status, scope);\n } catch (error) {\n errors[scope] =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n\n // Acknowledge only after the last read, and only if every scope read —\n // acking moves the DCR to `completed`, which is terminal and no longer\n // read-ready, so acking on a partial failure would make the scope that\n // failed impossible to retry.\n if (Object.keys(errors).length === 0) {\n await acknowledgeQuietly(input.requestId);\n }\n\n return { results, errors };\n },\n };\n\n async function requireReadReady(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const status = await accessRequestClient.getAccessRequestStatus(requestId);\n // `scope` and `scopes` are both optional on the public status type, and a\n // client may return either one — require at least one approved scope rather\n // than the singular field specifically.\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n approvedScopes(status).length === 0\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: approvedScopes(status).length > 0,\n },\n );\n }\n return status;\n }\n\n /** Approved scopes in approval order, falling back to the single `scope`. */\n function approvedScopes(status: AccessRequestStatus): string[] {\n if (status.scopes && status.scopes.length > 0) return status.scopes;\n return status.scope ? [status.scope] : [];\n }\n\n /**\n * Resolve which scope to read. Rejects an unapproved scope up front so it\n * never reaches the Personal Server and never settles a fee.\n */\n function resolveRequestedScope(\n status: AccessRequestStatus,\n requested?: string,\n ): string {\n const scopes = approvedScopes(status);\n if (requested === undefined) return scopes[0];\n if (!scopes.includes(requested)) {\n throw new ScopeNotApprovedError(\n `Scope \"${requested}\" is not approved on this request`,\n { requestedScope: requested, approvedScopes: scopes },\n );\n }\n return requested;\n }\n\n async function requestTestCredit(): Promise<TestCreditResult> {\n if (network !== \"moksha\") {\n throw new DirectConfigError(\"Test credit is only available on Moksha\");\n }\n return requestTestCreditFromAccount({\n signMessage,\n baseUrl: config.testCredit?.baseUrl,\n pollIntervalMs: config.testCredit?.pollIntervalMs,\n timeoutMs: config.testCredit?.timeoutMs,\n fetchFn: config.testCredit?.fetchFn,\n });\n }\n\n async function readScope<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n try {\n return await readScopeOnce<T>(status, scope);\n } catch (error) {\n const autoTopUp =\n network === \"moksha\" && config.testCredit?.autoTopUp === true;\n if (!(autoTopUp && error instanceof EscrowBalanceTooLowError)) {\n throw error;\n }\n const credit = await requestTestCredit();\n if (credit.status === \"refused\") {\n throw error;\n }\n return readScopeOnce<T>(status, scope);\n }\n }\n\n async function readScopeOnce<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl as string,\n scope,\n grantId: status.grantId as string,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n return { scope, data: result.data as T, payment: result.payment };\n }\n\n async function acknowledgeQuietly(requestId: string): Promise<void> {\n try {\n await accessRequestClient.acknowledgeRead?.(requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { hexToBytes, type Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { parseScopeEntry } from \"../protocol/scope-actions\";\nimport {\n PDPP_SCOPE_NAMESPACE,\n grantRequestScopes,\n isPdppScopeKey,\n type PdppGrant,\n} from \"../protocol/pdpp-scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { createGatewayClient } from \"../protocol/gateway\";\nimport {\n mergeWithLiveGrant,\n unionGrantScopes,\n type GrantUnionGateway,\n} from \"./grant-union\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n validateAccessRequestQuestions,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport {\n AccessNotApprovedError,\n DirectConfigError,\n EscrowBalanceTooLowError,\n ScopeNotApprovedError,\n} from \"./errors\";\nimport {\n requestTestCredit as requestTestCreditFromAccount,\n type TestCreditResult,\n} from \"./test-credit\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport { signRequestReadAuthorization } from \"./request-authorization\";\nimport type { RequestAuthorization } from \"../connect/protocol\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestGrantUnion,\n AccessRequestQuestion,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectPaymentResponseMetadata,\n DirectServiceEndpoints,\n ForegroundDelivery,\n MultiScopeDataResult,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /**\n * PDPP grants to request: a registry `source_id` and the streams wanted\n * from it, e.g.\n * `[{ source_id: \"https://registry.pdpp.dev/sources/chatgpt\", streams: [\"conversations\", \"messages\"] }]`.\n *\n * Each stream is requested as the read scope `pdpp.<source>.<stream>` (see\n * `pdppScopeKey`), which is what the grant carries and what\n * `readApprovedData({ scope })` reads. Together with\n * {@link DirectDataControllerConfig.scopes} at least one entry is required;\n * when both are set the request is their union. An invalid `source_id` or\n * stream throws `InvalidPdppGrantError` at construction.\n */\n grants?: PdppGrant[];\n /**\n * Legacy grant scope entries to request. Optional when\n * {@link DirectDataControllerConfig.grants} names at least one stream;\n * otherwise at least one is required.\n *\n * Each entry is `[operation:]scope` (see `parseScopeEntry`): a bare entry\n * such as `\"icloud_notes.notes\"` requests read, and `\"write:coach.weekly\"`\n * requests write. The entries are carried through to the access request\n * verbatim and become the grant's `scopes`, so a request can mix both\n * (`[\"oura.sleep\", \"coach.weekly\", \"write:coach.weekly\"]`).\n *\n * The scope part must be a concrete `{source}.{category}[.{subcategory}]`\n * scope: this flow reads approved scopes back one by one, so wildcard\n * patterns (`chatgpt.*`, `write:chatgpt.*`) are not accepted here for\n * either operation.\n */\n scopes?: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /**\n * Gateway the controller reads the owner's live grant from when\n * `createAccessRequest` is given an `owner` (see\n * {@link DirectDataController.createAccessRequest}). Only public reads are\n * used: `listGrantsByUser` and `getBuilder`. Defaults to a gateway client at\n * `endpoints.escrowGatewayUrl`.\n */\n gateway?: GrantUnionGateway;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n /**\n * Moksha test credit (testnet only).\n *\n * @remarks\n * With `autoTopUp: true` on `network: \"moksha\"`, a read refused because the\n * app's escrow is too low ({@link EscrowBalanceTooLowError}) asks Vana Account\n * for test credit, waits for it, and retries that read once. The refused\n * read charged nothing, so the retry cannot pay twice. Ignored on mainnet.\n */\n testCredit?: {\n autoTopUp?: boolean;\n /** Vana Account origin. Defaults to `https://account.vana.org`. */\n baseUrl?: string;\n pollIntervalMs?: number;\n timeoutMs?: number;\n /** `fetch` for the Vana Account calls. Defaults to `globalThis.fetch`. */\n fetchFn?: typeof fetch;\n };\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController extends Partial<DirectConnectCapabilities> {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL and optional create retry key.\n * @returns The request id, HTTPS approval URL, and — for a pending deep Direct\n * request on mobile — an optional HTTPS `mobileContinuationUrl`.\n */\n createAccessRequest(input: {\n returnUrl: string;\n /** Optional foreground mobile delivery callback. */\n foregroundDelivery?: ForegroundDelivery;\n /**\n * Derivative questions to carry on the request (1 to 4). Each question\n * asks the user's Personal Server to compute its `derivedScope` from\n * `sourceScopes` the app never reads; the derived scope must also appear\n * in the controller's `scopes` as a bare read entry. Validated eagerly\n * against the configured scopes before the request is sent. See\n * {@link AccessRequestQuestion}.\n */\n questions?: AccessRequestQuestion[];\n /**\n * The data owner, when the app already knows who will approve (for\n * example from an earlier approval). With an owner, the controller reads\n * the owner's live grant for this app and includes its still-granted\n * scopes, so the new request extends the grant instead of replacing it.\n *\n * Without an owner nothing is read: the person is often anonymous until\n * they approve, and the Vana Web approval page signs the union of the\n * live grant and the request for whoever approves.\n */\n owner?: string;\n /**\n * Merge the owner's live grant into the request scopes. Defaults to\n * `true`; has no effect without `owner`. Pass `false` to send the\n * configured scopes verbatim.\n */\n mergeLiveGrant?: boolean;\n /**\n * Scope entries the app gives up from the owner's live grant. Removed\n * from the merged scopes here and sent on the request so the approval\n * page leaves them out of its own union. Matched verbatim\n * (`write:coach.weekly` and `coach.weekly` are separate entries).\n */\n removeScopes?: string[];\n /**\n * Stable retry key when the caller retries after an uncertain response.\n * Each create without one gets its own generated key.\n */\n idempotencyKey?: string;\n }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from `createAccessRequest`.\n * @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches shape-validated but unauthenticated\n * {@link DirectPaymentResponseMetadata} under `payment` when the Personal\n * Server returns it. After a successful read, the controller acknowledges\n * the DCR so Vana Web can close/redirect the approval tab.\n *\n * A request can approve several scopes. This reads **one** of them — `scope`\n * when given, otherwise the first approved scope. Use\n * {@link DirectDataController.readAllApprovedData} to read them all.\n *\n * Acknowledging moves the DCR to `completed`, which is terminal and no longer\n * read-ready. To read several scopes with your own loop, pass\n * `acknowledge: false` on every call but the last.\n *\n * @param input - The `dcr_*` request id, the optional `scope` to read, and an\n * optional `acknowledge` flag (default `true`).\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>>;\n\n /**\n * Read every scope the user approved on a request.\n *\n * @remarks\n * Reads the scopes in approval order, then acknowledges the DCR **once**,\n * after the last read — acknowledging earlier would move the request to\n * `completed` and make the remaining scopes unreadable.\n *\n * Each scope is a separate Personal Server read that settles its own\n * `data_access` fee from escrow, so reading N scopes costs N times a\n * single-scope read. The one-off registration fee is charged per grant, not\n * per scope.\n *\n * A scope that fails does not abort the rest: successes land in `results` and\n * failures in `errors`, because the fees for earlier scopes are already spent.\n * If any scope fails the request is left unacknowledged, so the scopes that\n * failed stay retryable — read them with `readApprovedData({ scope })` and\n * acknowledge on the last one.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ results, errors }`, both keyed by scope.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n */\n readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>>;\n /**\n * Ask Vana Account for Moksha test credit for this app's escrow and wait\n * until it is usable. Moksha only.\n *\n * @throws {@link DirectConfigError} on mainnet.\n * @throws TestCreditError if the request fails or does not finalize in time.\n */\n requestTestCredit(): Promise<TestCreditResult>;\n\n // Connect-sheet methods (authorizeRequestRead, getEndpoints, deriveSecret,\n // getEnv) come from DirectConnectCapabilities and are optional here, so a\n // hand-written controller keeps compiling. See isConnectCapableController.\n}\n\n/**\n * What the embedded connect sheet needs from a controller, on top of\n * {@link DirectDataController}.\n *\n * @remarks\n * `createDirectDataController` implements all of it. On the base interface\n * these methods are optional, so an existing hand-written\n * `DirectDataController` keeps compiling in this minor version;\n * `createVanaConnectHandler` checks for them with\n * {@link isConnectCapableController} and names any that are missing.\n */\nexport interface DirectConnectCapabilities {\n /**\n * Sign the short-lived read capability the embedded connect sheet needs to\n * show a request (`presentation: \"sheet\"`).\n *\n * @remarks\n * The same Direct SDK signature this controller sends when it polls the\n * request's status. It does not check whose request this is: call it only\n * for a request this browser created (`createVanaConnectHandler` binds them\n * with a signed cookie). Sign on each ask; Vana Account accepts it for\n * about five minutes.\n *\n * @param requestId - The `dcr_*` id from `createAccessRequest`.\n * @returns `{ appAddress, timestamp, signature }`.\n * @throws {@link DirectConfigError} when `requestId` is not a request id.\n */\n authorizeRequestRead(requestId: string): Promise<RequestAuthorization>;\n\n /**\n * The resolved service endpoints, including the Vana Account origin that\n * serves the connect sheet.\n *\n * @returns The endpoints this controller talks to.\n */\n getEndpoints(): DirectServiceEndpoints;\n\n /**\n * Derive a purpose-bound secret from the app key, without revealing it.\n *\n * @remarks\n * HKDF-SHA256 over the 32 key bytes, salt `\"vana-sdk/derived-secret/v1\"`,\n * `info` = `purpose`. Different purposes give independent secrets, so a\n * leaked derived secret never exposes the key or another purpose's secret.\n * `createVanaConnectHandler` keys its request-binding cookie with\n * `deriveSecret(\"vana-sdk/connect/request-binding/v1\")`.\n *\n * @param purpose - A label naming what the secret is for.\n * @returns 32 secret bytes.\n */\n deriveSecret(purpose: string): Promise<ArrayBuffer>;\n\n /**\n * The resolved deployment environment.\n *\n * @returns `\"production\"` or `\"dev\"`.\n */\n getEnv(): DirectEnv;\n}\n\n/** A controller that can back the embedded connect sheet. */\nexport type ConnectCapableDirectDataController = DirectDataController &\n DirectConnectCapabilities;\n\n/** The {@link DirectConnectCapabilities} method names, for checks and errors. */\nexport const DIRECT_CONNECT_CAPABILITIES = [\n \"authorizeRequestRead\",\n \"getEndpoints\",\n \"deriveSecret\",\n \"getEnv\",\n] as const satisfies readonly (keyof DirectConnectCapabilities)[];\n\n/**\n * The {@link DirectConnectCapabilities} methods a controller lacks.\n *\n * @param controller - Any {@link DirectDataController}.\n * @returns Missing method names; empty when it can back the sheet.\n */\nexport function missingConnectCapabilities(\n controller: DirectDataController,\n): (keyof DirectConnectCapabilities)[] {\n return DIRECT_CONNECT_CAPABILITIES.filter(\n (name) => typeof controller[name] !== \"function\",\n );\n}\n\n/**\n * Whether a controller implements every {@link DirectConnectCapabilities}\n * method (any controller from `createDirectDataController` does).\n *\n * @param controller - Any {@link DirectDataController}.\n * @returns `true` when it can back `createVanaConnectHandler`.\n */\nexport function isConnectCapableController(\n controller: DirectDataController,\n): controller is ConnectCapableDirectDataController {\n return missingConnectCapabilities(controller).length === 0;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key is missing or malformed, when\n * `scopes` and `grants` together request nothing, or when no escrow contract\n * can be resolved.\n * @throws InvalidPdppGrantError when a `grants` entry does not fit the PDPP\n * grant grammar.\n * @throws InvalidScopeEntryError when a `scopes` entry does not fit the\n * `[operation:]scope` grammar (an unknown operation prefix such as `delete:`).\n * @throws ZodError when the scope part of an entry is not a valid scope.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): ConnectCapableDirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n // The scopes every request starts from: legacy entries verbatim, then the\n // keys derived from PDPP grants.\n const scopes = grantRequestScopes({\n grants: config.grants,\n scopes: config.scopes,\n });\n if (scopes.length === 0) {\n throw new DirectConfigError(\n \"At least one scope or PDPP grant stream is required\",\n );\n }\n // Validate scopes eagerly so misconfiguration fails at construction. Each\n // element is a grant scope entry (`[operation:]scope`), so the operation\n // prefix is stripped first and only the scope part is checked against the\n // scope grammar — `write:coach.weekly` is a valid write-grant request, and\n // an unknown operation (`delete:x`) throws rather than being taken as read.\n // The entries themselves are passed through to the access request verbatim,\n // prefix included.\n for (const entry of scopes) {\n const scope = parseScopeEntry(entry).scope;\n parseScope(scope);\n // `pdpp.` is reserved for keys derived from PDPP grants; a hand-written\n // `pdpp.chatgpt` would read as one without being one.\n if (\n scope.startsWith(`${PDPP_SCOPE_NAMESPACE}.`) &&\n !isPdppScopeKey(scope)\n ) {\n throw new DirectConfigError(\n `\"${entry}\" is in the reserved \"${PDPP_SCOPE_NAMESPACE}.\" namespace but is not a PDPP scope key. Request PDPP data with grants: [{ source_id, streams }].`,\n { entry },\n );\n }\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n accountOrigin:\n config.endpoints?.accountOrigin ?? defaultEndpoints.accountOrigin,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n env,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n let gateway: GrantUnionGateway | undefined = config.gateway;\n\n // Read the owner's live grant and union it with the configured scopes. A\n // failed read never blocks the request: it goes out with the configured\n // scopes, and the approval page's own union still keeps the live grant.\n async function resolveGrantUnion(\n owner: string | undefined,\n options: { merge: boolean; removeScopes: string[] },\n ): Promise<AccessRequestGrantUnion> {\n // Without a merged live grant the request carries the configured scopes\n // verbatim (duplicates and order included), so bodies and their\n // signatures match SDKs that predate the grant union.\n const requestedOnly = {\n ...unionGrantScopes([], scopes, options.removeScopes),\n scopes: [...scopes],\n };\n if (!options.merge) {\n return { status: \"disabled\", ...requestedOnly };\n }\n if (!owner) {\n return { status: \"owner_unknown\", ...requestedOnly };\n }\n gateway ??= createGatewayClient(endpoints.escrowGatewayUrl);\n try {\n return await mergeWithLiveGrant({\n gateway,\n owner,\n appAddress: account.address,\n scopes,\n removeScopes: options.removeScopes,\n });\n } catch (error) {\n return {\n status: \"unavailable\",\n reason: error instanceof Error ? error.message : String(error),\n ...requestedOnly,\n };\n }\n }\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n // Fail fast even with an injected client: a malformed question would be\n // rejected by the access-request service anyway, after a signed round\n // trip.\n if (input.questions !== undefined) {\n validateAccessRequestQuestions(input.questions, scopes);\n }\n const removeScopes = input.removeScopes ?? [];\n for (const entry of removeScopes) {\n parseScopeEntry(entry);\n }\n const grantUnion = await resolveGrantUnion(input.owner, {\n merge: input.mergeLiveGrant !== false,\n removeScopes,\n });\n const created = await accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: grantUnion.scopes,\n ...(removeScopes.length > 0 ? { removeScopes } : {}),\n returnUrl: input.returnUrl,\n network,\n ...(input.foregroundDelivery !== undefined\n ? { foregroundDelivery: input.foregroundDelivery }\n : {}),\n ...(input.questions !== undefined\n ? { questions: input.questions }\n : {}),\n ...(input.idempotencyKey !== undefined\n ? { idempotencyKey: input.idempotencyKey }\n : {}),\n });\n return { ...created, scopes: grantUnion.scopes, grantUnion };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scope = resolveRequestedScope(status, input.scope);\n\n const result = await readScope<T>(status, scope);\n if (input.acknowledge !== false) {\n await acknowledgeQuietly(input.requestId);\n }\n return result;\n },\n\n requestTestCredit,\n\n async readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scopes = approvedScopes(status);\n\n const results: Record<string, ApprovedDataResult<T>> = {};\n const errors: Record<string, Error> = {};\n // Sequential, not parallel: each read settles its own escrow payment and\n // the default nonce source is process-local, so concurrent reads would\n // race on the payment nonce.\n for (const scope of scopes) {\n try {\n results[scope] = await readScope<T>(status, scope);\n } catch (error) {\n errors[scope] =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n\n // Acknowledge only after the last read, and only if every scope read —\n // acking moves the DCR to `completed`, which is terminal and no longer\n // read-ready, so acking on a partial failure would make the scope that\n // failed impossible to retry.\n if (Object.keys(errors).length === 0) {\n await acknowledgeQuietly(input.requestId);\n }\n\n return { results, errors };\n },\n\n authorizeRequestRead(requestId: string): Promise<RequestAuthorization> {\n return signRequestReadAuthorization({\n requestId,\n appAddress: account.address,\n signMessage,\n });\n },\n\n getEndpoints(): DirectServiceEndpoints {\n return { ...endpoints };\n },\n\n getEnv(): DirectEnv {\n return env;\n },\n\n async deriveSecret(purpose: string): Promise<ArrayBuffer> {\n const ikm = await globalThis.crypto.subtle.importKey(\n \"raw\",\n new Uint8Array(hexToBytes(privateKey as Hex)),\n \"HKDF\",\n false,\n [\"deriveBits\"],\n );\n return globalThis.crypto.subtle.deriveBits(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: new TextEncoder().encode(\"vana-sdk/derived-secret/v1\"),\n info: new TextEncoder().encode(purpose),\n },\n ikm,\n 256,\n );\n },\n };\n\n async function requireReadReady(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const status = await accessRequestClient.getAccessRequestStatus(requestId);\n // `scope` and `scopes` are both optional on the public status type, and a\n // client may return either one — require at least one approved scope rather\n // than the singular field specifically.\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n approvedScopes(status).length === 0\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: approvedScopes(status).length > 0,\n },\n );\n }\n return status;\n }\n\n /** Approved scopes in approval order, falling back to the single `scope`. */\n function approvedScopes(status: AccessRequestStatus): string[] {\n if (status.scopes && status.scopes.length > 0) return status.scopes;\n return status.scope ? [status.scope] : [];\n }\n\n /**\n * Resolve which scope to read. Rejects an unapproved scope up front so it\n * never reaches the Personal Server and never settles a fee.\n */\n function resolveRequestedScope(\n status: AccessRequestStatus,\n requested?: string,\n ): string {\n const scopes = approvedScopes(status);\n if (requested === undefined) return scopes[0];\n if (!scopes.includes(requested)) {\n throw new ScopeNotApprovedError(\n `Scope \"${requested}\" is not approved on this request`,\n { requestedScope: requested, approvedScopes: scopes },\n );\n }\n return requested;\n }\n\n async function requestTestCredit(): Promise<TestCreditResult> {\n if (network !== \"moksha\") {\n throw new DirectConfigError(\"Test credit is only available on Moksha\");\n }\n return requestTestCreditFromAccount({\n signMessage,\n baseUrl: config.testCredit?.baseUrl,\n pollIntervalMs: config.testCredit?.pollIntervalMs,\n timeoutMs: config.testCredit?.timeoutMs,\n fetchFn: config.testCredit?.fetchFn,\n });\n }\n\n async function readScope<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n try {\n return await readScopeOnce<T>(status, scope);\n } catch (error) {\n const autoTopUp =\n network === \"moksha\" && config.testCredit?.autoTopUp === true;\n if (!(autoTopUp && error instanceof EscrowBalanceTooLowError)) {\n throw error;\n }\n const credit = await requestTestCredit();\n if (credit.status === \"refused\") {\n throw error;\n }\n return readScopeOnce<T>(status, scope);\n }\n }\n\n async function readScopeOnce<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl as string,\n scope,\n grantId: status.grantId as string,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n return { scope, data: result.data as T, payment: result.payment };\n }\n\n async function acknowledgeQuietly(requestId: string): Promise<void> {\n try {\n await accessRequestClient.acknowledgeRead?.(requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n }\n}\n"],"mappings":"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","names":["scopes"]}
|
|
@@ -26,17 +26,20 @@ __export(endpoints_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(endpoints_exports);
|
|
28
28
|
var import_networks = require("../protocol/networks");
|
|
29
|
+
var import_protocol = require("../connect/protocol");
|
|
29
30
|
const PRODUCTION_ENDPOINTS = {
|
|
30
31
|
chainId: 1480,
|
|
31
32
|
accessRequestBaseUrl: "https://app.vana.org",
|
|
32
33
|
approvalAppBaseUrl: "https://app.vana.org",
|
|
33
|
-
escrowGatewayUrl: "https://dp-rpc.vana.org"
|
|
34
|
+
escrowGatewayUrl: "https://dp-rpc.vana.org",
|
|
35
|
+
accountOrigin: import_protocol.VANA_ACCOUNT_ORIGINS.production
|
|
34
36
|
};
|
|
35
37
|
const DEV_ENDPOINTS = {
|
|
36
38
|
chainId: 14800,
|
|
37
39
|
accessRequestBaseUrl: "https://app-dev.vana.org",
|
|
38
40
|
approvalAppBaseUrl: "https://app-dev.vana.org",
|
|
39
|
-
escrowGatewayUrl: "https://dp-rpc.moksha.vana.org"
|
|
41
|
+
escrowGatewayUrl: "https://dp-rpc.moksha.vana.org",
|
|
42
|
+
accountOrigin: import_protocol.VANA_ACCOUNT_ORIGINS.dev
|
|
40
43
|
};
|
|
41
44
|
function getDirectEndpoints(env) {
|
|
42
45
|
if (env === "dev") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/endpoints.ts"],"sourcesContent":["/**\n * Per-environment service URLs for the Direct Data Controller.\n *\n * @remarks\n * The SDK ships production defaults; pass `env: \"dev\"` only when testing\n * against Vana's internal dev stack. Use the controller `network` option for\n * chain selection without changing deployment URLs.\n *\n * @category Direct\n * @module direct/endpoints\n */\n\nimport type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from \"./types\";\nimport { getProtocolNetworkChainId } from \"../protocol/networks\";\n\n/** Production (mainnet) service URLs. */\nexport const PRODUCTION_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 1480,\n accessRequestBaseUrl: \"https://app.vana.org\",\n approvalAppBaseUrl: \"https://app.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.vana.org\",\n} as const;\n\n/** Internal dev stack service URLs. */\nexport const DEV_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 14800,\n accessRequestBaseUrl: \"https://app-dev.vana.org\",\n approvalAppBaseUrl: \"https://app-dev.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.moksha.vana.org\",\n} as const;\n\n/**\n * Resolve the default {@link DirectServiceEndpoints} for an environment.\n *\n * @param env - Target environment.\n * @returns The default endpoints for that environment.\n */\nexport function getDirectEndpoints(env: DirectEnv): DirectServiceEndpoints {\n if (env === \"dev\") {\n return DEV_ENDPOINTS;\n }\n\n return PRODUCTION_ENDPOINTS;\n}\n\n/**\n * Resolve the default network for a deployment environment.\n *\n * @param env - Target deployment environment.\n * @returns The network historically paired with that deployment.\n */\nexport function getDirectDefaultNetwork(env: DirectEnv): DirectNetwork {\n return env === \"dev\" ? \"moksha\" : \"mainnet\";\n}\n\n/**\n * Resolve the Vana chain id for a network.\n *\n * @param network - Target Vana network.\n * @returns The network chain id.\n */\nexport function getDirectNetworkChainId(network: DirectNetwork): number {\n return getProtocolNetworkChainId(network);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,sBAA0C;
|
|
1
|
+
{"version":3,"sources":["../../src/direct/endpoints.ts"],"sourcesContent":["/**\n * Per-environment service URLs for the Direct Data Controller.\n *\n * @remarks\n * The SDK ships production defaults; pass `env: \"dev\"` only when testing\n * against Vana's internal dev stack. Use the controller `network` option for\n * chain selection without changing deployment URLs.\n *\n * @category Direct\n * @module direct/endpoints\n */\n\nimport type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from \"./types\";\nimport { getProtocolNetworkChainId } from \"../protocol/networks\";\nimport { VANA_ACCOUNT_ORIGINS } from \"../connect/protocol\";\n\n/** Production (mainnet) service URLs. */\nexport const PRODUCTION_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 1480,\n accessRequestBaseUrl: \"https://app.vana.org\",\n approvalAppBaseUrl: \"https://app.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.vana.org\",\n accountOrigin: VANA_ACCOUNT_ORIGINS.production,\n} as const;\n\n/** Internal dev stack service URLs. */\nexport const DEV_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 14800,\n accessRequestBaseUrl: \"https://app-dev.vana.org\",\n approvalAppBaseUrl: \"https://app-dev.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.moksha.vana.org\",\n accountOrigin: VANA_ACCOUNT_ORIGINS.dev,\n} as const;\n\n/**\n * Resolve the default {@link DirectServiceEndpoints} for an environment.\n *\n * @param env - Target environment.\n * @returns The default endpoints for that environment.\n */\nexport function getDirectEndpoints(env: DirectEnv): DirectServiceEndpoints {\n if (env === \"dev\") {\n return DEV_ENDPOINTS;\n }\n\n return PRODUCTION_ENDPOINTS;\n}\n\n/**\n * Resolve the default network for a deployment environment.\n *\n * @param env - Target deployment environment.\n * @returns The network historically paired with that deployment.\n */\nexport function getDirectDefaultNetwork(env: DirectEnv): DirectNetwork {\n return env === \"dev\" ? \"moksha\" : \"mainnet\";\n}\n\n/**\n * Resolve the Vana chain id for a network.\n *\n * @param network - Target Vana network.\n * @returns The network chain id.\n */\nexport function getDirectNetworkChainId(network: DirectNetwork): number {\n return getProtocolNetworkChainId(network);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,sBAA0C;AAC1C,sBAAqC;AAG9B,MAAM,uBAA+C;AAAA,EAC1D,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe,qCAAqB;AACtC;AAGO,MAAM,gBAAwC;AAAA,EACnD,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe,qCAAqB;AACtC;AAQO,SAAS,mBAAmB,KAAwC;AACzE,MAAI,QAAQ,OAAO;AACjB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAQO,SAAS,wBAAwB,KAA+B;AACrE,SAAO,QAAQ,QAAQ,WAAW;AACpC;AAQO,SAAS,wBAAwB,SAAgC;AACtE,aAAO,2CAA0B,OAAO;AAC1C;","names":[]}
|
package/dist/direct/endpoints.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { getProtocolNetworkChainId } from "../protocol/networks.js";
|
|
2
|
+
import { VANA_ACCOUNT_ORIGINS } from "../connect/protocol.js";
|
|
2
3
|
const PRODUCTION_ENDPOINTS = {
|
|
3
4
|
chainId: 1480,
|
|
4
5
|
accessRequestBaseUrl: "https://app.vana.org",
|
|
5
6
|
approvalAppBaseUrl: "https://app.vana.org",
|
|
6
|
-
escrowGatewayUrl: "https://dp-rpc.vana.org"
|
|
7
|
+
escrowGatewayUrl: "https://dp-rpc.vana.org",
|
|
8
|
+
accountOrigin: VANA_ACCOUNT_ORIGINS.production
|
|
7
9
|
};
|
|
8
10
|
const DEV_ENDPOINTS = {
|
|
9
11
|
chainId: 14800,
|
|
10
12
|
accessRequestBaseUrl: "https://app-dev.vana.org",
|
|
11
13
|
approvalAppBaseUrl: "https://app-dev.vana.org",
|
|
12
|
-
escrowGatewayUrl: "https://dp-rpc.moksha.vana.org"
|
|
14
|
+
escrowGatewayUrl: "https://dp-rpc.moksha.vana.org",
|
|
15
|
+
accountOrigin: VANA_ACCOUNT_ORIGINS.dev
|
|
13
16
|
};
|
|
14
17
|
function getDirectEndpoints(env) {
|
|
15
18
|
if (env === "dev") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/endpoints.ts"],"sourcesContent":["/**\n * Per-environment service URLs for the Direct Data Controller.\n *\n * @remarks\n * The SDK ships production defaults; pass `env: \"dev\"` only when testing\n * against Vana's internal dev stack. Use the controller `network` option for\n * chain selection without changing deployment URLs.\n *\n * @category Direct\n * @module direct/endpoints\n */\n\nimport type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from \"./types\";\nimport { getProtocolNetworkChainId } from \"../protocol/networks\";\n\n/** Production (mainnet) service URLs. */\nexport const PRODUCTION_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 1480,\n accessRequestBaseUrl: \"https://app.vana.org\",\n approvalAppBaseUrl: \"https://app.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.vana.org\",\n} as const;\n\n/** Internal dev stack service URLs. */\nexport const DEV_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 14800,\n accessRequestBaseUrl: \"https://app-dev.vana.org\",\n approvalAppBaseUrl: \"https://app-dev.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.moksha.vana.org\",\n} as const;\n\n/**\n * Resolve the default {@link DirectServiceEndpoints} for an environment.\n *\n * @param env - Target environment.\n * @returns The default endpoints for that environment.\n */\nexport function getDirectEndpoints(env: DirectEnv): DirectServiceEndpoints {\n if (env === \"dev\") {\n return DEV_ENDPOINTS;\n }\n\n return PRODUCTION_ENDPOINTS;\n}\n\n/**\n * Resolve the default network for a deployment environment.\n *\n * @param env - Target deployment environment.\n * @returns The network historically paired with that deployment.\n */\nexport function getDirectDefaultNetwork(env: DirectEnv): DirectNetwork {\n return env === \"dev\" ? \"moksha\" : \"mainnet\";\n}\n\n/**\n * Resolve the Vana chain id for a network.\n *\n * @param network - Target Vana network.\n * @returns The network chain id.\n */\nexport function getDirectNetworkChainId(network: DirectNetwork): number {\n return getProtocolNetworkChainId(network);\n}\n"],"mappings":"AAaA,SAAS,iCAAiC;
|
|
1
|
+
{"version":3,"sources":["../../src/direct/endpoints.ts"],"sourcesContent":["/**\n * Per-environment service URLs for the Direct Data Controller.\n *\n * @remarks\n * The SDK ships production defaults; pass `env: \"dev\"` only when testing\n * against Vana's internal dev stack. Use the controller `network` option for\n * chain selection without changing deployment URLs.\n *\n * @category Direct\n * @module direct/endpoints\n */\n\nimport type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from \"./types\";\nimport { getProtocolNetworkChainId } from \"../protocol/networks\";\nimport { VANA_ACCOUNT_ORIGINS } from \"../connect/protocol\";\n\n/** Production (mainnet) service URLs. */\nexport const PRODUCTION_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 1480,\n accessRequestBaseUrl: \"https://app.vana.org\",\n approvalAppBaseUrl: \"https://app.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.vana.org\",\n accountOrigin: VANA_ACCOUNT_ORIGINS.production,\n} as const;\n\n/** Internal dev stack service URLs. */\nexport const DEV_ENDPOINTS: DirectServiceEndpoints = {\n chainId: 14800,\n accessRequestBaseUrl: \"https://app-dev.vana.org\",\n approvalAppBaseUrl: \"https://app-dev.vana.org\",\n escrowGatewayUrl: \"https://dp-rpc.moksha.vana.org\",\n accountOrigin: VANA_ACCOUNT_ORIGINS.dev,\n} as const;\n\n/**\n * Resolve the default {@link DirectServiceEndpoints} for an environment.\n *\n * @param env - Target environment.\n * @returns The default endpoints for that environment.\n */\nexport function getDirectEndpoints(env: DirectEnv): DirectServiceEndpoints {\n if (env === \"dev\") {\n return DEV_ENDPOINTS;\n }\n\n return PRODUCTION_ENDPOINTS;\n}\n\n/**\n * Resolve the default network for a deployment environment.\n *\n * @param env - Target deployment environment.\n * @returns The network historically paired with that deployment.\n */\nexport function getDirectDefaultNetwork(env: DirectEnv): DirectNetwork {\n return env === \"dev\" ? \"moksha\" : \"mainnet\";\n}\n\n/**\n * Resolve the Vana chain id for a network.\n *\n * @param network - Target Vana network.\n * @returns The network chain id.\n */\nexport function getDirectNetworkChainId(network: DirectNetwork): number {\n return getProtocolNetworkChainId(network);\n}\n"],"mappings":"AAaA,SAAS,iCAAiC;AAC1C,SAAS,4BAA4B;AAG9B,MAAM,uBAA+C;AAAA,EAC1D,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe,qBAAqB;AACtC;AAGO,MAAM,gBAAwC;AAAA,EACnD,SAAS;AAAA,EACT,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe,qBAAqB;AACtC;AAQO,SAAS,mBAAmB,KAAwC;AACzE,MAAI,QAAQ,OAAO;AACjB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAQO,SAAS,wBAAwB,KAA+B;AACrE,SAAO,QAAQ,QAAQ,WAAW;AACpC;AAQO,SAAS,wBAAwB,SAAgC;AACtE,SAAO,0BAA0B,OAAO;AAC1C;","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var request_authorization_exports = {};
|
|
20
|
+
__export(request_authorization_exports, {
|
|
21
|
+
buildRequestReadAuthorizationMessage: () => buildRequestReadAuthorizationMessage,
|
|
22
|
+
requestReadPath: () => requestReadPath,
|
|
23
|
+
signRequestReadAuthorization: () => signRequestReadAuthorization
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(request_authorization_exports);
|
|
26
|
+
var import_protocol = require("../connect/protocol");
|
|
27
|
+
var import_access_request_client = require("./access-request-client");
|
|
28
|
+
var import_errors = require("./errors.cjs");
|
|
29
|
+
function requestReadPath(requestId) {
|
|
30
|
+
return `/api/data-connection-requests/${encodeURIComponent(requestId)}`;
|
|
31
|
+
}
|
|
32
|
+
function buildRequestReadAuthorizationMessage(requestId, timestamp) {
|
|
33
|
+
return (0, import_access_request_client.buildDirectAccessRequestAuthMessage)({
|
|
34
|
+
method: "GET",
|
|
35
|
+
path: requestReadPath(requestId),
|
|
36
|
+
timestamp,
|
|
37
|
+
body: ""
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function signRequestReadAuthorization(input) {
|
|
41
|
+
if (!(0, import_protocol.isRequestId)(input.requestId)) {
|
|
42
|
+
throw new import_errors.DirectConfigError("Not a request id to sign for", {
|
|
43
|
+
requestId: input.requestId
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(input.appAddress)) {
|
|
47
|
+
throw new import_errors.DirectConfigError("appAddress must be a 0x-prefixed address");
|
|
48
|
+
}
|
|
49
|
+
const timestamp = String((input.now ?? Date.now)());
|
|
50
|
+
const signature = await input.signMessage(
|
|
51
|
+
buildRequestReadAuthorizationMessage(input.requestId, timestamp)
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
appAddress: input.appAddress,
|
|
55
|
+
signature,
|
|
56
|
+
timestamp
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
60
|
+
0 && (module.exports = {
|
|
61
|
+
buildRequestReadAuthorizationMessage,
|
|
62
|
+
requestReadPath,
|
|
63
|
+
signRequestReadAuthorization
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=request-authorization.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/direct/request-authorization.ts"],"sourcesContent":["/**\n * The short-lived read capability the embedded connect sheet asks for.\n *\n * @remarks\n * It is the Direct SDK request signature the controller already sends when it\n * polls a request's status: an EIP-191 `personal_sign`, by the app key, of\n *\n * ```\n * Vana Direct Access Request v1\n * method:GET\n * path:/api/data-connection-requests/<requestId>\n * timestamp:<ms since epoch>\n * body:\n * ```\n *\n * Vana Account replays it to the request authority, so the sheet can show the\n * request's real scopes while the key stays on the app's server. Account\n * accepts it for about five minutes, so sign it each time the sheet asks.\n *\n * @category Direct\n * @module direct/request-authorization\n */\n\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { isRequestId, type RequestAuthorization } from \"../connect/protocol\";\nimport { buildDirectAccessRequestAuthMessage } from \"./access-request-client\";\nimport { DirectConfigError } from \"./errors\";\n\n/** Input for {@link signRequestReadAuthorization}. */\nexport interface SignRequestReadAuthorizationInput {\n /** The `dcr_*` request id to authorize reading. */\n requestId: string;\n /** The app's `0x` address (the signer of `signMessage`). */\n appAddress: string;\n /** EIP-191 `personal_sign` by the app key. */\n signMessage: Web3SignedSignFn;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * The path a read authorization covers.\n *\n * @param requestId - The `dcr_*` request id.\n * @returns `/api/data-connection-requests/<requestId>`.\n */\nexport function requestReadPath(requestId: string): string {\n return `/api/data-connection-requests/${encodeURIComponent(requestId)}`;\n}\n\n/**\n * The exact message a read authorization signs.\n *\n * @param requestId - The `dcr_*` request id.\n * @param timestamp - Milliseconds since epoch, as a decimal string.\n * @returns The newline-joined Direct access-request message for the GET.\n */\nexport function buildRequestReadAuthorizationMessage(\n requestId: string,\n timestamp: string,\n): string {\n return buildDirectAccessRequestAuthMessage({\n method: \"GET\",\n path: requestReadPath(requestId),\n timestamp,\n body: \"\",\n });\n}\n\n/**\n * Sign the read capability for one request.\n *\n * @remarks\n * Signs only; it does not check whose request this is. Sign only requests the\n * caller created for this browser (the handler from\n * `createVanaConnectHandler` does that with a signed cookie).\n *\n * @param input - Request id, app address and signer.\n * @returns `{ appAddress, timestamp, signature }`, the `authorization` of a\n * `ConnectRequest`.\n * @throws {@link DirectConfigError} when `requestId` is not a request id\n * (`[A-Za-z0-9_-]{1,128}`), or `appAddress` is not an address.\n */\nexport async function signRequestReadAuthorization(\n input: SignRequestReadAuthorizationInput,\n): Promise<RequestAuthorization> {\n if (!isRequestId(input.requestId)) {\n throw new DirectConfigError(\"Not a request id to sign for\", {\n requestId: input.requestId,\n });\n }\n if (!/^0x[0-9a-fA-F]{40}$/.test(input.appAddress)) {\n throw new DirectConfigError(\"appAddress must be a 0x-prefixed address\");\n }\n const timestamp = String((input.now ?? Date.now)());\n const signature = await input.signMessage(\n buildRequestReadAuthorizationMessage(input.requestId, timestamp),\n );\n return {\n appAddress: input.appAddress as `0x${string}`,\n signature: signature as `0x${string}`,\n timestamp,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBA,sBAAuD;AACvD,mCAAoD;AACpD,oBAAkC;AAoB3B,SAAS,gBAAgB,WAA2B;AACzD,SAAO,iCAAiC,mBAAmB,SAAS,CAAC;AACvE;AASO,SAAS,qCACd,WACA,WACQ;AACR,aAAO,kEAAoC;AAAA,IACzC,QAAQ;AAAA,IACR,MAAM,gBAAgB,SAAS;AAAA,IAC/B;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACH;AAgBA,eAAsB,6BACpB,OAC+B;AAC/B,MAAI,KAAC,6BAAY,MAAM,SAAS,GAAG;AACjC,UAAM,IAAI,gCAAkB,gCAAgC;AAAA,MAC1D,WAAW,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AACA,MAAI,CAAC,sBAAsB,KAAK,MAAM,UAAU,GAAG;AACjD,UAAM,IAAI,gCAAkB,0CAA0C;AAAA,EACxE;AACA,QAAM,YAAY,QAAQ,MAAM,OAAO,KAAK,KAAK,CAAC;AAClD,QAAM,YAAY,MAAM,MAAM;AAAA,IAC5B,qCAAqC,MAAM,WAAW,SAAS;AAAA,EACjE;AACA,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The short-lived read capability the embedded connect sheet asks for.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* It is the Direct SDK request signature the controller already sends when it
|
|
6
|
+
* polls a request's status: an EIP-191 `personal_sign`, by the app key, of
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* Vana Direct Access Request v1
|
|
10
|
+
* method:GET
|
|
11
|
+
* path:/api/data-connection-requests/<requestId>
|
|
12
|
+
* timestamp:<ms since epoch>
|
|
13
|
+
* body:
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Vana Account replays it to the request authority, so the sheet can show the
|
|
17
|
+
* request's real scopes while the key stays on the app's server. Account
|
|
18
|
+
* accepts it for about five minutes, so sign it each time the sheet asks.
|
|
19
|
+
*
|
|
20
|
+
* @category Direct
|
|
21
|
+
* @module direct/request-authorization
|
|
22
|
+
*/
|
|
23
|
+
import type { Web3SignedSignFn } from "../auth/web3-signed-builder.js";
|
|
24
|
+
import { type RequestAuthorization } from "../connect/protocol.js";
|
|
25
|
+
/** Input for {@link signRequestReadAuthorization}. */
|
|
26
|
+
export interface SignRequestReadAuthorizationInput {
|
|
27
|
+
/** The `dcr_*` request id to authorize reading. */
|
|
28
|
+
requestId: string;
|
|
29
|
+
/** The app's `0x` address (the signer of `signMessage`). */
|
|
30
|
+
appAddress: string;
|
|
31
|
+
/** EIP-191 `personal_sign` by the app key. */
|
|
32
|
+
signMessage: Web3SignedSignFn;
|
|
33
|
+
/** Clock in ms. Defaults to `Date.now`. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The path a read authorization covers.
|
|
38
|
+
*
|
|
39
|
+
* @param requestId - The `dcr_*` request id.
|
|
40
|
+
* @returns `/api/data-connection-requests/<requestId>`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function requestReadPath(requestId: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* The exact message a read authorization signs.
|
|
45
|
+
*
|
|
46
|
+
* @param requestId - The `dcr_*` request id.
|
|
47
|
+
* @param timestamp - Milliseconds since epoch, as a decimal string.
|
|
48
|
+
* @returns The newline-joined Direct access-request message for the GET.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildRequestReadAuthorizationMessage(requestId: string, timestamp: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Sign the read capability for one request.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Signs only; it does not check whose request this is. Sign only requests the
|
|
56
|
+
* caller created for this browser (the handler from
|
|
57
|
+
* `createVanaConnectHandler` does that with a signed cookie).
|
|
58
|
+
*
|
|
59
|
+
* @param input - Request id, app address and signer.
|
|
60
|
+
* @returns `{ appAddress, timestamp, signature }`, the `authorization` of a
|
|
61
|
+
* `ConnectRequest`.
|
|
62
|
+
* @throws {@link DirectConfigError} when `requestId` is not a request id
|
|
63
|
+
* (`[A-Za-z0-9_-]{1,128}`), or `appAddress` is not an address.
|
|
64
|
+
*/
|
|
65
|
+
export declare function signRequestReadAuthorization(input: SignRequestReadAuthorizationInput): Promise<RequestAuthorization>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isRequestId } from "../connect/protocol.js";
|
|
2
|
+
import { buildDirectAccessRequestAuthMessage } from "./access-request-client.js";
|
|
3
|
+
import { DirectConfigError } from "./errors.js";
|
|
4
|
+
function requestReadPath(requestId) {
|
|
5
|
+
return `/api/data-connection-requests/${encodeURIComponent(requestId)}`;
|
|
6
|
+
}
|
|
7
|
+
function buildRequestReadAuthorizationMessage(requestId, timestamp) {
|
|
8
|
+
return buildDirectAccessRequestAuthMessage({
|
|
9
|
+
method: "GET",
|
|
10
|
+
path: requestReadPath(requestId),
|
|
11
|
+
timestamp,
|
|
12
|
+
body: ""
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function signRequestReadAuthorization(input) {
|
|
16
|
+
if (!isRequestId(input.requestId)) {
|
|
17
|
+
throw new DirectConfigError("Not a request id to sign for", {
|
|
18
|
+
requestId: input.requestId
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(input.appAddress)) {
|
|
22
|
+
throw new DirectConfigError("appAddress must be a 0x-prefixed address");
|
|
23
|
+
}
|
|
24
|
+
const timestamp = String((input.now ?? Date.now)());
|
|
25
|
+
const signature = await input.signMessage(
|
|
26
|
+
buildRequestReadAuthorizationMessage(input.requestId, timestamp)
|
|
27
|
+
);
|
|
28
|
+
return {
|
|
29
|
+
appAddress: input.appAddress,
|
|
30
|
+
signature,
|
|
31
|
+
timestamp
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
buildRequestReadAuthorizationMessage,
|
|
36
|
+
requestReadPath,
|
|
37
|
+
signRequestReadAuthorization
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=request-authorization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/direct/request-authorization.ts"],"sourcesContent":["/**\n * The short-lived read capability the embedded connect sheet asks for.\n *\n * @remarks\n * It is the Direct SDK request signature the controller already sends when it\n * polls a request's status: an EIP-191 `personal_sign`, by the app key, of\n *\n * ```\n * Vana Direct Access Request v1\n * method:GET\n * path:/api/data-connection-requests/<requestId>\n * timestamp:<ms since epoch>\n * body:\n * ```\n *\n * Vana Account replays it to the request authority, so the sheet can show the\n * request's real scopes while the key stays on the app's server. Account\n * accepts it for about five minutes, so sign it each time the sheet asks.\n *\n * @category Direct\n * @module direct/request-authorization\n */\n\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { isRequestId, type RequestAuthorization } from \"../connect/protocol\";\nimport { buildDirectAccessRequestAuthMessage } from \"./access-request-client\";\nimport { DirectConfigError } from \"./errors\";\n\n/** Input for {@link signRequestReadAuthorization}. */\nexport interface SignRequestReadAuthorizationInput {\n /** The `dcr_*` request id to authorize reading. */\n requestId: string;\n /** The app's `0x` address (the signer of `signMessage`). */\n appAddress: string;\n /** EIP-191 `personal_sign` by the app key. */\n signMessage: Web3SignedSignFn;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * The path a read authorization covers.\n *\n * @param requestId - The `dcr_*` request id.\n * @returns `/api/data-connection-requests/<requestId>`.\n */\nexport function requestReadPath(requestId: string): string {\n return `/api/data-connection-requests/${encodeURIComponent(requestId)}`;\n}\n\n/**\n * The exact message a read authorization signs.\n *\n * @param requestId - The `dcr_*` request id.\n * @param timestamp - Milliseconds since epoch, as a decimal string.\n * @returns The newline-joined Direct access-request message for the GET.\n */\nexport function buildRequestReadAuthorizationMessage(\n requestId: string,\n timestamp: string,\n): string {\n return buildDirectAccessRequestAuthMessage({\n method: \"GET\",\n path: requestReadPath(requestId),\n timestamp,\n body: \"\",\n });\n}\n\n/**\n * Sign the read capability for one request.\n *\n * @remarks\n * Signs only; it does not check whose request this is. Sign only requests the\n * caller created for this browser (the handler from\n * `createVanaConnectHandler` does that with a signed cookie).\n *\n * @param input - Request id, app address and signer.\n * @returns `{ appAddress, timestamp, signature }`, the `authorization` of a\n * `ConnectRequest`.\n * @throws {@link DirectConfigError} when `requestId` is not a request id\n * (`[A-Za-z0-9_-]{1,128}`), or `appAddress` is not an address.\n */\nexport async function signRequestReadAuthorization(\n input: SignRequestReadAuthorizationInput,\n): Promise<RequestAuthorization> {\n if (!isRequestId(input.requestId)) {\n throw new DirectConfigError(\"Not a request id to sign for\", {\n requestId: input.requestId,\n });\n }\n if (!/^0x[0-9a-fA-F]{40}$/.test(input.appAddress)) {\n throw new DirectConfigError(\"appAddress must be a 0x-prefixed address\");\n }\n const timestamp = String((input.now ?? Date.now)());\n const signature = await input.signMessage(\n buildRequestReadAuthorizationMessage(input.requestId, timestamp),\n );\n return {\n appAddress: input.appAddress as `0x${string}`,\n signature: signature as `0x${string}`,\n timestamp,\n };\n}\n"],"mappings":"AAwBA,SAAS,mBAA8C;AACvD,SAAS,2CAA2C;AACpD,SAAS,yBAAyB;AAoB3B,SAAS,gBAAgB,WAA2B;AACzD,SAAO,iCAAiC,mBAAmB,SAAS,CAAC;AACvE;AASO,SAAS,qCACd,WACA,WACQ;AACR,SAAO,oCAAoC;AAAA,IACzC,QAAQ;AAAA,IACR,MAAM,gBAAgB,SAAS;AAAA,IAC/B;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACH;AAgBA,eAAsB,6BACpB,OAC+B;AAC/B,MAAI,CAAC,YAAY,MAAM,SAAS,GAAG;AACjC,UAAM,IAAI,kBAAkB,gCAAgC;AAAA,MAC1D,WAAW,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AACA,MAAI,CAAC,sBAAsB,KAAK,MAAM,UAAU,GAAG;AACjD,UAAM,IAAI,kBAAkB,0CAA0C;AAAA,EACxE;AACA,QAAM,YAAY,QAAQ,MAAM,OAAO,KAAK,KAAK,CAAC;AAClD,QAAM,YAAY,MAAM,MAAM;AAAA,IAC5B,qCAAqC,MAAM,WAAW,SAAS;AAAA,EACjE;AACA,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|