@opendatalabs/vana-sdk 3.17.0 → 3.18.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/dist/direct/personal-server-read.cjs +20 -1
- package/dist/direct/personal-server-read.cjs.map +1 -1
- package/dist/direct/personal-server-read.js +23 -1
- package/dist/direct/personal-server-read.js.map +1 -1
- package/dist/errors.cjs +27 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.browser.d.ts +2 -1
- package/dist/index.browser.js +724 -309
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +736 -311
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +2 -1
- package/dist/index.node.js +724 -309
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/data-point-deletion.cjs +220 -0
- package/dist/protocol/data-point-deletion.cjs.map +1 -0
- package/dist/protocol/data-point-deletion.d.ts +180 -0
- package/dist/protocol/data-point-deletion.js +193 -0
- package/dist/protocol/data-point-deletion.js.map +1 -0
- package/dist/protocol/data-point-deletion.test.d.ts +1 -0
- package/dist/protocol/gateway.cjs +145 -32
- package/dist/protocol/gateway.cjs.map +1 -1
- package/dist/protocol/gateway.d.ts +42 -3
- package/dist/protocol/gateway.js +156 -32
- package/dist/protocol/gateway.js.map +1 -1
- package/dist/protocol/personal-server-data.cjs +20 -1
- package/dist/protocol/personal-server-data.cjs.map +1 -1
- package/dist/protocol/personal-server-data.js +23 -1
- package/dist/protocol/personal-server-data.js.map +1 -1
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/providers/vana-storage.cjs +66 -0
- package/dist/storage/providers/vana-storage.cjs.map +1 -1
- package/dist/storage/providers/vana-storage.d.ts +26 -0
- package/dist/storage/providers/vana-storage.js +66 -0
- package/dist/storage/providers/vana-storage.js.map +1 -1
- package/dist/utils/response-body.cjs +46 -0
- package/dist/utils/response-body.cjs.map +1 -0
- package/dist/utils/response-body.d.ts +26 -0
- package/dist/utils/response-body.js +20 -0
- package/dist/utils/response-body.js.map +1 -0
- package/dist/utils/response-body.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,220 @@
|
|
|
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 data_point_deletion_exports = {};
|
|
20
|
+
__export(data_point_deletion_exports, {
|
|
21
|
+
TOMBSTONE_DATA_HASH: () => TOMBSTONE_DATA_HASH,
|
|
22
|
+
TOMBSTONE_DATA_HASH_PREIMAGE: () => TOMBSTONE_DATA_HASH_PREIMAGE,
|
|
23
|
+
TOMBSTONE_METADATA_HASH: () => TOMBSTONE_METADATA_HASH,
|
|
24
|
+
TOMBSTONE_METADATA_HASH_PREIMAGE: () => TOMBSTONE_METADATA_HASH_PREIMAGE,
|
|
25
|
+
buildDataPointDeletionSignature: () => buildDataPointDeletionSignature,
|
|
26
|
+
buildDataPointDeletionTypedData: () => buildDataPointDeletionTypedData,
|
|
27
|
+
computeTombstoneHash: () => computeTombstoneHash,
|
|
28
|
+
createViemDataPointDeletionSigner: () => createViemDataPointDeletionSigner,
|
|
29
|
+
deleteDataPoint: () => deleteDataPoint,
|
|
30
|
+
isDataPointTombstone: () => isDataPointTombstone,
|
|
31
|
+
isTombstoneHashes: () => isTombstoneHashes,
|
|
32
|
+
tombstoneDeletedAt: () => tombstoneDeletedAt
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(data_point_deletion_exports);
|
|
35
|
+
var import_viem = require("viem");
|
|
36
|
+
var import_errors = require("../errors");
|
|
37
|
+
var import_response_body = require("../utils/response-body");
|
|
38
|
+
var import_eip712 = require("./eip712");
|
|
39
|
+
var import_lineage = require("./lineage");
|
|
40
|
+
const TOMBSTONE_DATA_HASH_PREIMAGE = "vana.data-point.tombstone.v1";
|
|
41
|
+
const TOMBSTONE_METADATA_HASH_PREIMAGE = "vana.data-point.tombstone.metadata.v1";
|
|
42
|
+
const TOMBSTONE_DATA_HASH = "0x30c45ee72fe56d1927701316925ab7ceacd3b6f9267061735d59396f075c6222";
|
|
43
|
+
const TOMBSTONE_METADATA_HASH = "0xc5255a141acd6a2ae55971b62c0a85977c2511989dc114ad2abc2b7644f57d90";
|
|
44
|
+
function computeTombstoneHash(preimage) {
|
|
45
|
+
return (0, import_viem.keccak256)((0, import_viem.stringToBytes)(preimage));
|
|
46
|
+
}
|
|
47
|
+
function isTombstoneHashes(dataHash, metadataHash) {
|
|
48
|
+
return typeof dataHash === "string" && typeof metadataHash === "string" && dataHash.toLowerCase() === TOMBSTONE_DATA_HASH && metadataHash.toLowerCase() === TOMBSTONE_METADATA_HASH;
|
|
49
|
+
}
|
|
50
|
+
function isDataPointTombstone(value) {
|
|
51
|
+
if (!(0, import_response_body.isPlainObject)(value)) return false;
|
|
52
|
+
if (typeof value["deletedAt"] === "string") return true;
|
|
53
|
+
return isTombstoneHashes(
|
|
54
|
+
typeof value["dataHash"] === "string" ? value["dataHash"] : void 0,
|
|
55
|
+
typeof value["metadataHash"] === "string" ? value["metadataHash"] : void 0
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
function tombstoneDeletedAt(value) {
|
|
59
|
+
if (!(0, import_response_body.isPlainObject)(value)) return null;
|
|
60
|
+
const deletedAt = value["deletedAt"];
|
|
61
|
+
return typeof deletedAt === "string" ? deletedAt : null;
|
|
62
|
+
}
|
|
63
|
+
function assertAddress(value, name) {
|
|
64
|
+
if (!(0, import_viem.isAddress)(value)) {
|
|
65
|
+
throw new Error(`${name} must be a valid EVM address`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function assertUint256(value, name) {
|
|
69
|
+
if (value < 0n || value > import_viem.maxUint256) {
|
|
70
|
+
throw new Error(`${name} must fit in uint256, got ${value}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getAccountAddress(account) {
|
|
74
|
+
if (!account) return void 0;
|
|
75
|
+
return typeof account === "string" ? account : account.address;
|
|
76
|
+
}
|
|
77
|
+
function isDataPointDeletionSigner(source) {
|
|
78
|
+
return "address" in source && typeof source.signTypedData === "function";
|
|
79
|
+
}
|
|
80
|
+
function createViemDataPointDeletionSigner(source, options = {}) {
|
|
81
|
+
if (isDataPointDeletionSigner(source)) {
|
|
82
|
+
return source;
|
|
83
|
+
}
|
|
84
|
+
const accountAddress = getAccountAddress(options.account) ?? getAccountAddress(source.account);
|
|
85
|
+
if (accountAddress) {
|
|
86
|
+
return {
|
|
87
|
+
address: accountAddress,
|
|
88
|
+
signTypedData: (typedData) => source.signTypedData({
|
|
89
|
+
...typedData,
|
|
90
|
+
account: options.account ?? source.account ?? accountAddress
|
|
91
|
+
})
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
throw new Error(
|
|
95
|
+
"Viem wallet client requires an account option or account property"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function buildDataPointDeletionTypedData(input) {
|
|
99
|
+
assertAddress(input.ownerAddress, "ownerAddress");
|
|
100
|
+
if (input.scope.length === 0) {
|
|
101
|
+
throw new Error("scope must be a non-empty string");
|
|
102
|
+
}
|
|
103
|
+
if (input.expectedVersion <= 0n) {
|
|
104
|
+
throw new Error("expectedVersion must be a positive version number");
|
|
105
|
+
}
|
|
106
|
+
assertUint256(input.expectedVersion, "expectedVersion");
|
|
107
|
+
return {
|
|
108
|
+
domain: (0, import_eip712.dataRegistryDomain)(input.config),
|
|
109
|
+
types: import_eip712.ADD_DATA_TYPES,
|
|
110
|
+
primaryType: "AddData",
|
|
111
|
+
message: {
|
|
112
|
+
ownerAddress: input.ownerAddress,
|
|
113
|
+
scope: input.scope,
|
|
114
|
+
dataHash: TOMBSTONE_DATA_HASH,
|
|
115
|
+
metadataHash: TOMBSTONE_METADATA_HASH,
|
|
116
|
+
expectedVersion: input.expectedVersion
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function buildDataPointDeletionSignature(input) {
|
|
121
|
+
const typedData = buildDataPointDeletionTypedData({
|
|
122
|
+
ownerAddress: input.signer.address,
|
|
123
|
+
scope: input.scope,
|
|
124
|
+
expectedVersion: input.expectedVersion,
|
|
125
|
+
config: input.config
|
|
126
|
+
});
|
|
127
|
+
const signature = await input.signer.signTypedData(typedData);
|
|
128
|
+
return {
|
|
129
|
+
signature,
|
|
130
|
+
signerAddress: input.signer.address,
|
|
131
|
+
typedData
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function toError(value) {
|
|
135
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
136
|
+
}
|
|
137
|
+
function parseExpectedVersion(value) {
|
|
138
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Gateway returned a malformed expectedVersion: ${JSON.stringify(value)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const parsed = BigInt(value);
|
|
144
|
+
assertUint256(parsed, "Gateway expectedVersion");
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
async function deleteDataPoint(input) {
|
|
148
|
+
const ownerAddress = input.signer.address;
|
|
149
|
+
const dataPointId = (0, import_lineage.deriveDataPointId)(ownerAddress, input.scope);
|
|
150
|
+
let currentVersion = input.currentVersion;
|
|
151
|
+
if (currentVersion === void 0) {
|
|
152
|
+
const record = await input.gateway.getDataPoint(dataPointId);
|
|
153
|
+
if (record === null) {
|
|
154
|
+
throw new import_errors.DataPointNotFoundError(
|
|
155
|
+
`No data point registered for scope '${input.scope}' owned by ${ownerAddress}`,
|
|
156
|
+
{ dataPointId, scope: input.scope, ownerAddress }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (isDataPointTombstone(record)) {
|
|
160
|
+
throw new import_errors.DataPointDeletedError(
|
|
161
|
+
`Data point ${dataPointId} (scope '${input.scope}') is already deleted`,
|
|
162
|
+
{
|
|
163
|
+
dataPointId,
|
|
164
|
+
scope: input.scope,
|
|
165
|
+
ownerAddress,
|
|
166
|
+
deletedAt: tombstoneDeletedAt(record)
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
currentVersion = parseExpectedVersion(record.expectedVersion);
|
|
171
|
+
}
|
|
172
|
+
if (currentVersion < 0n || currentVersion >= import_viem.maxUint256) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`currentVersion ${currentVersion} cannot be incremented to a uint256 tombstone version`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const tombstoneVersion = currentVersion + 1n;
|
|
178
|
+
const signed = await buildDataPointDeletionSignature({
|
|
179
|
+
signer: input.signer,
|
|
180
|
+
scope: input.scope,
|
|
181
|
+
expectedVersion: tombstoneVersion,
|
|
182
|
+
config: input.config
|
|
183
|
+
});
|
|
184
|
+
const tombstone = await input.gateway.deleteDataPoint({
|
|
185
|
+
ownerAddress,
|
|
186
|
+
scope: input.scope,
|
|
187
|
+
expectedVersion: tombstoneVersion.toString(),
|
|
188
|
+
signature: signed.signature
|
|
189
|
+
});
|
|
190
|
+
const base = {
|
|
191
|
+
dataPointId,
|
|
192
|
+
ownerAddress,
|
|
193
|
+
scope: input.scope,
|
|
194
|
+
version: tombstoneVersion.toString(),
|
|
195
|
+
signature: signed.signature,
|
|
196
|
+
tombstone
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
const storage = await input.storage.deleteScope(ownerAddress, input.scope);
|
|
200
|
+
return { ...base, status: "deleted", storage };
|
|
201
|
+
} catch (cause) {
|
|
202
|
+
return { ...base, status: "partial", storageError: toError(cause) };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
TOMBSTONE_DATA_HASH,
|
|
208
|
+
TOMBSTONE_DATA_HASH_PREIMAGE,
|
|
209
|
+
TOMBSTONE_METADATA_HASH,
|
|
210
|
+
TOMBSTONE_METADATA_HASH_PREIMAGE,
|
|
211
|
+
buildDataPointDeletionSignature,
|
|
212
|
+
buildDataPointDeletionTypedData,
|
|
213
|
+
computeTombstoneHash,
|
|
214
|
+
createViemDataPointDeletionSigner,
|
|
215
|
+
deleteDataPoint,
|
|
216
|
+
isDataPointTombstone,
|
|
217
|
+
isTombstoneHashes,
|
|
218
|
+
tombstoneDeletedAt
|
|
219
|
+
});
|
|
220
|
+
//# sourceMappingURL=data-point-deletion.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/data-point-deletion.ts"],"sourcesContent":["/**\n * Data point deletion (tombstone) helpers for DataRegistryV2.\n *\n * A deletion is an owner-signed `AddData` for version `current + 1` whose\n * `dataHash` / `metadataHash` are the protocol-wide tombstone constants below.\n * The gateway records the tombstone version and marks the row `deletedAt`;\n * the encrypted blobs in vana-storage are removed afterwards as a best-effort\n * follow-up. The tombstone is the durable fact; blob removal is cleanup.\n *\n * The typed-data definition is exactly {@link ADD_DATA_TYPES} against\n * {@link dataRegistryDomain} -- the same signature the registration path uses,\n * so a verifier cannot tell a deletion apart from a registration except by\n * the hashes.\n *\n * @category Protocol\n */\n\nimport {\n isAddress,\n keccak256,\n maxUint256,\n stringToBytes,\n type Account,\n type Address,\n type Hex,\n type TypedDataDefinition,\n} from \"viem\";\nimport { DataPointDeletedError, DataPointNotFoundError } from \"../errors\";\nimport { isPlainObject } from \"../utils/response-body\";\nimport {\n ADD_DATA_TYPES,\n dataRegistryDomain,\n type AddDataMessage,\n type DataPortabilityGatewayConfig,\n} from \"./eip712\";\nimport type {\n DeleteDataPointResult as GatewayDeleteDataPointResult,\n GatewayClient,\n} from \"./gateway\";\nimport { deriveDataPointId } from \"./lineage\";\n\n/** UTF-8 preimage of {@link TOMBSTONE_DATA_HASH}. */\nexport const TOMBSTONE_DATA_HASH_PREIMAGE = \"vana.data-point.tombstone.v1\";\n/** UTF-8 preimage of {@link TOMBSTONE_METADATA_HASH}. */\nexport const TOMBSTONE_METADATA_HASH_PREIMAGE =\n \"vana.data-point.tombstone.metadata.v1\";\n\n/**\n * `keccak256(utf8(\"vana.data-point.tombstone.v1\"))` -- the `dataHash` a\n * deletion AddData carries. Single source of truth shared with the gateway\n * and Personal Server; never change without a new `.vN` preimage.\n */\nexport const TOMBSTONE_DATA_HASH =\n \"0x30c45ee72fe56d1927701316925ab7ceacd3b6f9267061735d59396f075c6222\" as const;\n\n/**\n * `keccak256(utf8(\"vana.data-point.tombstone.metadata.v1\"))` -- the\n * `metadataHash` a deletion AddData carries.\n */\nexport const TOMBSTONE_METADATA_HASH =\n \"0xc5255a141acd6a2ae55971b62c0a85977c2511989dc114ad2abc2b7644f57d90\" as const;\n\n/**\n * Recompute a tombstone hash from its preimage. Exposed so tests and\n * downstream services can prove the pinned constants match the contract.\n */\nexport function computeTombstoneHash(preimage: string): Hex {\n return keccak256(stringToBytes(preimage));\n}\n\n/** True when the pair of hashes is exactly the tombstone pair. */\nexport function isTombstoneHashes(\n dataHash: string | undefined,\n metadataHash: string | undefined,\n): boolean {\n return (\n typeof dataHash === \"string\" &&\n typeof metadataHash === \"string\" &&\n dataHash.toLowerCase() === TOMBSTONE_DATA_HASH &&\n metadataHash.toLowerCase() === TOMBSTONE_METADATA_HASH\n );\n}\n\n/**\n * Detect a deleted data point from any record-shaped value: a non-null\n * `deletedAt`, or the tombstone hash pair. Accepts `unknown` so read\n * helpers can run it on a raw JSON body before trusting the payload.\n */\nexport function isDataPointTombstone(value: unknown): boolean {\n if (!isPlainObject(value)) return false;\n if (typeof value[\"deletedAt\"] === \"string\") return true;\n return isTombstoneHashes(\n typeof value[\"dataHash\"] === \"string\" ? value[\"dataHash\"] : undefined,\n typeof value[\"metadataHash\"] === \"string\"\n ? value[\"metadataHash\"]\n : undefined,\n );\n}\n\n/**\n * Read `deletedAt` off any value a gateway or Personal Server might hand\n * back (a record, an error body, `null`, an array, ...). Returns `null`\n * unless it is a string, so every `DataPointDeletedError` carries the same\n * `deletedAt` semantics regardless of which read path raised it.\n */\nexport function tombstoneDeletedAt(value: unknown): string | null {\n if (!isPlainObject(value)) return null;\n const deletedAt = value[\"deletedAt\"];\n return typeof deletedAt === \"string\" ? deletedAt : null;\n}\n\nexport type DataPointDeletionTypedData = TypedDataDefinition<\n typeof ADD_DATA_TYPES,\n \"AddData\"\n> & {\n message: AddDataMessage;\n};\n\nexport interface DataPointDeletionSigner {\n /** The data point owner. Must match `AddData.ownerAddress`. */\n address: Address;\n signTypedData(typedData: DataPointDeletionTypedData): Promise<Hex> | Hex;\n}\n\nexport interface ViemDataPointDeletionWalletClient {\n account?: Account | Address | null;\n signTypedData(\n typedData: DataPointDeletionTypedData & { account?: Account | Address },\n ): Promise<Hex>;\n}\n\nexport type ViemDataPointDeletionSignerSource =\n | DataPointDeletionSigner\n | ViemDataPointDeletionWalletClient;\n\nexport interface BuildDataPointDeletionTypedDataInput {\n ownerAddress: Address;\n scope: string;\n /**\n * The version the tombstone is written at -- `current + 1`. This is the\n * `AddData.expectedVersion` field; the gateway rejects with 409 unless it\n * is strictly greater than the stored version.\n */\n expectedVersion: bigint;\n config: DataPortabilityGatewayConfig;\n}\n\nexport interface BuildDataPointDeletionSignatureInput {\n signer: DataPointDeletionSigner;\n scope: string;\n /** See {@link BuildDataPointDeletionTypedDataInput.expectedVersion}. */\n expectedVersion: bigint;\n config: DataPortabilityGatewayConfig;\n}\n\nexport interface DataPointDeletionSignature {\n signature: Hex;\n signerAddress: Address;\n typedData: DataPointDeletionTypedData;\n}\n\nfunction assertAddress(value: string, name: string): void {\n if (!isAddress(value)) {\n throw new Error(`${name} must be a valid EVM address`);\n }\n}\n\n// `AddData.expectedVersion` is a uint256 on the contract. A bigint has no\n// such bound, so every version the SDK signs or increments is checked here\n// rather than trusting the TypeScript type.\nfunction assertUint256(value: bigint, name: string): void {\n if (value < 0n || value > maxUint256) {\n throw new Error(`${name} must fit in uint256, got ${value}`);\n }\n}\n\nfunction getAccountAddress(\n account: Account | Address | null | undefined,\n): Address | undefined {\n if (!account) return undefined;\n return typeof account === \"string\" ? account : account.address;\n}\n\nfunction isDataPointDeletionSigner(\n source: ViemDataPointDeletionSignerSource,\n): source is DataPointDeletionSigner {\n return \"address\" in source && typeof source.signTypedData === \"function\";\n}\n\n/**\n * Adapt a viem local account or wallet client to\n * {@link DataPointDeletionSigner}. Mirrors\n * `createViemPersonalServerRegistrationSigner`.\n */\nexport function createViemDataPointDeletionSigner(\n source: ViemDataPointDeletionSignerSource,\n options: { account?: Account | Address } = {},\n): DataPointDeletionSigner {\n if (isDataPointDeletionSigner(source)) {\n return source;\n }\n\n const accountAddress =\n getAccountAddress(options.account) ?? getAccountAddress(source.account);\n\n if (accountAddress) {\n return {\n address: accountAddress,\n signTypedData: (typedData) =>\n source.signTypedData({\n ...typedData,\n account: options.account ?? source.account ?? accountAddress,\n }),\n };\n }\n\n throw new Error(\n \"Viem wallet client requires an account option or account property\",\n );\n}\n\n/**\n * Build the exact `AddData` typed data a deletion signs: the tombstone hash\n * pair at `expectedVersion`, against the DataRegistryV2 domain.\n */\nexport function buildDataPointDeletionTypedData(\n input: BuildDataPointDeletionTypedDataInput,\n): DataPointDeletionTypedData {\n assertAddress(input.ownerAddress, \"ownerAddress\");\n if (input.scope.length === 0) {\n throw new Error(\"scope must be a non-empty string\");\n }\n if (input.expectedVersion <= 0n) {\n throw new Error(\"expectedVersion must be a positive version number\");\n }\n assertUint256(input.expectedVersion, \"expectedVersion\");\n\n return {\n domain: dataRegistryDomain(input.config),\n types: ADD_DATA_TYPES,\n primaryType: \"AddData\",\n message: {\n ownerAddress: input.ownerAddress,\n scope: input.scope,\n dataHash: TOMBSTONE_DATA_HASH,\n metadataHash: TOMBSTONE_METADATA_HASH,\n expectedVersion: input.expectedVersion,\n },\n };\n}\n\n/** Sign a deletion `AddData` with the owner's signer. */\nexport async function buildDataPointDeletionSignature(\n input: BuildDataPointDeletionSignatureInput,\n): Promise<DataPointDeletionSignature> {\n const typedData = buildDataPointDeletionTypedData({\n ownerAddress: input.signer.address,\n scope: input.scope,\n expectedVersion: input.expectedVersion,\n config: input.config,\n });\n const signature = await input.signer.signTypedData(typedData);\n\n return {\n signature,\n signerAddress: input.signer.address,\n typedData,\n };\n}\n\n/**\n * Outcome of a scope-wide blob delete. `VanaStorage.deleteScope` returns a\n * superset of this shape.\n */\nexport interface DataPointBlobDeleteResult {\n count: number;\n totalBytes: number;\n}\n\n/**\n * The storage half of a deletion -- structurally satisfied by `VanaStorage`.\n * Removes every version's blob under `(owner, scope)`.\n */\nexport interface DataPointBlobStore {\n deleteScope(\n ownerAddress: Address,\n scope: string,\n ): Promise<DataPointBlobDeleteResult>;\n}\n\nexport interface DeleteDataPointInput {\n /** Gateway client -- only `getDataPoint` and `deleteDataPoint` are used. */\n gateway: Pick<GatewayClient, \"getDataPoint\" | \"deleteDataPoint\">;\n /** Blob store for the best-effort cleanup after the tombstone lands. */\n storage: DataPointBlobStore;\n /** Owner signer. `signer.address` is the data point owner. */\n signer: DataPointDeletionSigner;\n scope: string;\n config: DataPortabilityGatewayConfig;\n /**\n * Skip the gateway lookup and treat this as the current version. Use when\n * the caller already holds a fresh `DataPointRecord.expectedVersion`;\n * omit to let the SDK fetch it.\n */\n currentVersion?: bigint;\n}\n\ninterface DataPointDeletionBase {\n dataPointId: Hex;\n ownerAddress: Address;\n scope: string;\n /** Decimal-string uint256 of the tombstone version (`current + 1`). */\n version: string;\n signature: Hex;\n /** The gateway's 200 body for the tombstone. */\n tombstone: GatewayDeleteDataPointResult;\n}\n\n/** Both the gateway tombstone and the blob cleanup succeeded. */\nexport interface DataPointDeletedResult extends DataPointDeletionBase {\n status: \"deleted\";\n storage: DataPointBlobDeleteResult;\n}\n\n/**\n * The gateway recorded the tombstone (durable, the data point is deleted)\n * but the blob cleanup failed. Retry `storage.deleteScope(owner, scope)`\n * later; the tombstone does not need to be re-signed.\n */\nexport interface DataPointDeletionPartialResult extends DataPointDeletionBase {\n status: \"partial\";\n storageError: Error;\n}\n\nexport type DataPointDeletionResult =\n | DataPointDeletedResult\n | DataPointDeletionPartialResult;\n\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\n// The wire type says decimal-string uint256, but the type system cannot\n// enforce that at runtime: refuse anything else here rather than letting\n// `BigInt()` throw a bare SyntaxError (or, worse, sign for a bogus version).\nfunction parseExpectedVersion(value: unknown): bigint {\n if (typeof value !== \"string\" || !/^\\d+$/.test(value)) {\n throw new Error(\n `Gateway returned a malformed expectedVersion: ${JSON.stringify(value)}`,\n );\n }\n const parsed = BigInt(value);\n assertUint256(parsed, \"Gateway expectedVersion\");\n return parsed;\n}\n\n/**\n * Delete a data point end to end. Symmetric with `registerDataPoint`.\n *\n * Order, deliberately:\n * 1. fetch the current version from the gateway (unless supplied),\n * 2. sign the tombstone `AddData` for `current + 1`,\n * 3. `DELETE /v1/data/:dataPointId` on the gateway,\n * 4. delete every blob under `(owner, scope)` in vana-storage.\n *\n * Steps 1-3 throw on failure ({@link DataPointNotFoundError},\n * {@link DataPointDeletedError}, `DataPointVersionConflictError`, or a\n * generic gateway error). A step-4 failure does NOT throw: the tombstone is\n * already the durable fact, so the result comes back with\n * `status: \"partial\"` and the storage error attached.\n */\nexport async function deleteDataPoint(\n input: DeleteDataPointInput,\n): Promise<DataPointDeletionResult> {\n const ownerAddress = input.signer.address;\n const dataPointId = deriveDataPointId(ownerAddress, input.scope);\n\n let currentVersion = input.currentVersion;\n if (currentVersion === undefined) {\n const record = await input.gateway.getDataPoint(dataPointId);\n if (record === null) {\n throw new DataPointNotFoundError(\n `No data point registered for scope '${input.scope}' owned by ${ownerAddress}`,\n { dataPointId, scope: input.scope, ownerAddress },\n );\n }\n if (isDataPointTombstone(record)) {\n throw new DataPointDeletedError(\n `Data point ${dataPointId} (scope '${input.scope}') is already deleted`,\n {\n dataPointId,\n scope: input.scope,\n ownerAddress,\n deletedAt: tombstoneDeletedAt(record),\n },\n );\n }\n currentVersion = parseExpectedVersion(record.expectedVersion);\n }\n\n // Whichever source supplied it, `current + 1` must itself be a uint256:\n // a negative current version would sign for 0 or wrap, and\n // 2^256 - 1 cannot be incremented at all.\n if (currentVersion < 0n || currentVersion >= maxUint256) {\n throw new Error(\n `currentVersion ${currentVersion} cannot be incremented to a uint256 tombstone version`,\n );\n }\n const tombstoneVersion = currentVersion + 1n;\n const signed = await buildDataPointDeletionSignature({\n signer: input.signer,\n scope: input.scope,\n expectedVersion: tombstoneVersion,\n config: input.config,\n });\n\n const tombstone = await input.gateway.deleteDataPoint({\n ownerAddress,\n scope: input.scope,\n expectedVersion: tombstoneVersion.toString(),\n signature: signed.signature,\n });\n\n const base: DataPointDeletionBase = {\n dataPointId,\n ownerAddress,\n scope: input.scope,\n version: tombstoneVersion.toString(),\n signature: signed.signature,\n tombstone,\n };\n\n try {\n const storage = await input.storage.deleteScope(ownerAddress, input.scope);\n return { ...base, status: \"deleted\", storage };\n } catch (cause) {\n return { ...base, status: \"partial\", storageError: toError(cause) };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data point deletion (tombstone) helpers for DataRegistryV2.
|
|
3
|
+
*
|
|
4
|
+
* A deletion is an owner-signed `AddData` for version `current + 1` whose
|
|
5
|
+
* `dataHash` / `metadataHash` are the protocol-wide tombstone constants below.
|
|
6
|
+
* The gateway records the tombstone version and marks the row `deletedAt`;
|
|
7
|
+
* the encrypted blobs in vana-storage are removed afterwards as a best-effort
|
|
8
|
+
* follow-up. The tombstone is the durable fact; blob removal is cleanup.
|
|
9
|
+
*
|
|
10
|
+
* The typed-data definition is exactly {@link ADD_DATA_TYPES} against
|
|
11
|
+
* {@link dataRegistryDomain} -- the same signature the registration path uses,
|
|
12
|
+
* so a verifier cannot tell a deletion apart from a registration except by
|
|
13
|
+
* the hashes.
|
|
14
|
+
*
|
|
15
|
+
* @category Protocol
|
|
16
|
+
*/
|
|
17
|
+
import { type Account, type Address, type Hex, type TypedDataDefinition } from "viem";
|
|
18
|
+
import { ADD_DATA_TYPES, type AddDataMessage, type DataPortabilityGatewayConfig } from "./eip712.js";
|
|
19
|
+
import type { DeleteDataPointResult as GatewayDeleteDataPointResult, GatewayClient } from "./gateway.js";
|
|
20
|
+
/** UTF-8 preimage of {@link TOMBSTONE_DATA_HASH}. */
|
|
21
|
+
export declare const TOMBSTONE_DATA_HASH_PREIMAGE = "vana.data-point.tombstone.v1";
|
|
22
|
+
/** UTF-8 preimage of {@link TOMBSTONE_METADATA_HASH}. */
|
|
23
|
+
export declare const TOMBSTONE_METADATA_HASH_PREIMAGE = "vana.data-point.tombstone.metadata.v1";
|
|
24
|
+
/**
|
|
25
|
+
* `keccak256(utf8("vana.data-point.tombstone.v1"))` -- the `dataHash` a
|
|
26
|
+
* deletion AddData carries. Single source of truth shared with the gateway
|
|
27
|
+
* and Personal Server; never change without a new `.vN` preimage.
|
|
28
|
+
*/
|
|
29
|
+
export declare const TOMBSTONE_DATA_HASH: "0x30c45ee72fe56d1927701316925ab7ceacd3b6f9267061735d59396f075c6222";
|
|
30
|
+
/**
|
|
31
|
+
* `keccak256(utf8("vana.data-point.tombstone.metadata.v1"))` -- the
|
|
32
|
+
* `metadataHash` a deletion AddData carries.
|
|
33
|
+
*/
|
|
34
|
+
export declare const TOMBSTONE_METADATA_HASH: "0xc5255a141acd6a2ae55971b62c0a85977c2511989dc114ad2abc2b7644f57d90";
|
|
35
|
+
/**
|
|
36
|
+
* Recompute a tombstone hash from its preimage. Exposed so tests and
|
|
37
|
+
* downstream services can prove the pinned constants match the contract.
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeTombstoneHash(preimage: string): Hex;
|
|
40
|
+
/** True when the pair of hashes is exactly the tombstone pair. */
|
|
41
|
+
export declare function isTombstoneHashes(dataHash: string | undefined, metadataHash: string | undefined): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Detect a deleted data point from any record-shaped value: a non-null
|
|
44
|
+
* `deletedAt`, or the tombstone hash pair. Accepts `unknown` so read
|
|
45
|
+
* helpers can run it on a raw JSON body before trusting the payload.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isDataPointTombstone(value: unknown): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Read `deletedAt` off any value a gateway or Personal Server might hand
|
|
50
|
+
* back (a record, an error body, `null`, an array, ...). Returns `null`
|
|
51
|
+
* unless it is a string, so every `DataPointDeletedError` carries the same
|
|
52
|
+
* `deletedAt` semantics regardless of which read path raised it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function tombstoneDeletedAt(value: unknown): string | null;
|
|
55
|
+
export type DataPointDeletionTypedData = TypedDataDefinition<typeof ADD_DATA_TYPES, "AddData"> & {
|
|
56
|
+
message: AddDataMessage;
|
|
57
|
+
};
|
|
58
|
+
export interface DataPointDeletionSigner {
|
|
59
|
+
/** The data point owner. Must match `AddData.ownerAddress`. */
|
|
60
|
+
address: Address;
|
|
61
|
+
signTypedData(typedData: DataPointDeletionTypedData): Promise<Hex> | Hex;
|
|
62
|
+
}
|
|
63
|
+
export interface ViemDataPointDeletionWalletClient {
|
|
64
|
+
account?: Account | Address | null;
|
|
65
|
+
signTypedData(typedData: DataPointDeletionTypedData & {
|
|
66
|
+
account?: Account | Address;
|
|
67
|
+
}): Promise<Hex>;
|
|
68
|
+
}
|
|
69
|
+
export type ViemDataPointDeletionSignerSource = DataPointDeletionSigner | ViemDataPointDeletionWalletClient;
|
|
70
|
+
export interface BuildDataPointDeletionTypedDataInput {
|
|
71
|
+
ownerAddress: Address;
|
|
72
|
+
scope: string;
|
|
73
|
+
/**
|
|
74
|
+
* The version the tombstone is written at -- `current + 1`. This is the
|
|
75
|
+
* `AddData.expectedVersion` field; the gateway rejects with 409 unless it
|
|
76
|
+
* is strictly greater than the stored version.
|
|
77
|
+
*/
|
|
78
|
+
expectedVersion: bigint;
|
|
79
|
+
config: DataPortabilityGatewayConfig;
|
|
80
|
+
}
|
|
81
|
+
export interface BuildDataPointDeletionSignatureInput {
|
|
82
|
+
signer: DataPointDeletionSigner;
|
|
83
|
+
scope: string;
|
|
84
|
+
/** See {@link BuildDataPointDeletionTypedDataInput.expectedVersion}. */
|
|
85
|
+
expectedVersion: bigint;
|
|
86
|
+
config: DataPortabilityGatewayConfig;
|
|
87
|
+
}
|
|
88
|
+
export interface DataPointDeletionSignature {
|
|
89
|
+
signature: Hex;
|
|
90
|
+
signerAddress: Address;
|
|
91
|
+
typedData: DataPointDeletionTypedData;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adapt a viem local account or wallet client to
|
|
95
|
+
* {@link DataPointDeletionSigner}. Mirrors
|
|
96
|
+
* `createViemPersonalServerRegistrationSigner`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function createViemDataPointDeletionSigner(source: ViemDataPointDeletionSignerSource, options?: {
|
|
99
|
+
account?: Account | Address;
|
|
100
|
+
}): DataPointDeletionSigner;
|
|
101
|
+
/**
|
|
102
|
+
* Build the exact `AddData` typed data a deletion signs: the tombstone hash
|
|
103
|
+
* pair at `expectedVersion`, against the DataRegistryV2 domain.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildDataPointDeletionTypedData(input: BuildDataPointDeletionTypedDataInput): DataPointDeletionTypedData;
|
|
106
|
+
/** Sign a deletion `AddData` with the owner's signer. */
|
|
107
|
+
export declare function buildDataPointDeletionSignature(input: BuildDataPointDeletionSignatureInput): Promise<DataPointDeletionSignature>;
|
|
108
|
+
/**
|
|
109
|
+
* Outcome of a scope-wide blob delete. `VanaStorage.deleteScope` returns a
|
|
110
|
+
* superset of this shape.
|
|
111
|
+
*/
|
|
112
|
+
export interface DataPointBlobDeleteResult {
|
|
113
|
+
count: number;
|
|
114
|
+
totalBytes: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The storage half of a deletion -- structurally satisfied by `VanaStorage`.
|
|
118
|
+
* Removes every version's blob under `(owner, scope)`.
|
|
119
|
+
*/
|
|
120
|
+
export interface DataPointBlobStore {
|
|
121
|
+
deleteScope(ownerAddress: Address, scope: string): Promise<DataPointBlobDeleteResult>;
|
|
122
|
+
}
|
|
123
|
+
export interface DeleteDataPointInput {
|
|
124
|
+
/** Gateway client -- only `getDataPoint` and `deleteDataPoint` are used. */
|
|
125
|
+
gateway: Pick<GatewayClient, "getDataPoint" | "deleteDataPoint">;
|
|
126
|
+
/** Blob store for the best-effort cleanup after the tombstone lands. */
|
|
127
|
+
storage: DataPointBlobStore;
|
|
128
|
+
/** Owner signer. `signer.address` is the data point owner. */
|
|
129
|
+
signer: DataPointDeletionSigner;
|
|
130
|
+
scope: string;
|
|
131
|
+
config: DataPortabilityGatewayConfig;
|
|
132
|
+
/**
|
|
133
|
+
* Skip the gateway lookup and treat this as the current version. Use when
|
|
134
|
+
* the caller already holds a fresh `DataPointRecord.expectedVersion`;
|
|
135
|
+
* omit to let the SDK fetch it.
|
|
136
|
+
*/
|
|
137
|
+
currentVersion?: bigint;
|
|
138
|
+
}
|
|
139
|
+
interface DataPointDeletionBase {
|
|
140
|
+
dataPointId: Hex;
|
|
141
|
+
ownerAddress: Address;
|
|
142
|
+
scope: string;
|
|
143
|
+
/** Decimal-string uint256 of the tombstone version (`current + 1`). */
|
|
144
|
+
version: string;
|
|
145
|
+
signature: Hex;
|
|
146
|
+
/** The gateway's 200 body for the tombstone. */
|
|
147
|
+
tombstone: GatewayDeleteDataPointResult;
|
|
148
|
+
}
|
|
149
|
+
/** Both the gateway tombstone and the blob cleanup succeeded. */
|
|
150
|
+
export interface DataPointDeletedResult extends DataPointDeletionBase {
|
|
151
|
+
status: "deleted";
|
|
152
|
+
storage: DataPointBlobDeleteResult;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The gateway recorded the tombstone (durable, the data point is deleted)
|
|
156
|
+
* but the blob cleanup failed. Retry `storage.deleteScope(owner, scope)`
|
|
157
|
+
* later; the tombstone does not need to be re-signed.
|
|
158
|
+
*/
|
|
159
|
+
export interface DataPointDeletionPartialResult extends DataPointDeletionBase {
|
|
160
|
+
status: "partial";
|
|
161
|
+
storageError: Error;
|
|
162
|
+
}
|
|
163
|
+
export type DataPointDeletionResult = DataPointDeletedResult | DataPointDeletionPartialResult;
|
|
164
|
+
/**
|
|
165
|
+
* Delete a data point end to end. Symmetric with `registerDataPoint`.
|
|
166
|
+
*
|
|
167
|
+
* Order, deliberately:
|
|
168
|
+
* 1. fetch the current version from the gateway (unless supplied),
|
|
169
|
+
* 2. sign the tombstone `AddData` for `current + 1`,
|
|
170
|
+
* 3. `DELETE /v1/data/:dataPointId` on the gateway,
|
|
171
|
+
* 4. delete every blob under `(owner, scope)` in vana-storage.
|
|
172
|
+
*
|
|
173
|
+
* Steps 1-3 throw on failure ({@link DataPointNotFoundError},
|
|
174
|
+
* {@link DataPointDeletedError}, `DataPointVersionConflictError`, or a
|
|
175
|
+
* generic gateway error). A step-4 failure does NOT throw: the tombstone is
|
|
176
|
+
* already the durable fact, so the result comes back with
|
|
177
|
+
* `status: "partial"` and the storage error attached.
|
|
178
|
+
*/
|
|
179
|
+
export declare function deleteDataPoint(input: DeleteDataPointInput): Promise<DataPointDeletionResult>;
|
|
180
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAddress,
|
|
3
|
+
keccak256,
|
|
4
|
+
maxUint256,
|
|
5
|
+
stringToBytes
|
|
6
|
+
} from "viem";
|
|
7
|
+
import { DataPointDeletedError, DataPointNotFoundError } from "../errors.js";
|
|
8
|
+
import { isPlainObject } from "../utils/response-body.js";
|
|
9
|
+
import {
|
|
10
|
+
ADD_DATA_TYPES,
|
|
11
|
+
dataRegistryDomain
|
|
12
|
+
} from "./eip712.js";
|
|
13
|
+
import { deriveDataPointId } from "./lineage.js";
|
|
14
|
+
const TOMBSTONE_DATA_HASH_PREIMAGE = "vana.data-point.tombstone.v1";
|
|
15
|
+
const TOMBSTONE_METADATA_HASH_PREIMAGE = "vana.data-point.tombstone.metadata.v1";
|
|
16
|
+
const TOMBSTONE_DATA_HASH = "0x30c45ee72fe56d1927701316925ab7ceacd3b6f9267061735d59396f075c6222";
|
|
17
|
+
const TOMBSTONE_METADATA_HASH = "0xc5255a141acd6a2ae55971b62c0a85977c2511989dc114ad2abc2b7644f57d90";
|
|
18
|
+
function computeTombstoneHash(preimage) {
|
|
19
|
+
return keccak256(stringToBytes(preimage));
|
|
20
|
+
}
|
|
21
|
+
function isTombstoneHashes(dataHash, metadataHash) {
|
|
22
|
+
return typeof dataHash === "string" && typeof metadataHash === "string" && dataHash.toLowerCase() === TOMBSTONE_DATA_HASH && metadataHash.toLowerCase() === TOMBSTONE_METADATA_HASH;
|
|
23
|
+
}
|
|
24
|
+
function isDataPointTombstone(value) {
|
|
25
|
+
if (!isPlainObject(value)) return false;
|
|
26
|
+
if (typeof value["deletedAt"] === "string") return true;
|
|
27
|
+
return isTombstoneHashes(
|
|
28
|
+
typeof value["dataHash"] === "string" ? value["dataHash"] : void 0,
|
|
29
|
+
typeof value["metadataHash"] === "string" ? value["metadataHash"] : void 0
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function tombstoneDeletedAt(value) {
|
|
33
|
+
if (!isPlainObject(value)) return null;
|
|
34
|
+
const deletedAt = value["deletedAt"];
|
|
35
|
+
return typeof deletedAt === "string" ? deletedAt : null;
|
|
36
|
+
}
|
|
37
|
+
function assertAddress(value, name) {
|
|
38
|
+
if (!isAddress(value)) {
|
|
39
|
+
throw new Error(`${name} must be a valid EVM address`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function assertUint256(value, name) {
|
|
43
|
+
if (value < 0n || value > maxUint256) {
|
|
44
|
+
throw new Error(`${name} must fit in uint256, got ${value}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getAccountAddress(account) {
|
|
48
|
+
if (!account) return void 0;
|
|
49
|
+
return typeof account === "string" ? account : account.address;
|
|
50
|
+
}
|
|
51
|
+
function isDataPointDeletionSigner(source) {
|
|
52
|
+
return "address" in source && typeof source.signTypedData === "function";
|
|
53
|
+
}
|
|
54
|
+
function createViemDataPointDeletionSigner(source, options = {}) {
|
|
55
|
+
if (isDataPointDeletionSigner(source)) {
|
|
56
|
+
return source;
|
|
57
|
+
}
|
|
58
|
+
const accountAddress = getAccountAddress(options.account) ?? getAccountAddress(source.account);
|
|
59
|
+
if (accountAddress) {
|
|
60
|
+
return {
|
|
61
|
+
address: accountAddress,
|
|
62
|
+
signTypedData: (typedData) => source.signTypedData({
|
|
63
|
+
...typedData,
|
|
64
|
+
account: options.account ?? source.account ?? accountAddress
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
throw new Error(
|
|
69
|
+
"Viem wallet client requires an account option or account property"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function buildDataPointDeletionTypedData(input) {
|
|
73
|
+
assertAddress(input.ownerAddress, "ownerAddress");
|
|
74
|
+
if (input.scope.length === 0) {
|
|
75
|
+
throw new Error("scope must be a non-empty string");
|
|
76
|
+
}
|
|
77
|
+
if (input.expectedVersion <= 0n) {
|
|
78
|
+
throw new Error("expectedVersion must be a positive version number");
|
|
79
|
+
}
|
|
80
|
+
assertUint256(input.expectedVersion, "expectedVersion");
|
|
81
|
+
return {
|
|
82
|
+
domain: dataRegistryDomain(input.config),
|
|
83
|
+
types: ADD_DATA_TYPES,
|
|
84
|
+
primaryType: "AddData",
|
|
85
|
+
message: {
|
|
86
|
+
ownerAddress: input.ownerAddress,
|
|
87
|
+
scope: input.scope,
|
|
88
|
+
dataHash: TOMBSTONE_DATA_HASH,
|
|
89
|
+
metadataHash: TOMBSTONE_METADATA_HASH,
|
|
90
|
+
expectedVersion: input.expectedVersion
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function buildDataPointDeletionSignature(input) {
|
|
95
|
+
const typedData = buildDataPointDeletionTypedData({
|
|
96
|
+
ownerAddress: input.signer.address,
|
|
97
|
+
scope: input.scope,
|
|
98
|
+
expectedVersion: input.expectedVersion,
|
|
99
|
+
config: input.config
|
|
100
|
+
});
|
|
101
|
+
const signature = await input.signer.signTypedData(typedData);
|
|
102
|
+
return {
|
|
103
|
+
signature,
|
|
104
|
+
signerAddress: input.signer.address,
|
|
105
|
+
typedData
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function toError(value) {
|
|
109
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
110
|
+
}
|
|
111
|
+
function parseExpectedVersion(value) {
|
|
112
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Gateway returned a malformed expectedVersion: ${JSON.stringify(value)}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const parsed = BigInt(value);
|
|
118
|
+
assertUint256(parsed, "Gateway expectedVersion");
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
async function deleteDataPoint(input) {
|
|
122
|
+
const ownerAddress = input.signer.address;
|
|
123
|
+
const dataPointId = deriveDataPointId(ownerAddress, input.scope);
|
|
124
|
+
let currentVersion = input.currentVersion;
|
|
125
|
+
if (currentVersion === void 0) {
|
|
126
|
+
const record = await input.gateway.getDataPoint(dataPointId);
|
|
127
|
+
if (record === null) {
|
|
128
|
+
throw new DataPointNotFoundError(
|
|
129
|
+
`No data point registered for scope '${input.scope}' owned by ${ownerAddress}`,
|
|
130
|
+
{ dataPointId, scope: input.scope, ownerAddress }
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (isDataPointTombstone(record)) {
|
|
134
|
+
throw new DataPointDeletedError(
|
|
135
|
+
`Data point ${dataPointId} (scope '${input.scope}') is already deleted`,
|
|
136
|
+
{
|
|
137
|
+
dataPointId,
|
|
138
|
+
scope: input.scope,
|
|
139
|
+
ownerAddress,
|
|
140
|
+
deletedAt: tombstoneDeletedAt(record)
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
currentVersion = parseExpectedVersion(record.expectedVersion);
|
|
145
|
+
}
|
|
146
|
+
if (currentVersion < 0n || currentVersion >= maxUint256) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`currentVersion ${currentVersion} cannot be incremented to a uint256 tombstone version`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const tombstoneVersion = currentVersion + 1n;
|
|
152
|
+
const signed = await buildDataPointDeletionSignature({
|
|
153
|
+
signer: input.signer,
|
|
154
|
+
scope: input.scope,
|
|
155
|
+
expectedVersion: tombstoneVersion,
|
|
156
|
+
config: input.config
|
|
157
|
+
});
|
|
158
|
+
const tombstone = await input.gateway.deleteDataPoint({
|
|
159
|
+
ownerAddress,
|
|
160
|
+
scope: input.scope,
|
|
161
|
+
expectedVersion: tombstoneVersion.toString(),
|
|
162
|
+
signature: signed.signature
|
|
163
|
+
});
|
|
164
|
+
const base = {
|
|
165
|
+
dataPointId,
|
|
166
|
+
ownerAddress,
|
|
167
|
+
scope: input.scope,
|
|
168
|
+
version: tombstoneVersion.toString(),
|
|
169
|
+
signature: signed.signature,
|
|
170
|
+
tombstone
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
const storage = await input.storage.deleteScope(ownerAddress, input.scope);
|
|
174
|
+
return { ...base, status: "deleted", storage };
|
|
175
|
+
} catch (cause) {
|
|
176
|
+
return { ...base, status: "partial", storageError: toError(cause) };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
TOMBSTONE_DATA_HASH,
|
|
181
|
+
TOMBSTONE_DATA_HASH_PREIMAGE,
|
|
182
|
+
TOMBSTONE_METADATA_HASH,
|
|
183
|
+
TOMBSTONE_METADATA_HASH_PREIMAGE,
|
|
184
|
+
buildDataPointDeletionSignature,
|
|
185
|
+
buildDataPointDeletionTypedData,
|
|
186
|
+
computeTombstoneHash,
|
|
187
|
+
createViemDataPointDeletionSigner,
|
|
188
|
+
deleteDataPoint,
|
|
189
|
+
isDataPointTombstone,
|
|
190
|
+
isTombstoneHashes,
|
|
191
|
+
tombstoneDeletedAt
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=data-point-deletion.js.map
|