@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.
- package/dist/auth/web3-signed.cjs +28 -3
- package/dist/auth/web3-signed.cjs.map +1 -1
- package/dist/auth/web3-signed.js +28 -3
- package/dist/auth/web3-signed.js.map +1 -1
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +183 -15
- package/dist/index.browser.js.map +3 -3
- package/dist/index.node.cjs +186 -15
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +183 -15
- package/dist/index.node.js.map +3 -3
- package/dist/protocol/grants.cjs +146 -0
- package/dist/protocol/grants.cjs.map +1 -0
- package/dist/protocol/grants.d.ts +31 -0
- package/dist/protocol/grants.js +123 -0
- package/dist/protocol/grants.js.map +1 -0
- package/dist/protocol/grants.test.d.ts +1 -0
- package/dist/types/ps-errors.cjs +37 -12
- package/dist/types/ps-errors.cjs.map +1 -1
- package/dist/types/ps-errors.d.ts +7 -6
- package/dist/types/ps-errors.js +37 -12
- package/dist/types/ps-errors.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 {};
|
package/dist/types/ps-errors.cjs
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
71
|
+
return { code, message };
|
|
72
|
+
}
|
|
73
|
+
async function parsePSError(response) {
|
|
74
|
+
if (response.ok) {
|
|
54
75
|
return null;
|
|
55
76
|
}
|
|
56
|
-
|
|
77
|
+
let body;
|
|
78
|
+
try {
|
|
79
|
+
body = await response.json();
|
|
80
|
+
} catch {
|
|
57
81
|
return null;
|
|
58
82
|
}
|
|
59
|
-
|
|
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 *
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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.
|
package/dist/types/ps-errors.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
47
|
+
return { code, message };
|
|
48
|
+
}
|
|
49
|
+
async function parsePSError(response) {
|
|
50
|
+
if (response.ok) {
|
|
30
51
|
return null;
|
|
31
52
|
}
|
|
32
|
-
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
body = await response.json();
|
|
56
|
+
} catch {
|
|
33
57
|
return null;
|
|
34
58
|
}
|
|
35
|
-
|
|
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 *
|
|
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":[]}
|