prividium 1.3.1 → 1.4.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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # Prividium™ SDK & CLI
2
2
 
3
- A TypeScript SDK and CLI for integrating with the Prividium™ authorization system. The SDK provides popup OAuth, token
4
- management, and a viem transport for secure RPC; the CLI runs a local authenticated JSON-RPC proxy to your Prividium™
5
- RPC and manages basic configuration.
3
+ A TypeScript SDK and CLI for integrating with the Prividium™ authorization system. The SDK handles **authentication**
4
+ signing in with OIDC or SIWE and attaching your identity to each request while Prividium™ enforces **authorization**
5
+ server-side from your assigned roles, controlling which contract functions, events, and data you can access; the SDK
6
+ does not grant permissions. It provides popup OAuth, token management, and a viem transport for secure RPC; the CLI runs
7
+ a local authenticated JSON-RPC proxy to your Prividium™ RPC and manages basic configuration.
6
8
 
7
9
  ## Features
8
10
 
@@ -4,12 +4,10 @@ import { fetchAuthenticatedJson } from '../../clients/http.js';
4
4
  import { DEFAULT_DOCTOR_CALLBACK_PORT } from '../../constants.js';
5
5
  import { profileSchema } from '../../profile.js';
6
6
  export async function runBrowserAuthProbe(context) {
7
- const token = await authenticateInBrowser(context.targets.userPanelBaseUrl, DEFAULT_DOCTOR_CALLBACK_PORT);
8
- context.pendingToken = token;
7
+ context.pendingToken = await authenticateInBrowser(context.targets.userPanelBaseUrl, DEFAULT_DOCTOR_CALLBACK_PORT);
9
8
  }
10
9
  export async function runSessionValidationProbe(context) {
11
- const session = await fetchAuthenticatedJson(sessionSchema, new URL('/api/auth/current-session', context.targets.apiBaseUrl).href, context.pendingToken);
12
- context.pendingSession = session;
10
+ context.pendingSession = await fetchAuthenticatedJson(sessionSchema, new URL('/api/auth/current-session', context.targets.apiBaseUrl).href, context.pendingToken);
13
11
  }
14
12
  export async function runProfileFetchProbe(context) {
15
13
  const profile = await fetchAuthenticatedJson(profileSchema, new URL('/api/profiles/me', context.targets.apiBaseUrl).href, context.pendingToken);
@@ -1,15 +1,14 @@
1
1
  import { validateAndNormalizeUrls } from '../../utils.js';
2
2
  export async function runInputValidationProbe(context) {
3
- const targets = validateAndNormalizeUrls(context.rawRpcUrl, context.rawUserPanelUrl);
4
- context.targets = targets;
3
+ context.targets = validateAndNormalizeUrls(context.rawRpcUrl);
5
4
  }
6
5
  export async function runHostMismatchProbe(context) {
7
6
  const warnings = [];
8
- if (context.targets.apiBaseUrl !== context.rawRpcUrl) {
7
+ if (new URL(context.targets.apiBaseUrl).origin !== new URL(context.rawRpcUrl).origin) {
9
8
  warnings.push({ label: 'api', value: context.rawRpcUrl });
10
9
  }
11
10
  if (context.targets.userPanelBaseUrl !== context.rawUserPanelUrl) {
12
- warnings.push({ label: 'user panel', value: context.rawUserPanelUrl });
11
+ warnings.push({ label: 'user panel', value: context.rawUserPanelUrl ?? '' });
13
12
  }
14
13
  if (warnings.length > 0) {
15
14
  return {
@@ -0,0 +1,2 @@
1
+ import type { DoctorContext, ProbeOutput } from '../../types.js';
2
+ export declare function runWellKnownProbe(context: DoctorContext): Promise<ProbeOutput>;
@@ -0,0 +1,16 @@
1
+ import { fetchPrividiumConfig } from '#sdk';
2
+ export async function runWellKnownProbe(context) {
3
+ const prividiumInfo = await fetchPrividiumConfig(new URL(context.targets.apiBaseUrl).origin);
4
+ context.targets = {
5
+ ...context.targets,
6
+ userPanelBaseUrl: new URL(prividiumInfo.userPanelUrl).origin
7
+ };
8
+ context.apiVersion = prividiumInfo.version;
9
+ return {
10
+ values: [
11
+ { label: 'User Panel', value: prividiumInfo.userPanelUrl, inline: true },
12
+ { label: 'Chain', value: prividiumInfo.chainName, inline: true },
13
+ { label: 'Version', value: prividiumInfo.version, inline: true }
14
+ ]
15
+ };
16
+ }
@@ -1,6 +1,7 @@
1
1
  import { passed } from '../utils.js';
2
2
  import { runHostMismatchProbe, runInputValidationProbe } from './global/input-validation.js';
3
3
  import { runApiHealthProbe, runCallbackPortProbe, runPublicRpcProbe, runUserPanelReachabilityProbe } from './global/reachability.js';
4
+ import { runWellKnownProbe } from './global/well-known.js';
4
5
  export const globalStage = {
5
6
  id: 'global',
6
7
  title: 'Global',
@@ -22,11 +23,18 @@ export const globalStage = {
22
23
  runIf: passed('input-validation', 'Input validation failed'),
23
24
  run: runHostMismatchProbe
24
25
  },
26
+ {
27
+ id: 'well-known',
28
+ label: 'Prividium configuration',
29
+ progressMessage: 'Fetching Prividium configuration',
30
+ runIf: passed('input-validation', 'Input validation failed'),
31
+ run: runWellKnownProbe
32
+ },
25
33
  {
26
34
  id: 'user-panel-reachable',
27
35
  label: 'User Panel reachability',
28
36
  progressMessage: 'Checking User Panel reachability',
29
- runIf: passed('input-validation', 'Input validation failed'),
37
+ runIf: passed('well-known', 'Could not discover User Panel url'),
30
38
  run: runUserPanelReachabilityProbe
31
39
  },
32
40
  {
@@ -22,7 +22,9 @@ function buildSummaryBlock(context) {
22
22
  { label: 'Status', value: `${failCount} failed, ${skipCount} skipped, ${passCount} passed` },
23
23
  { label: 'API', value: context.targets?.apiBaseUrl ?? context.rawRpcUrl },
24
24
  ...(context.apiVersion ? [{ label: 'API Version', value: context.apiVersion }] : []),
25
- { label: 'User Panel', value: context.targets?.userPanelBaseUrl ?? context.rawUserPanelUrl },
25
+ ...(context.targets?.userPanelBaseUrl
26
+ ? [{ label: 'User Panel', value: context.targets.userPanelBaseUrl }]
27
+ : []),
26
28
  { label: 'Time', value: new Date().toISOString() }
27
29
  ]
28
30
  };
@@ -8,7 +8,7 @@ export type Options = {
8
8
  };
9
9
  export type NormalizedTargets = {
10
10
  apiBaseUrl: string;
11
- userPanelBaseUrl: string;
11
+ userPanelBaseUrl?: string;
12
12
  };
13
13
  export type ReportStatus = 'pass' | 'warn' | 'fail' | 'skip';
14
14
  export type ReportValue = {
@@ -59,7 +59,7 @@ export type AuthenticatedDoctorState = {
59
59
  };
60
60
  export type DoctorContext = {
61
61
  rawRpcUrl: string;
62
- rawUserPanelUrl: string;
62
+ rawUserPanelUrl?: string;
63
63
  targets?: NormalizedTargets;
64
64
  authState?: AuthenticatedDoctorState;
65
65
  reportContext?: ReportContext;
@@ -15,4 +15,4 @@ export declare function formatHexQuantity(value: string): string;
15
15
  export declare function shortenAddress(address: string): string;
16
16
  export declare function getRawReason(status: number, body: string): string;
17
17
  export declare function getThrownReason(error: unknown): string;
18
- export declare function validateAndNormalizeUrls(prividiumRpcUrl: string, userPanelUrl: string): NormalizedTargets;
18
+ export declare function validateAndNormalizeUrls(prividiumRpcUrl: string): NormalizedTargets;
@@ -36,17 +36,12 @@ export function getRawReason(status, body) {
36
36
  export function getThrownReason(error) {
37
37
  return error instanceof Error ? error.message : String(error);
38
38
  }
39
- export function validateAndNormalizeUrls(prividiumRpcUrl, userPanelUrl) {
39
+ export function validateAndNormalizeUrls(prividiumRpcUrl) {
40
40
  const apiParse = z.url().safeParse(prividiumRpcUrl);
41
41
  if (!apiParse.success) {
42
42
  throw new Error(`Invalid API url provided: ${prividiumRpcUrl}`);
43
43
  }
44
- const panelParse = z.url().safeParse(userPanelUrl);
45
- if (!panelParse.success) {
46
- throw new Error(`Invalid user panel url provided: ${userPanelUrl}`);
47
- }
48
44
  return {
49
- apiBaseUrl: `${new URL(prividiumRpcUrl).origin}/rpc`,
50
- userPanelBaseUrl: new URL(userPanelUrl).origin
45
+ apiBaseUrl: `${new URL(prividiumRpcUrl).origin}/rpc`
51
46
  };
52
47
  }
@@ -8,19 +8,16 @@ import { buildDoctorReport } from './doctor/report/build.js';
8
8
  import { printReport } from './doctor/report/render.js';
9
9
  import { executeDoctorStages } from './doctor/stages.js';
10
10
  import { showPrividiumHeader } from './utils/show-prividium-header.js';
11
- import { gatherUrlConfig } from './utils/url-config.js';
11
+ import { gatherApiUrl } from './utils/url-config.js';
12
12
  async function runDoctor(opts) {
13
13
  showPrividiumHeader();
14
14
  intro('Prividium™ Doctor');
15
- const { prividiumRpcUrl, userPanelUrl } = await gatherUrlConfig({
15
+ const prividiumRpcUrl = await gatherApiUrl({
16
16
  configPath: opts.configPath,
17
- rpcUrl: opts.rpcUrl,
18
- userPanelUrl: opts.userPanelUrl,
19
- rpcUrlLabel: 'prividium api'
17
+ apiUrl: opts.rpcUrl
20
18
  });
21
19
  const executionContext = {
22
20
  rawRpcUrl: prividiumRpcUrl,
23
- rawUserPanelUrl: userPanelUrl,
24
21
  walletAddresses: [],
25
22
  walletApiAvailable: false,
26
23
  results: []
@@ -52,13 +49,15 @@ export const addDoctor = (cli) => {
52
49
  return cli.command('doctor', 'Runs Prividium Doctor checks', (yargs) => yargs
53
50
  .option('rpcUrl', {
54
51
  alias: ['prividium-api-url', 'rpc-url', 'r'],
55
- description: 'Target Prividium™ API URL or /rpc URL. Takes precedence over config file and env.',
52
+ description: 'Target Prividium™ API URL or /rpc URL. The User Panel url and chain config are fetched from /.well-known/prividium on this host.',
56
53
  demandOption: false,
57
54
  type: 'string'
58
55
  })
59
56
  .option('userPanelUrl', {
60
57
  alias: ['user-panel-url', 'u'],
61
- description: 'User panel URL for browser authentication. Takes precedence over config file and env.',
58
+ description: 'Specifies the User Panel url to check. When provided, this value takes priority and the User Panel url is not fetched from /.well-known/prividium.',
59
+ demandOption: false,
60
+ deprecated: 'fetched automatically from Prividium™ API.',
62
61
  type: 'string'
63
62
  }), async (args) => {
64
63
  try {
@@ -1,12 +1,11 @@
1
- import { intro, log, text } from '@clack/prompts';
1
+ import { intro, log } from '@clack/prompts';
2
2
  import color from 'kleur';
3
3
  import { privateKeyToAccount } from 'viem/accounts';
4
- import { z } from 'zod';
5
4
  import { MemoryStorage, SiweAuth, TokenManager } from '#sdk/siwe';
6
5
  import { CreationWorkflow } from '../server/connection-workflow.js';
7
6
  import { buildServer } from '../server/server.js';
8
7
  import { showPrividiumHeader } from './utils/show-prividium-header.js';
9
- import { gatherApiUrl } from './utils/url-config.js';
8
+ import { gatherApiUrl, getUserPanelUrl } from './utils/url-config.js';
10
9
  const DEFAULT_PORT = 24101;
11
10
  const PRIVATE_KEY_REGEX = /^0x[0-9a-fA-F]{64}$/;
12
11
  function checkHostAndPortWarnings(host, port, allowExternalAccess) {
@@ -26,64 +25,6 @@ function checkHostAndPortWarnings(host, port, allowExternalAccess) {
26
25
  }
27
26
  }
28
27
  }
29
- const wellKnownMetadataSchema = z.object({
30
- apiUrl: z.string().optional()
31
- });
32
- // Fetches /.well-known/prividium from the User Panel and returns its apiUrl.
33
- async function discoverApiUrlFromUserPanel(userPanelUrl) {
34
- const wellKnownUrl = new URL('/.well-known/prividium', userPanelUrl).toString();
35
- let response;
36
- try {
37
- response = await fetch(wellKnownUrl, { signal: AbortSignal.timeout(5000) });
38
- }
39
- catch (error) {
40
- throw new Error(`Could not reach User Panel at ${userPanelUrl}.\n` +
41
- ` ${error instanceof Error ? error.message : String(error)}\n` +
42
- ' Please verify the URL is correct and the User Panel is running.');
43
- }
44
- if (!response.ok) {
45
- throw new Error(`User Panel at ${userPanelUrl} did not return /.well-known/prividium (HTTP ${response.status}).\n` +
46
- ' Please verify the URL points to a Prividium™ User Panel, or pass --api-url directly.');
47
- }
48
- let json;
49
- try {
50
- json = await response.json();
51
- }
52
- catch {
53
- throw new Error(`User Panel at ${userPanelUrl} returned non-JSON for /.well-known/prividium.\n` +
54
- ' Please verify the URL points to a Prividium™ User Panel, or pass --api-url directly.');
55
- }
56
- const parsed = wellKnownMetadataSchema.safeParse(json);
57
- if (!parsed.success || !parsed.data.apiUrl) {
58
- throw new Error(`User Panel at ${userPanelUrl} did not advertise an apiUrl in /.well-known/prividium.\n` +
59
- ' Please pass --api-url directly.');
60
- }
61
- return parsed.data.apiUrl;
62
- }
63
- async function resolveUserPanelUrl(opts) {
64
- const explicit = opts.userPanelUrl ?? process.env.USER_PANEL_URL;
65
- if (explicit) {
66
- const res = z.url().safeParse(explicit);
67
- if (!res.success) {
68
- throw new Error(`Invalid user panel url provided: ${explicit}`);
69
- }
70
- log.info(`Using user panel url: ${explicit}`);
71
- return explicit;
72
- }
73
- const prompted = await text({
74
- message: 'Please insert your Prividium™ User Panel url',
75
- validate(value) {
76
- const parsed = z.url().safeParse(value);
77
- if (!parsed.success) {
78
- return 'Please provide a valid url';
79
- }
80
- }
81
- });
82
- if (typeof prompted === 'symbol') {
83
- throw new Error('Canceled by the user');
84
- }
85
- return prompted;
86
- }
87
28
  async function startServerWithPrivateKey(opts) {
88
29
  const privateKey = opts.privateKey;
89
30
  if (!PRIVATE_KEY_REGEX.test(privateKey)) {
@@ -143,17 +84,12 @@ async function startServerWithPrivateKey(opts) {
143
84
  async function startServer(opts) {
144
85
  const workflow = new CreationWorkflow(opts.host, opts.port);
145
86
  workflow.start();
146
- const userPanelUrl = await resolveUserPanelUrl(opts);
147
- let apiUrl;
148
- if (opts.apiUrl) {
149
- apiUrl = opts.apiUrl;
150
- log.info(`Using Prividium™ API url: ${apiUrl}`);
151
- }
152
- else {
153
- log.info('Discovering API URL from User Panel...');
154
- apiUrl = await discoverApiUrlFromUserPanel(userPanelUrl);
155
- log.info(`Using Prividium™ API url: ${apiUrl}`);
156
- }
87
+ const apiUrl = await gatherApiUrl({
88
+ configPath: opts.configPath,
89
+ apiUrl: opts.apiUrl,
90
+ logProvidedUrls: true
91
+ });
92
+ const userPanelUrl = await getUserPanelUrl(opts.userPanelUrl, apiUrl);
157
93
  checkHostAndPortWarnings(opts.host, opts.port, opts.allowExternalAccess);
158
94
  const serverUrl = `http://${opts.host}:${opts.port}`;
159
95
  const app = buildServer({
@@ -184,13 +120,15 @@ export const addProxy = (cli) => {
184
120
  return cli.command('proxy', 'Starts authenticated rpc proxy server', (yargs) => yargs
185
121
  .option('apiUrl', {
186
122
  alias: ['api-url', 'rpc-url', 'r'],
187
- description: 'Specifies target Prividium™ API url. Required with --private-key.',
123
+ description: 'Specifies target Prividium™ API url. The User Panel url is fetched from /.well-known/prividium on this host.',
188
124
  demandOption: false,
189
125
  type: 'string'
190
126
  })
191
127
  .option('userPanelUrl', {
192
128
  alias: ['user-panel-url', 'u'],
193
- description: 'Specifies the User Panel url for browser-based login. If --api-url is omitted, it is fetched from the User Panel via /.well-known/prividium.',
129
+ description: 'Specifies the User Panel url for browser-based login. When provided, this value takes priority and the User Panel url is not fetched from /.well-known/prividium.',
130
+ demandOption: false,
131
+ deprecated: 'fetched automatically from Prividium™ API.',
194
132
  type: 'string'
195
133
  })
196
134
  .option('configPath', {
@@ -4,15 +4,5 @@ type GatherApiUrlOptions = {
4
4
  logProvidedUrls?: boolean;
5
5
  };
6
6
  export declare function gatherApiUrl({ configPath, apiUrl, logProvidedUrls }: GatherApiUrlOptions): Promise<string>;
7
- type GatherUrlConfigOptions = {
8
- configPath?: string;
9
- rpcUrl?: string;
10
- rpcUrlLabel: string;
11
- logProvidedUrls?: boolean;
12
- userPanelUrl?: string;
13
- };
14
- export declare function gatherUrlConfig({ configPath, rpcUrl, userPanelUrl, rpcUrlLabel, logProvidedUrls }: GatherUrlConfigOptions): Promise<{
15
- prividiumRpcUrl: string;
16
- userPanelUrl: string;
17
- }>;
7
+ export declare function getUserPanelUrl(userInput: string | undefined, apiUrl: string): Promise<string>;
18
8
  export {};
@@ -1,10 +1,10 @@
1
1
  import { confirm, log, text } from '@clack/prompts';
2
2
  import { z } from 'zod';
3
+ import { fetchPrividiumConfig } from '#sdk';
3
4
  import { ConfigFile } from '../../server/config-file.js';
4
5
  const envSchema = z.object({
5
6
  PRIVIDIUM_API_URL: z.string().optional(),
6
- PRIVIDIUM_RPC_URL: z.string().optional(),
7
- USER_PANEL_URL: z.string().optional()
7
+ PRIVIDIUM_RPC_URL: z.string().optional()
8
8
  });
9
9
  async function askForUrl(maybeUrl, name, logProvidedUrls) {
10
10
  if (maybeUrl !== undefined) {
@@ -35,7 +35,7 @@ export async function gatherApiUrl({ configPath, apiUrl, logProvidedUrls = false
35
35
  const config = new ConfigFile(configPath);
36
36
  const configData = config.read();
37
37
  const env = envSchema.parse(process.env);
38
- const givenApiUrl = apiUrl ?? env.PRIVIDIUM_API_URL ?? configData.apiUrl;
38
+ const givenApiUrl = apiUrl ?? env.PRIVIDIUM_API_URL ?? env.PRIVIDIUM_RPC_URL ?? configData.apiUrl;
39
39
  const { url: resolvedApiUrl, prompted } = await askForUrl(givenApiUrl, 'Prividium™ API', logProvidedUrls);
40
40
  if (prompted) {
41
41
  const confirmation = await confirm({
@@ -47,21 +47,20 @@ export async function gatherApiUrl({ configPath, apiUrl, logProvidedUrls = false
47
47
  }
48
48
  return resolvedApiUrl;
49
49
  }
50
- export async function gatherUrlConfig({ configPath, rpcUrl, userPanelUrl, rpcUrlLabel, logProvidedUrls = false }) {
51
- const config = new ConfigFile(configPath);
52
- const configData = config.read();
53
- const env = envSchema.parse(process.env);
54
- const givenRpcUrl = rpcUrl ?? env.PRIVIDIUM_API_URL ?? env.PRIVIDIUM_RPC_URL ?? configData.apiUrl;
55
- const givenUserPanelUrl = userPanelUrl ?? env.USER_PANEL_URL;
56
- const { url: prividiumRpcUrl, prompted: promptedRpc } = await askForUrl(givenRpcUrl, rpcUrlLabel, logProvidedUrls);
57
- const { url: resolvedUserPanelUrl, prompted: promptedPanel } = await askForUrl(givenUserPanelUrl, 'user panel', logProvidedUrls);
58
- if (promptedRpc || promptedPanel) {
59
- const confirmation = await confirm({
60
- message: 'Do you want to store this config for future usages?'
61
- });
62
- if (confirmation) {
63
- config.write({ apiUrl: prividiumRpcUrl });
50
+ export async function getUserPanelUrl(userInput, apiUrl) {
51
+ let userPanelUrl;
52
+ if (userInput !== undefined) {
53
+ const parsed = z.url().safeParse(userInput);
54
+ if (!parsed.success) {
55
+ throw new Error(`Invalid User Panel url provided: ${userInput}`);
64
56
  }
57
+ userPanelUrl = parsed.data;
58
+ }
59
+ else {
60
+ log.info('Fetching configuration from Prividium™ API...');
61
+ const prividiumInfo = await fetchPrividiumConfig(apiUrl);
62
+ userPanelUrl = prividiumInfo.userPanelUrl;
65
63
  }
66
- return { prividiumRpcUrl, userPanelUrl: resolvedUserPanelUrl };
64
+ log.info(`Using User Panel url: ${userPanelUrl}`);
65
+ return userPanelUrl;
67
66
  }
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  declare const schemas: {
3
3
  readonly latest: z.ZodObject<{
4
- userPanelUrl: z.ZodOptional<z.ZodString>;
5
4
  apiUrl: z.ZodOptional<z.ZodString>;
6
5
  l1RpcUrl: z.ZodOptional<z.ZodString>;
7
6
  localZksyncOsRpcUrl: z.ZodOptional<z.ZodString>;
@@ -13,11 +12,6 @@ declare const schemas: {
13
12
  }, z.core.$strip>;
14
13
  };
15
14
  declare const PROMPTS_FOR_CONFIGS: {
16
- readonly userPanelUrl: {
17
- readonly msg: "Please insert your Prividium™ User Panel url";
18
- readonly retry: "Please provide a valid url";
19
- readonly schema: z.ZodURL;
20
- };
21
15
  readonly apiUrl: {
22
16
  readonly msg: "Please insert your Prividium™ api url";
23
17
  readonly retry: "Please provide a valid url";
@@ -16,7 +16,6 @@ const dirs = appDirsFn({ appName: 'prividium-proxy' });
16
16
  // key, then change `latest` to the new shape and add a branch in `migrate()`.
17
17
  const schemas = {
18
18
  latest: z.object({
19
- userPanelUrl: z.string().optional(),
20
19
  apiUrl: z.string().optional(),
21
20
  l1RpcUrl: z.string().optional(),
22
21
  localZksyncOsRpcUrl: z.string().optional(),
@@ -31,15 +30,9 @@ const ENV_VARS_FOR_CONFIG = {
31
30
  apiUrl: 'PRIVIDIUM_API_URL',
32
31
  l1RpcUrl: 'L1_RPC_URL',
33
32
  diamondAddress: 'ZSYNCOS_L1_DIAMOND',
34
- userPanelUrl: 'USER_PANEL_URL',
35
33
  localZksyncOsRpcUrl: 'LOCAL_ZKSYNCOS_RPC_URL'
36
34
  };
37
35
  const PROMPTS_FOR_CONFIGS = {
38
- userPanelUrl: {
39
- msg: 'Please insert your Prividium™ User Panel url',
40
- retry: 'Please provide a valid url',
41
- schema: z.url()
42
- },
43
36
  apiUrl: {
44
37
  msg: 'Please insert your Prividium™ api url',
45
38
  retry: 'Please provide a valid url',
@@ -100,13 +93,13 @@ export class ConfigFile {
100
93
  write(config) {
101
94
  const dir = path.parse(this.filePath).dir;
102
95
  mkdirSync(dir, { recursive: true });
103
- writeFileSync(this.filePath, JSON.stringify({ latest: config }));
96
+ writeFileSync(this.filePath, JSON.stringify(config));
104
97
  }
105
98
  partialWrite(newConfig) {
106
99
  const old = this.read();
107
100
  const dir = path.parse(this.filePath).dir;
108
101
  mkdirSync(dir, { recursive: true });
109
- writeFileSync(this.filePath, JSON.stringify({ latest: { ...old, ...newConfig } }));
102
+ writeFileSync(this.filePath, JSON.stringify({ ...old, ...newConfig }));
110
103
  }
111
104
  remove() {
112
105
  if (existsSync(this.filePath)) {
@@ -10,5 +10,5 @@ export declare function createAdminMethods(deps: {
10
10
  prividiumApiBaseUrl: string;
11
11
  }): AdminMethods;
12
12
  export type { ContractsAdminMethods } from './contracts.js';
13
- export type { AdminContract, AdminContractCreate, AdminUser, AdminUserUpdate } from './types.js';
13
+ export type { AdminContract, AdminContractCreate, AdminUser, AdminUserUpdate, AdminUserUpdateInput } from './types.js';
14
14
  export type { UsersAdminMethods } from './users.js';
@@ -33,16 +33,11 @@ export interface AdminUser {
33
33
  wallets: Array<AdminUserWallet>;
34
34
  }
35
35
  export interface AdminUserUpdate {
36
- id?: string;
37
- oidcSub?: string | null;
38
- displayName?: string;
39
- source?: AdminUserSource;
40
- walletToken?: string | null;
41
- createdAt?: unknown;
42
- updatedAt?: unknown;
43
- roles?: Array<string>;
44
- wallets?: Array<string>;
36
+ displayName: string;
37
+ roles: Array<string>;
38
+ wallets: Array<string>;
45
39
  }
40
+ export type AdminUserUpdateInput = Partial<AdminUserUpdate>;
46
41
  export interface AdminDisclosedAddress {
47
42
  address: string;
48
43
  }
@@ -1,8 +1,8 @@
1
1
  import type { ApiCaller } from '../chain-core.js';
2
- import type { AdminUser, AdminUserUpdate } from './types.js';
2
+ import type { AdminUser, AdminUserUpdateInput } from './types.js';
3
3
  export interface UsersAdminMethods {
4
4
  getById(userId: string): Promise<AdminUser>;
5
- update(userId: string, params: AdminUserUpdate): Promise<AdminUser>;
5
+ update(userId: string, params: AdminUserUpdateInput): Promise<AdminUser>;
6
6
  }
7
7
  export declare function createUsersAdminMethods(deps: {
8
8
  prividiumApiCall: ApiCaller;
@@ -2,12 +2,26 @@ import { adminUserSchema } from './schemas.js';
2
2
  export function createUsersAdminMethods(deps) {
3
3
  const { prividiumApiCall, prividiumApiBaseUrl } = deps;
4
4
  const baseUrl = (id) => `${prividiumApiBaseUrl}/api/users/${encodeURIComponent(id)}`;
5
+ const getById = (userId) => prividiumApiCall(adminUserSchema, baseUrl(userId), 'GET');
5
6
  return {
6
- async getById(userId) {
7
- return prividiumApiCall(adminUserSchema, baseUrl(userId), 'GET');
8
- },
7
+ getById,
9
8
  async update(userId, params) {
10
- return prividiumApiCall(adminUserSchema, baseUrl(userId), 'PUT', JSON.stringify(params));
9
+ // PUT /users/{id} is a full replacement. To keep partial updates working,
10
+ // fetch the current user and merge in any omitted fields before sending.
11
+ // When the caller already provides every field, skip the extra round-trip.
12
+ let body;
13
+ if (params.displayName !== undefined && params.roles !== undefined && params.wallets !== undefined) {
14
+ body = { displayName: params.displayName, roles: params.roles, wallets: params.wallets };
15
+ }
16
+ else {
17
+ const current = await getById(userId);
18
+ body = {
19
+ displayName: params.displayName ?? current.displayName,
20
+ roles: params.roles ?? current.roles.map((role) => role.roleName),
21
+ wallets: params.wallets ?? current.wallets.map((wallet) => wallet.walletAddress)
22
+ };
23
+ }
24
+ return prividiumApiCall(adminUserSchema, baseUrl(userId), 'PUT', JSON.stringify(body));
11
25
  }
12
26
  };
13
27
  }
@@ -9,3 +9,4 @@ export type { AddNetworkParams, ContractAbiResponse, PopupOptions, PrividiumChai
9
9
  export { AUTH_ERRORS, STORAGE_KEYS } from './types.js';
10
10
  export type { UserTokenVerifyError, UserTokenVerifyResult, VerifyUserAccessTokenOptions } from './verify-user-access-token.js';
11
11
  export { verifyUserAccessToken } from './verify-user-access-token.js';
12
+ export { type FetchPrividiumConfigOptions, fetchPrividiumConfig, type PrividiumSystemInfo, PrividiumWellKnownError, type PrividiumWellKnownErrorKind, prividiumSystemInfoSchema } from './well-known.js';
package/dist/sdk/index.js CHANGED
@@ -7,3 +7,4 @@ export { LocalStorage, TokenManager } from './storage.js';
7
7
  export { generateRandomState } from './token-utils.js';
8
8
  export { AUTH_ERRORS, STORAGE_KEYS } from './types.js';
9
9
  export { verifyUserAccessToken } from './verify-user-access-token.js';
10
+ export { fetchPrividiumConfig, PrividiumWellKnownError, prividiumSystemInfoSchema } from './well-known.js';
@@ -1,4 +1,4 @@
1
- export type { AdminContract, AdminContractCreate, AdminMethods, AdminUser, AdminUserUpdate, ContractsAdminMethods, UsersAdminMethods } from './admin-api/index.js';
1
+ export type { AdminContract, AdminContractCreate, AdminMethods, AdminUser, AdminUserUpdate, AdminUserUpdateInput, ContractsAdminMethods, UsersAdminMethods } from './admin-api/index.js';
2
2
  export { createPrividiumClient } from './create-prividium-client.js';
3
3
  export { MemoryStorage } from './memory-storage.js';
4
4
  export { FORBIDDEN_ERROR_CODE, METHOD_NOT_FOUND_ERROR_CODE, UNAUTHORIZED_ERROR_CODE } from './rpc-error-codes.js';
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ export declare const prividiumSystemInfoSchema: z.ZodObject<{
3
+ blockExplorerUrl: z.ZodURL;
4
+ chainId: z.ZodTemplateLiteral<`0x${string}`>;
5
+ chainName: z.ZodString;
6
+ baseToken: z.ZodObject<{
7
+ name: z.ZodString;
8
+ symbol: z.ZodString;
9
+ decimals: z.ZodNumber;
10
+ l1Address: z.ZodNullable<z.ZodTemplateLiteral<`0x${string}`>>;
11
+ }, z.core.$strip>;
12
+ rpcUrl: z.ZodURL;
13
+ userPanelUrl: z.ZodURL;
14
+ version: z.ZodString;
15
+ l1ChainId: z.ZodTemplateLiteral<`0x${string}`>;
16
+ l1ChainName: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export type PrividiumSystemInfo = z.infer<typeof prividiumSystemInfoSchema>;
19
+ export type PrividiumWellKnownErrorKind = 'network' | 'http' | 'schema';
20
+ export declare class PrividiumWellKnownError extends Error {
21
+ readonly kind: PrividiumWellKnownErrorKind;
22
+ constructor(kind: PrividiumWellKnownErrorKind, message: string, options?: {
23
+ cause?: unknown;
24
+ });
25
+ }
26
+ export type FetchPrividiumConfigOptions = {
27
+ signal?: AbortSignal;
28
+ fetch?: typeof fetch;
29
+ };
30
+ export declare function fetchPrividiumConfig(apiUrl: string, opts?: FetchPrividiumConfigOptions): Promise<PrividiumSystemInfo>;
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ const hexSchema = z.templateLiteral(['0x', z.string().regex(/^[0-9a-fA-F]+$/)]);
3
+ const addressSchema = z.templateLiteral(['0x', z.string().regex(/^[0-9a-fA-F]{40}$/)]);
4
+ export const prividiumSystemInfoSchema = z.object({
5
+ blockExplorerUrl: z.url(),
6
+ chainId: hexSchema,
7
+ chainName: z.string(),
8
+ baseToken: z.object({
9
+ name: z.string(),
10
+ symbol: z.string(),
11
+ decimals: z.number(),
12
+ l1Address: addressSchema.nullable()
13
+ }),
14
+ rpcUrl: z.url(),
15
+ userPanelUrl: z.url(),
16
+ version: z.string(),
17
+ l1ChainId: hexSchema,
18
+ l1ChainName: z.string()
19
+ });
20
+ const wellKnownSchema = z.object({
21
+ v1: prividiumSystemInfoSchema
22
+ });
23
+ export class PrividiumWellKnownError extends Error {
24
+ kind;
25
+ constructor(kind, message, options) {
26
+ super(message, options);
27
+ this.name = 'PrividiumWellKnownError';
28
+ this.kind = kind;
29
+ }
30
+ }
31
+ const DEFAULT_TIMEOUT_MS = 5000;
32
+ export async function fetchPrividiumConfig(apiUrl, opts) {
33
+ const url = new URL('/.well-known/prividium', apiUrl).toString();
34
+ const doFetch = opts?.fetch ?? fetch;
35
+ const signal = opts?.signal ?? AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
36
+ let response;
37
+ try {
38
+ response = await doFetch(url, { signal });
39
+ }
40
+ catch (cause) {
41
+ throw new PrividiumWellKnownError('network', `Could not reach ${url}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
42
+ }
43
+ if (!response.ok) {
44
+ throw new PrividiumWellKnownError('http', `Unexpected HTTP ${response.status} from ${url}`);
45
+ }
46
+ let json;
47
+ try {
48
+ json = await response.json();
49
+ }
50
+ catch (cause) {
51
+ throw new PrividiumWellKnownError('schema', `Response from ${url} was not valid JSON`, { cause });
52
+ }
53
+ const parsed = wellKnownSchema.safeParse(json);
54
+ if (!parsed.success) {
55
+ throw new PrividiumWellKnownError('schema', `Response from ${url} did not match the Prividium well-known shape: ${parsed.error.message}`, { cause: parsed.error });
56
+ }
57
+ return parsed.data.v1;
58
+ }