@opendatalabs/vana-sdk 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
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 grants_exports = {};
20
+ __export(grants_exports, {
21
+ isDataPortabilityGatewayConfig: () => isDataPortabilityGatewayConfig,
22
+ parseGrantRegistrationPayload: () => parseGrantRegistrationPayload,
23
+ verifyGrantRegistration: () => verifyGrantRegistration
24
+ });
25
+ module.exports = __toCommonJS(grants_exports);
26
+ var import_viem = require("viem");
27
+ var import_eip712 = require("./eip712");
28
+ function isHexString(value) {
29
+ return typeof value === "string" && value.startsWith("0x");
30
+ }
31
+ function isDataPortabilityGatewayConfig(value) {
32
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
33
+ return false;
34
+ }
35
+ const config = value;
36
+ const contracts = config["contracts"];
37
+ if (typeof config["chainId"] !== "number" || !Number.isInteger(config["chainId"]) || config["chainId"] <= 0 || contracts === null || typeof contracts !== "object" || Array.isArray(contracts)) {
38
+ return false;
39
+ }
40
+ const c = contracts;
41
+ return isHexString(c["dataRegistry"]) && isHexString(c["dataPortabilityPermissions"]) && isHexString(c["dataPortabilityServer"]) && isHexString(c["dataPortabilityGrantees"]);
42
+ }
43
+ function parseGrantRegistrationPayload(grant) {
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(grant);
47
+ } catch {
48
+ return null;
49
+ }
50
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
51
+ return null;
52
+ }
53
+ const value = parsed;
54
+ if (!Array.isArray(value["scopes"]) || value["scopes"].length === 0) {
55
+ return null;
56
+ }
57
+ if (!value["scopes"].every((scope) => typeof scope === "string")) {
58
+ return null;
59
+ }
60
+ if (typeof value["expiresAt"] !== "number" || !Number.isFinite(value["expiresAt"])) {
61
+ return null;
62
+ }
63
+ if (value["user"] !== void 0 && !isHexString(value["user"])) {
64
+ return null;
65
+ }
66
+ if (value["builder"] !== void 0 && !isHexString(value["builder"])) {
67
+ return null;
68
+ }
69
+ if (value["nonce"] !== void 0 && (typeof value["nonce"] !== "number" || !Number.isFinite(value["nonce"]))) {
70
+ return null;
71
+ }
72
+ return {
73
+ user: value["user"],
74
+ builder: value["builder"],
75
+ scopes: value["scopes"],
76
+ expiresAt: value["expiresAt"],
77
+ nonce: value["nonce"]
78
+ };
79
+ }
80
+ function parseFileIds(fileIds) {
81
+ try {
82
+ const values = (fileIds ?? []).map((fileId) => BigInt(fileId));
83
+ return {
84
+ values,
85
+ display: values.map((fileId) => fileId.toString())
86
+ };
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+ async function verifyGrantRegistration(input) {
92
+ const payload = parseGrantRegistrationPayload(input.grant);
93
+ if (!payload) {
94
+ return {
95
+ valid: false,
96
+ error: "Grant must be JSON with scopes and expiresAt"
97
+ };
98
+ }
99
+ const fileIds = parseFileIds(input.fileIds);
100
+ if (!fileIds) {
101
+ return { valid: false, error: "fileIds must contain integer values" };
102
+ }
103
+ let valid;
104
+ try {
105
+ valid = await (0, import_viem.verifyTypedData)({
106
+ address: input.grantorAddress,
107
+ domain: (0, import_eip712.grantRegistrationDomain)(input.gatewayConfig),
108
+ types: import_eip712.GRANT_REGISTRATION_TYPES,
109
+ primaryType: "GrantRegistration",
110
+ message: {
111
+ grantorAddress: input.grantorAddress,
112
+ granteeId: input.granteeId,
113
+ grant: input.grant,
114
+ fileIds: fileIds.values
115
+ },
116
+ signature: input.signature
117
+ });
118
+ } catch {
119
+ return { valid: false, error: "EIP-712 signature verification failed" };
120
+ }
121
+ if (!valid) {
122
+ return { valid: false, error: "Grant signature does not match grantor" };
123
+ }
124
+ const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1e3);
125
+ if (payload.expiresAt > 0 && payload.expiresAt < nowSeconds) {
126
+ return { valid: false, error: "Grant has expired" };
127
+ }
128
+ if (payload.user !== void 0 && payload.user.toLowerCase() !== input.grantorAddress.toLowerCase()) {
129
+ return { valid: false, error: "Grant user does not match grantorAddress" };
130
+ }
131
+ return {
132
+ valid: true,
133
+ grantorAddress: input.grantorAddress,
134
+ granteeId: input.granteeId,
135
+ grant: input.grant,
136
+ payload,
137
+ fileIds: fileIds.display
138
+ };
139
+ }
140
+ // Annotate the CommonJS export names for ESM import in node:
141
+ 0 && (module.exports = {
142
+ isDataPortabilityGatewayConfig,
143
+ parseGrantRegistrationPayload,
144
+ verifyGrantRegistration
145
+ });
146
+ //# sourceMappingURL=grants.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/grants.ts"],"sourcesContent":["import { verifyTypedData } from \"viem\";\nimport {\n GRANT_REGISTRATION_TYPES,\n grantRegistrationDomain,\n type DataPortabilityGatewayConfig,\n} from \"./eip712\";\n\nexport interface DataPortabilityGrantPayload {\n user?: `0x${string}`;\n builder?: `0x${string}`;\n scopes: string[];\n expiresAt: number;\n nonce?: number;\n}\n\nexport interface VerifyGrantRegistrationInput {\n gatewayConfig: DataPortabilityGatewayConfig;\n grantorAddress: `0x${string}`;\n granteeId: `0x${string}`;\n grant: string;\n fileIds?: Array<string | number | bigint>;\n signature: `0x${string}`;\n nowSeconds?: number;\n}\n\nexport type VerifyGrantRegistrationResult =\n | {\n valid: true;\n grantorAddress: `0x${string}`;\n granteeId: `0x${string}`;\n grant: string;\n payload: DataPortabilityGrantPayload;\n fileIds: string[];\n }\n | {\n valid: false;\n error: string;\n };\n\nfunction isHexString(value: unknown): value is `0x${string}` {\n return typeof value === \"string\" && value.startsWith(\"0x\");\n}\n\nexport function isDataPortabilityGatewayConfig(\n value: unknown,\n): value is DataPortabilityGatewayConfig {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n const config = value as Record<string, unknown>;\n const contracts = config[\"contracts\"];\n if (\n typeof config[\"chainId\"] !== \"number\" ||\n !Number.isInteger(config[\"chainId\"]) ||\n config[\"chainId\"] <= 0 ||\n contracts === null ||\n typeof contracts !== \"object\" ||\n Array.isArray(contracts)\n ) {\n return false;\n }\n const c = contracts as Record<string, unknown>;\n return (\n isHexString(c[\"dataRegistry\"]) &&\n isHexString(c[\"dataPortabilityPermissions\"]) &&\n isHexString(c[\"dataPortabilityServer\"]) &&\n isHexString(c[\"dataPortabilityGrantees\"])\n );\n}\n\nexport function parseGrantRegistrationPayload(\n grant: string,\n): DataPortabilityGrantPayload | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(grant);\n } catch {\n return null;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return null;\n }\n const value = parsed as Record<string, unknown>;\n if (!Array.isArray(value[\"scopes\"]) || value[\"scopes\"].length === 0) {\n return null;\n }\n if (!value[\"scopes\"].every((scope) => typeof scope === \"string\")) {\n return null;\n }\n if (\n typeof value[\"expiresAt\"] !== \"number\" ||\n !Number.isFinite(value[\"expiresAt\"])\n ) {\n return null;\n }\n if (value[\"user\"] !== undefined && !isHexString(value[\"user\"])) {\n return null;\n }\n if (value[\"builder\"] !== undefined && !isHexString(value[\"builder\"])) {\n return null;\n }\n if (\n value[\"nonce\"] !== undefined &&\n (typeof value[\"nonce\"] !== \"number\" || !Number.isFinite(value[\"nonce\"]))\n ) {\n return null;\n }\n return {\n user: value[\"user\"] as `0x${string}` | undefined,\n builder: value[\"builder\"] as `0x${string}` | undefined,\n scopes: value[\"scopes\"] as string[],\n expiresAt: value[\"expiresAt\"],\n nonce: value[\"nonce\"] as number | undefined,\n };\n}\n\nfunction parseFileIds(fileIds: Array<string | number | bigint> | undefined): {\n values: bigint[];\n display: string[];\n} | null {\n try {\n const values = (fileIds ?? []).map((fileId) => BigInt(fileId));\n return {\n values,\n display: values.map((fileId) => fileId.toString()),\n };\n } catch {\n return null;\n }\n}\n\nexport async function verifyGrantRegistration(\n input: VerifyGrantRegistrationInput,\n): Promise<VerifyGrantRegistrationResult> {\n const payload = parseGrantRegistrationPayload(input.grant);\n if (!payload) {\n return {\n valid: false,\n error: \"Grant must be JSON with scopes and expiresAt\",\n };\n }\n\n const fileIds = parseFileIds(input.fileIds);\n if (!fileIds) {\n return { valid: false, error: \"fileIds must contain integer values\" };\n }\n\n let valid: boolean;\n try {\n valid = await verifyTypedData({\n address: input.grantorAddress,\n domain: grantRegistrationDomain(input.gatewayConfig),\n types: GRANT_REGISTRATION_TYPES,\n primaryType: \"GrantRegistration\",\n message: {\n grantorAddress: input.grantorAddress,\n granteeId: input.granteeId,\n grant: input.grant,\n fileIds: fileIds.values,\n },\n signature: input.signature,\n });\n } catch {\n return { valid: false, error: \"EIP-712 signature verification failed\" };\n }\n\n if (!valid) {\n return { valid: false, error: \"Grant signature does not match grantor\" };\n }\n\n const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (payload.expiresAt > 0 && payload.expiresAt < nowSeconds) {\n return { valid: false, error: \"Grant has expired\" };\n }\n\n if (\n payload.user !== undefined &&\n payload.user.toLowerCase() !== input.grantorAddress.toLowerCase()\n ) {\n return { valid: false, error: \"Grant user does not match grantorAddress\" };\n }\n\n return {\n valid: true,\n grantorAddress: input.grantorAddress,\n granteeId: input.granteeId,\n grant: input.grant,\n payload,\n fileIds: fileIds.display,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,31 @@
1
+ import { type DataPortabilityGatewayConfig } from "./eip712";
2
+ export interface DataPortabilityGrantPayload {
3
+ user?: `0x${string}`;
4
+ builder?: `0x${string}`;
5
+ scopes: string[];
6
+ expiresAt: number;
7
+ nonce?: number;
8
+ }
9
+ export interface VerifyGrantRegistrationInput {
10
+ gatewayConfig: DataPortabilityGatewayConfig;
11
+ grantorAddress: `0x${string}`;
12
+ granteeId: `0x${string}`;
13
+ grant: string;
14
+ fileIds?: Array<string | number | bigint>;
15
+ signature: `0x${string}`;
16
+ nowSeconds?: number;
17
+ }
18
+ export type VerifyGrantRegistrationResult = {
19
+ valid: true;
20
+ grantorAddress: `0x${string}`;
21
+ granteeId: `0x${string}`;
22
+ grant: string;
23
+ payload: DataPortabilityGrantPayload;
24
+ fileIds: string[];
25
+ } | {
26
+ valid: false;
27
+ error: string;
28
+ };
29
+ export declare function isDataPortabilityGatewayConfig(value: unknown): value is DataPortabilityGatewayConfig;
30
+ export declare function parseGrantRegistrationPayload(grant: string): DataPortabilityGrantPayload | null;
31
+ export declare function verifyGrantRegistration(input: VerifyGrantRegistrationInput): Promise<VerifyGrantRegistrationResult>;
@@ -0,0 +1,123 @@
1
+ import { verifyTypedData } from "viem";
2
+ import {
3
+ GRANT_REGISTRATION_TYPES,
4
+ grantRegistrationDomain
5
+ } from "./eip712";
6
+ function isHexString(value) {
7
+ return typeof value === "string" && value.startsWith("0x");
8
+ }
9
+ function isDataPortabilityGatewayConfig(value) {
10
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
11
+ return false;
12
+ }
13
+ const config = value;
14
+ const contracts = config["contracts"];
15
+ if (typeof config["chainId"] !== "number" || !Number.isInteger(config["chainId"]) || config["chainId"] <= 0 || contracts === null || typeof contracts !== "object" || Array.isArray(contracts)) {
16
+ return false;
17
+ }
18
+ const c = contracts;
19
+ return isHexString(c["dataRegistry"]) && isHexString(c["dataPortabilityPermissions"]) && isHexString(c["dataPortabilityServer"]) && isHexString(c["dataPortabilityGrantees"]);
20
+ }
21
+ function parseGrantRegistrationPayload(grant) {
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(grant);
25
+ } catch {
26
+ return null;
27
+ }
28
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
29
+ return null;
30
+ }
31
+ const value = parsed;
32
+ if (!Array.isArray(value["scopes"]) || value["scopes"].length === 0) {
33
+ return null;
34
+ }
35
+ if (!value["scopes"].every((scope) => typeof scope === "string")) {
36
+ return null;
37
+ }
38
+ if (typeof value["expiresAt"] !== "number" || !Number.isFinite(value["expiresAt"])) {
39
+ return null;
40
+ }
41
+ if (value["user"] !== void 0 && !isHexString(value["user"])) {
42
+ return null;
43
+ }
44
+ if (value["builder"] !== void 0 && !isHexString(value["builder"])) {
45
+ return null;
46
+ }
47
+ if (value["nonce"] !== void 0 && (typeof value["nonce"] !== "number" || !Number.isFinite(value["nonce"]))) {
48
+ return null;
49
+ }
50
+ return {
51
+ user: value["user"],
52
+ builder: value["builder"],
53
+ scopes: value["scopes"],
54
+ expiresAt: value["expiresAt"],
55
+ nonce: value["nonce"]
56
+ };
57
+ }
58
+ function parseFileIds(fileIds) {
59
+ try {
60
+ const values = (fileIds ?? []).map((fileId) => BigInt(fileId));
61
+ return {
62
+ values,
63
+ display: values.map((fileId) => fileId.toString())
64
+ };
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ async function verifyGrantRegistration(input) {
70
+ const payload = parseGrantRegistrationPayload(input.grant);
71
+ if (!payload) {
72
+ return {
73
+ valid: false,
74
+ error: "Grant must be JSON with scopes and expiresAt"
75
+ };
76
+ }
77
+ const fileIds = parseFileIds(input.fileIds);
78
+ if (!fileIds) {
79
+ return { valid: false, error: "fileIds must contain integer values" };
80
+ }
81
+ let valid;
82
+ try {
83
+ valid = await verifyTypedData({
84
+ address: input.grantorAddress,
85
+ domain: grantRegistrationDomain(input.gatewayConfig),
86
+ types: GRANT_REGISTRATION_TYPES,
87
+ primaryType: "GrantRegistration",
88
+ message: {
89
+ grantorAddress: input.grantorAddress,
90
+ granteeId: input.granteeId,
91
+ grant: input.grant,
92
+ fileIds: fileIds.values
93
+ },
94
+ signature: input.signature
95
+ });
96
+ } catch {
97
+ return { valid: false, error: "EIP-712 signature verification failed" };
98
+ }
99
+ if (!valid) {
100
+ return { valid: false, error: "Grant signature does not match grantor" };
101
+ }
102
+ const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1e3);
103
+ if (payload.expiresAt > 0 && payload.expiresAt < nowSeconds) {
104
+ return { valid: false, error: "Grant has expired" };
105
+ }
106
+ if (payload.user !== void 0 && payload.user.toLowerCase() !== input.grantorAddress.toLowerCase()) {
107
+ return { valid: false, error: "Grant user does not match grantorAddress" };
108
+ }
109
+ return {
110
+ valid: true,
111
+ grantorAddress: input.grantorAddress,
112
+ granteeId: input.granteeId,
113
+ grant: input.grant,
114
+ payload,
115
+ fileIds: fileIds.display
116
+ };
117
+ }
118
+ export {
119
+ isDataPortabilityGatewayConfig,
120
+ parseGrantRegistrationPayload,
121
+ verifyGrantRegistration
122
+ };
123
+ //# sourceMappingURL=grants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/grants.ts"],"sourcesContent":["import { verifyTypedData } from \"viem\";\nimport {\n GRANT_REGISTRATION_TYPES,\n grantRegistrationDomain,\n type DataPortabilityGatewayConfig,\n} from \"./eip712\";\n\nexport interface DataPortabilityGrantPayload {\n user?: `0x${string}`;\n builder?: `0x${string}`;\n scopes: string[];\n expiresAt: number;\n nonce?: number;\n}\n\nexport interface VerifyGrantRegistrationInput {\n gatewayConfig: DataPortabilityGatewayConfig;\n grantorAddress: `0x${string}`;\n granteeId: `0x${string}`;\n grant: string;\n fileIds?: Array<string | number | bigint>;\n signature: `0x${string}`;\n nowSeconds?: number;\n}\n\nexport type VerifyGrantRegistrationResult =\n | {\n valid: true;\n grantorAddress: `0x${string}`;\n granteeId: `0x${string}`;\n grant: string;\n payload: DataPortabilityGrantPayload;\n fileIds: string[];\n }\n | {\n valid: false;\n error: string;\n };\n\nfunction isHexString(value: unknown): value is `0x${string}` {\n return typeof value === \"string\" && value.startsWith(\"0x\");\n}\n\nexport function isDataPortabilityGatewayConfig(\n value: unknown,\n): value is DataPortabilityGatewayConfig {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n const config = value as Record<string, unknown>;\n const contracts = config[\"contracts\"];\n if (\n typeof config[\"chainId\"] !== \"number\" ||\n !Number.isInteger(config[\"chainId\"]) ||\n config[\"chainId\"] <= 0 ||\n contracts === null ||\n typeof contracts !== \"object\" ||\n Array.isArray(contracts)\n ) {\n return false;\n }\n const c = contracts as Record<string, unknown>;\n return (\n isHexString(c[\"dataRegistry\"]) &&\n isHexString(c[\"dataPortabilityPermissions\"]) &&\n isHexString(c[\"dataPortabilityServer\"]) &&\n isHexString(c[\"dataPortabilityGrantees\"])\n );\n}\n\nexport function parseGrantRegistrationPayload(\n grant: string,\n): DataPortabilityGrantPayload | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(grant);\n } catch {\n return null;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return null;\n }\n const value = parsed as Record<string, unknown>;\n if (!Array.isArray(value[\"scopes\"]) || value[\"scopes\"].length === 0) {\n return null;\n }\n if (!value[\"scopes\"].every((scope) => typeof scope === \"string\")) {\n return null;\n }\n if (\n typeof value[\"expiresAt\"] !== \"number\" ||\n !Number.isFinite(value[\"expiresAt\"])\n ) {\n return null;\n }\n if (value[\"user\"] !== undefined && !isHexString(value[\"user\"])) {\n return null;\n }\n if (value[\"builder\"] !== undefined && !isHexString(value[\"builder\"])) {\n return null;\n }\n if (\n value[\"nonce\"] !== undefined &&\n (typeof value[\"nonce\"] !== \"number\" || !Number.isFinite(value[\"nonce\"]))\n ) {\n return null;\n }\n return {\n user: value[\"user\"] as `0x${string}` | undefined,\n builder: value[\"builder\"] as `0x${string}` | undefined,\n scopes: value[\"scopes\"] as string[],\n expiresAt: value[\"expiresAt\"],\n nonce: value[\"nonce\"] as number | undefined,\n };\n}\n\nfunction parseFileIds(fileIds: Array<string | number | bigint> | undefined): {\n values: bigint[];\n display: string[];\n} | null {\n try {\n const values = (fileIds ?? []).map((fileId) => BigInt(fileId));\n return {\n values,\n display: values.map((fileId) => fileId.toString()),\n };\n } catch {\n return null;\n }\n}\n\nexport async function verifyGrantRegistration(\n input: VerifyGrantRegistrationInput,\n): Promise<VerifyGrantRegistrationResult> {\n const payload = parseGrantRegistrationPayload(input.grant);\n if (!payload) {\n return {\n valid: false,\n error: \"Grant must be JSON with scopes and expiresAt\",\n };\n }\n\n const fileIds = parseFileIds(input.fileIds);\n if (!fileIds) {\n return { valid: false, error: \"fileIds must contain integer values\" };\n }\n\n let valid: boolean;\n try {\n valid = await verifyTypedData({\n address: input.grantorAddress,\n domain: grantRegistrationDomain(input.gatewayConfig),\n types: GRANT_REGISTRATION_TYPES,\n primaryType: \"GrantRegistration\",\n message: {\n grantorAddress: input.grantorAddress,\n granteeId: input.granteeId,\n grant: input.grant,\n fileIds: fileIds.values,\n },\n signature: input.signature,\n });\n } catch {\n return { valid: false, error: \"EIP-712 signature verification failed\" };\n }\n\n if (!valid) {\n return { valid: false, error: \"Grant signature does not match grantor\" };\n }\n\n const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (payload.expiresAt > 0 && payload.expiresAt < nowSeconds) {\n return { valid: false, error: \"Grant has expired\" };\n }\n\n if (\n payload.user !== undefined &&\n payload.user.toLowerCase() !== input.grantorAddress.toLowerCase()\n ) {\n return { valid: false, error: \"Grant user does not match grantorAddress\" };\n }\n\n return {\n valid: true,\n grantorAddress: input.grantorAddress,\n granteeId: input.granteeId,\n grant: input.grant,\n payload,\n fileIds: fileIds.display,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -31,32 +31,57 @@ class PSError extends Error {
31
31
  code;
32
32
  }
33
33
  const KNOWN_CODES = /* @__PURE__ */ new Set([
34
+ "missing_auth",
35
+ "invalid_signature",
36
+ "unregistered_builder",
37
+ "not_owner",
38
+ "expired_token",
34
39
  "grant_invalid",
40
+ "grant_required",
41
+ "grant_expired",
35
42
  "grant_revoked",
43
+ "scope_mismatch",
36
44
  "fee_required",
37
- "ps_unavailable"
45
+ "ps_unavailable",
46
+ "server_not_configured",
47
+ "content_too_large"
38
48
  ]);
39
- async function parsePSError(response) {
40
- if (response.ok) {
49
+ function isRecord(value) {
50
+ return value !== null && typeof value === "object" && !Array.isArray(value);
51
+ }
52
+ function normalizeCode(value) {
53
+ if (typeof value !== "string") {
41
54
  return null;
42
55
  }
43
- let body;
44
- try {
45
- body = await response.json();
46
- } catch {
56
+ const code = value.toLowerCase();
57
+ return KNOWN_CODES.has(code) ? code : null;
58
+ }
59
+ function extractPSErrorBody(body) {
60
+ if (!isRecord(body)) {
47
61
  return null;
48
62
  }
49
- if (typeof body !== "object" || body === null) {
63
+ const nested = isRecord(body.error) ? body.error : null;
64
+ const code = normalizeCode(
65
+ nested?.errorCode ?? nested?.code ?? body.errorCode ?? body.code
66
+ );
67
+ const message = nested?.message ?? body.message;
68
+ if (!code || typeof message !== "string") {
50
69
  return null;
51
70
  }
52
- const { code, message } = body;
53
- if (typeof code !== "string" || typeof message !== "string") {
71
+ return { code, message };
72
+ }
73
+ async function parsePSError(response) {
74
+ if (response.ok) {
54
75
  return null;
55
76
  }
56
- if (!KNOWN_CODES.has(code)) {
77
+ let body;
78
+ try {
79
+ body = await response.json();
80
+ } catch {
57
81
  return null;
58
82
  }
59
- return new PSError(code, message);
83
+ const errorBody = extractPSErrorBody(body);
84
+ return errorBody ? new PSError(errorBody.code, errorBody.message) : null;
60
85
  }
61
86
  // Annotate the CommonJS export names for ESM import in node:
62
87
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/ps-errors.ts"],"sourcesContent":["/**\n * Typed errors returned by Personal Server endpoints.\n *\n * @remarks\n * Ported from the e2e POC sequence diagram — vana-connect (and other PS\n * clients) need to branch on a small, stable set of error codes. The PS\n * itself returns `{ code, message }` JSON on non-2xx responses.\n *\n * @category Auth\n */\n\n/** Stable error codes returned by Personal Server. */\nexport type PSErrorCode =\n | \"grant_invalid\"\n | \"grant_revoked\"\n | \"fee_required\"\n | \"ps_unavailable\";\n\n/** Typed error wrapping a non-2xx Personal Server response. */\nexport class PSError extends Error {\n constructor(\n public readonly code: PSErrorCode,\n message: string,\n ) {\n super(message);\n this.name = \"PSError\";\n }\n}\n\nconst KNOWN_CODES: ReadonlySet<PSErrorCode> = new Set<PSErrorCode>([\n \"grant_invalid\",\n \"grant_revoked\",\n \"fee_required\",\n \"ps_unavailable\",\n]);\n\n/**\n * Read a `{code, message}` JSON body from a non-2xx {@link Response} and\n * return the typed {@link PSError}.\n *\n * @returns A {@link PSError} for non-2xx responses with a recognised code,\n * or `null` for 2xx responses, malformed JSON, or unrecognised codes.\n */\nexport async function parsePSError(\n response: Response,\n): Promise<PSError | null> {\n if (response.ok) {\n return null;\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n if (typeof body !== \"object\" || body === null) {\n return null;\n }\n\n const { code, message } = body as { code?: unknown; message?: unknown };\n if (typeof code !== \"string\" || typeof message !== \"string\") {\n return null;\n }\n\n if (!KNOWN_CODES.has(code as PSErrorCode)) {\n return null;\n }\n\n return new PSError(code as PSErrorCode, message);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBO,MAAM,gBAAgB,MAAM;AAAA,EACjC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEA,MAAM,cAAwC,oBAAI,IAAiB;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASD,eAAsB,aACpB,UACyB;AACzB,MAAI,SAAS,IAAI;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,UAAU;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY,IAAI,IAAmB,GAAG;AACzC,WAAO;AAAA,EACT;AAEA,SAAO,IAAI,QAAQ,MAAqB,OAAO;AACjD;","names":[]}
1
+ {"version":3,"sources":["../../src/types/ps-errors.ts"],"sourcesContent":["/**\n * Typed errors returned by Personal Server endpoints.\n *\n * @remarks\n * vana-connect (and other PS clients) need to branch on a small, stable set\n * of lowercase error codes. Personal Server routes currently return protocol\n * errors as `{ error: { code, errorCode, message } }`, while older PoC\n * clients used `{ code, message }`; the parser accepts both shapes.\n *\n * @category Auth\n */\n\n/** Stable error codes returned by Personal Server. */\nexport type PSErrorCode =\n | \"missing_auth\"\n | \"invalid_signature\"\n | \"unregistered_builder\"\n | \"not_owner\"\n | \"expired_token\"\n | \"grant_invalid\"\n | \"grant_required\"\n | \"grant_expired\"\n | \"grant_revoked\"\n | \"scope_mismatch\"\n | \"fee_required\"\n | \"ps_unavailable\"\n | \"server_not_configured\"\n | \"content_too_large\";\n\n/** Typed error wrapping a non-2xx Personal Server response. */\nexport class PSError extends Error {\n constructor(\n public readonly code: PSErrorCode,\n message: string,\n ) {\n super(message);\n this.name = \"PSError\";\n }\n}\n\nconst KNOWN_CODES: ReadonlySet<PSErrorCode> = new Set<PSErrorCode>([\n \"missing_auth\",\n \"invalid_signature\",\n \"unregistered_builder\",\n \"not_owner\",\n \"expired_token\",\n \"grant_invalid\",\n \"grant_required\",\n \"grant_expired\",\n \"grant_revoked\",\n \"scope_mismatch\",\n \"fee_required\",\n \"ps_unavailable\",\n \"server_not_configured\",\n \"content_too_large\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction normalizeCode(value: unknown): PSErrorCode | null {\n if (typeof value !== \"string\") {\n return null;\n }\n const code = value.toLowerCase() as PSErrorCode;\n return KNOWN_CODES.has(code) ? code : null;\n}\n\nfunction extractPSErrorBody(\n body: unknown,\n): { code: PSErrorCode; message: string } | null {\n if (!isRecord(body)) {\n return null;\n }\n\n const nested = isRecord(body.error) ? body.error : null;\n const code = normalizeCode(\n nested?.errorCode ?? nested?.code ?? body.errorCode ?? body.code,\n );\n const message = nested?.message ?? body.message;\n\n if (!code || typeof message !== \"string\") {\n return null;\n }\n\n return { code, message };\n}\n\n/**\n * Read a Personal Server JSON error body from a non-2xx {@link Response} and\n * return the typed {@link PSError}. The returned code is always lowercase.\n *\n * @returns A {@link PSError} for non-2xx responses with a recognised code,\n * or `null` for 2xx responses, malformed JSON, or unrecognised codes.\n */\nexport async function parsePSError(\n response: Response,\n): Promise<PSError | null> {\n if (response.ok) {\n return null;\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n const errorBody = extractPSErrorBody(body);\n return errorBody ? new PSError(errorBody.code, errorBody.message) : null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BO,MAAM,gBAAgB,MAAM;AAAA,EACjC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEA,MAAM,cAAwC,oBAAI,IAAiB;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,SAAS,OAAkD;AAClE,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,cAAc,OAAoC;AACzD,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,OAAO,MAAM,YAAY;AAC/B,SAAO,YAAY,IAAI,IAAI,IAAI,OAAO;AACxC;AAEA,SAAS,mBACP,MAC+C;AAC/C,MAAI,CAAC,SAAS,IAAI,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,SAAS,KAAK,KAAK,IAAI,KAAK,QAAQ;AACnD,QAAM,OAAO;AAAA,IACX,QAAQ,aAAa,QAAQ,QAAQ,KAAK,aAAa,KAAK;AAAA,EAC9D;AACA,QAAM,UAAU,QAAQ,WAAW,KAAK;AAExC,MAAI,CAAC,QAAQ,OAAO,YAAY,UAAU;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,MAAM,QAAQ;AACzB;AASA,eAAsB,aACpB,UACyB;AACzB,MAAI,SAAS,IAAI;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,mBAAmB,IAAI;AACzC,SAAO,YAAY,IAAI,QAAQ,UAAU,MAAM,UAAU,OAAO,IAAI;AACtE;","names":[]}
@@ -2,22 +2,23 @@
2
2
  * Typed errors returned by Personal Server endpoints.
3
3
  *
4
4
  * @remarks
5
- * Ported from the e2e POC sequence diagram vana-connect (and other PS
6
- * clients) need to branch on a small, stable set of error codes. The PS
7
- * itself returns `{ code, message }` JSON on non-2xx responses.
5
+ * vana-connect (and other PS clients) need to branch on a small, stable set
6
+ * of lowercase error codes. Personal Server routes currently return protocol
7
+ * errors as `{ error: { code, errorCode, message } }`, while older PoC
8
+ * clients used `{ code, message }`; the parser accepts both shapes.
8
9
  *
9
10
  * @category Auth
10
11
  */
11
12
  /** Stable error codes returned by Personal Server. */
12
- export type PSErrorCode = "grant_invalid" | "grant_revoked" | "fee_required" | "ps_unavailable";
13
+ export type PSErrorCode = "missing_auth" | "invalid_signature" | "unregistered_builder" | "not_owner" | "expired_token" | "grant_invalid" | "grant_required" | "grant_expired" | "grant_revoked" | "scope_mismatch" | "fee_required" | "ps_unavailable" | "server_not_configured" | "content_too_large";
13
14
  /** Typed error wrapping a non-2xx Personal Server response. */
14
15
  export declare class PSError extends Error {
15
16
  readonly code: PSErrorCode;
16
17
  constructor(code: PSErrorCode, message: string);
17
18
  }
18
19
  /**
19
- * Read a `{code, message}` JSON body from a non-2xx {@link Response} and
20
- * return the typed {@link PSError}.
20
+ * Read a Personal Server JSON error body from a non-2xx {@link Response} and
21
+ * return the typed {@link PSError}. The returned code is always lowercase.
21
22
  *
22
23
  * @returns A {@link PSError} for non-2xx responses with a recognised code,
23
24
  * or `null` for 2xx responses, malformed JSON, or unrecognised codes.
@@ -7,32 +7,57 @@ class PSError extends Error {
7
7
  code;
8
8
  }
9
9
  const KNOWN_CODES = /* @__PURE__ */ new Set([
10
+ "missing_auth",
11
+ "invalid_signature",
12
+ "unregistered_builder",
13
+ "not_owner",
14
+ "expired_token",
10
15
  "grant_invalid",
16
+ "grant_required",
17
+ "grant_expired",
11
18
  "grant_revoked",
19
+ "scope_mismatch",
12
20
  "fee_required",
13
- "ps_unavailable"
21
+ "ps_unavailable",
22
+ "server_not_configured",
23
+ "content_too_large"
14
24
  ]);
15
- async function parsePSError(response) {
16
- if (response.ok) {
25
+ function isRecord(value) {
26
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ function normalizeCode(value) {
29
+ if (typeof value !== "string") {
17
30
  return null;
18
31
  }
19
- let body;
20
- try {
21
- body = await response.json();
22
- } catch {
32
+ const code = value.toLowerCase();
33
+ return KNOWN_CODES.has(code) ? code : null;
34
+ }
35
+ function extractPSErrorBody(body) {
36
+ if (!isRecord(body)) {
23
37
  return null;
24
38
  }
25
- if (typeof body !== "object" || body === null) {
39
+ const nested = isRecord(body.error) ? body.error : null;
40
+ const code = normalizeCode(
41
+ nested?.errorCode ?? nested?.code ?? body.errorCode ?? body.code
42
+ );
43
+ const message = nested?.message ?? body.message;
44
+ if (!code || typeof message !== "string") {
26
45
  return null;
27
46
  }
28
- const { code, message } = body;
29
- if (typeof code !== "string" || typeof message !== "string") {
47
+ return { code, message };
48
+ }
49
+ async function parsePSError(response) {
50
+ if (response.ok) {
30
51
  return null;
31
52
  }
32
- if (!KNOWN_CODES.has(code)) {
53
+ let body;
54
+ try {
55
+ body = await response.json();
56
+ } catch {
33
57
  return null;
34
58
  }
35
- return new PSError(code, message);
59
+ const errorBody = extractPSErrorBody(body);
60
+ return errorBody ? new PSError(errorBody.code, errorBody.message) : null;
36
61
  }
37
62
  export {
38
63
  PSError,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/ps-errors.ts"],"sourcesContent":["/**\n * Typed errors returned by Personal Server endpoints.\n *\n * @remarks\n * Ported from the e2e POC sequence diagram — vana-connect (and other PS\n * clients) need to branch on a small, stable set of error codes. The PS\n * itself returns `{ code, message }` JSON on non-2xx responses.\n *\n * @category Auth\n */\n\n/** Stable error codes returned by Personal Server. */\nexport type PSErrorCode =\n | \"grant_invalid\"\n | \"grant_revoked\"\n | \"fee_required\"\n | \"ps_unavailable\";\n\n/** Typed error wrapping a non-2xx Personal Server response. */\nexport class PSError extends Error {\n constructor(\n public readonly code: PSErrorCode,\n message: string,\n ) {\n super(message);\n this.name = \"PSError\";\n }\n}\n\nconst KNOWN_CODES: ReadonlySet<PSErrorCode> = new Set<PSErrorCode>([\n \"grant_invalid\",\n \"grant_revoked\",\n \"fee_required\",\n \"ps_unavailable\",\n]);\n\n/**\n * Read a `{code, message}` JSON body from a non-2xx {@link Response} and\n * return the typed {@link PSError}.\n *\n * @returns A {@link PSError} for non-2xx responses with a recognised code,\n * or `null` for 2xx responses, malformed JSON, or unrecognised codes.\n */\nexport async function parsePSError(\n response: Response,\n): Promise<PSError | null> {\n if (response.ok) {\n return null;\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n if (typeof body !== \"object\" || body === null) {\n return null;\n }\n\n const { code, message } = body as { code?: unknown; message?: unknown };\n if (typeof code !== \"string\" || typeof message !== \"string\") {\n return null;\n }\n\n if (!KNOWN_CODES.has(code as PSErrorCode)) {\n return null;\n }\n\n return new PSError(code as PSErrorCode, message);\n}\n"],"mappings":"AAmBO,MAAM,gBAAgB,MAAM;AAAA,EACjC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEA,MAAM,cAAwC,oBAAI,IAAiB;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASD,eAAsB,aACpB,UACyB;AACzB,MAAI,SAAS,IAAI;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,UAAU;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY,IAAI,IAAmB,GAAG;AACzC,WAAO;AAAA,EACT;AAEA,SAAO,IAAI,QAAQ,MAAqB,OAAO;AACjD;","names":[]}
1
+ {"version":3,"sources":["../../src/types/ps-errors.ts"],"sourcesContent":["/**\n * Typed errors returned by Personal Server endpoints.\n *\n * @remarks\n * vana-connect (and other PS clients) need to branch on a small, stable set\n * of lowercase error codes. Personal Server routes currently return protocol\n * errors as `{ error: { code, errorCode, message } }`, while older PoC\n * clients used `{ code, message }`; the parser accepts both shapes.\n *\n * @category Auth\n */\n\n/** Stable error codes returned by Personal Server. */\nexport type PSErrorCode =\n | \"missing_auth\"\n | \"invalid_signature\"\n | \"unregistered_builder\"\n | \"not_owner\"\n | \"expired_token\"\n | \"grant_invalid\"\n | \"grant_required\"\n | \"grant_expired\"\n | \"grant_revoked\"\n | \"scope_mismatch\"\n | \"fee_required\"\n | \"ps_unavailable\"\n | \"server_not_configured\"\n | \"content_too_large\";\n\n/** Typed error wrapping a non-2xx Personal Server response. */\nexport class PSError extends Error {\n constructor(\n public readonly code: PSErrorCode,\n message: string,\n ) {\n super(message);\n this.name = \"PSError\";\n }\n}\n\nconst KNOWN_CODES: ReadonlySet<PSErrorCode> = new Set<PSErrorCode>([\n \"missing_auth\",\n \"invalid_signature\",\n \"unregistered_builder\",\n \"not_owner\",\n \"expired_token\",\n \"grant_invalid\",\n \"grant_required\",\n \"grant_expired\",\n \"grant_revoked\",\n \"scope_mismatch\",\n \"fee_required\",\n \"ps_unavailable\",\n \"server_not_configured\",\n \"content_too_large\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction normalizeCode(value: unknown): PSErrorCode | null {\n if (typeof value !== \"string\") {\n return null;\n }\n const code = value.toLowerCase() as PSErrorCode;\n return KNOWN_CODES.has(code) ? code : null;\n}\n\nfunction extractPSErrorBody(\n body: unknown,\n): { code: PSErrorCode; message: string } | null {\n if (!isRecord(body)) {\n return null;\n }\n\n const nested = isRecord(body.error) ? body.error : null;\n const code = normalizeCode(\n nested?.errorCode ?? nested?.code ?? body.errorCode ?? body.code,\n );\n const message = nested?.message ?? body.message;\n\n if (!code || typeof message !== \"string\") {\n return null;\n }\n\n return { code, message };\n}\n\n/**\n * Read a Personal Server JSON error body from a non-2xx {@link Response} and\n * return the typed {@link PSError}. The returned code is always lowercase.\n *\n * @returns A {@link PSError} for non-2xx responses with a recognised code,\n * or `null` for 2xx responses, malformed JSON, or unrecognised codes.\n */\nexport async function parsePSError(\n response: Response,\n): Promise<PSError | null> {\n if (response.ok) {\n return null;\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n const errorBody = extractPSErrorBody(body);\n return errorBody ? new PSError(errorBody.code, errorBody.message) : null;\n}\n"],"mappings":"AA8BO,MAAM,gBAAgB,MAAM;AAAA,EACjC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEA,MAAM,cAAwC,oBAAI,IAAiB;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,SAAS,OAAkD;AAClE,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,cAAc,OAAoC;AACzD,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,OAAO,MAAM,YAAY;AAC/B,SAAO,YAAY,IAAI,IAAI,IAAI,OAAO;AACxC;AAEA,SAAS,mBACP,MAC+C;AAC/C,MAAI,CAAC,SAAS,IAAI,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,SAAS,KAAK,KAAK,IAAI,KAAK,QAAQ;AACnD,QAAM,OAAO;AAAA,IACX,QAAQ,aAAa,QAAQ,QAAQ,KAAK,aAAa,KAAK;AAAA,EAC9D;AACA,QAAM,UAAU,QAAQ,WAAW,KAAK;AAExC,MAAI,CAAC,QAAQ,OAAO,YAAY,UAAU;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,MAAM,QAAQ;AACzB;AASA,eAAsB,aACpB,UACyB;AACzB,MAAI,SAAS,IAAI;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,mBAAmB,IAAI;AACzC,SAAO,YAAY,IAAI,QAAQ,UAAU,MAAM,UAAU,OAAO,IAAI;AACtE;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/vana-sdk",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "A TypeScript library for interacting with Vana Network smart contracts.",
5
5
  "publishConfig": {
6
6
  "access": "public"