{"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":[]}