Skip to content

Build HarborClient: reserve→sign→finalize bucket, multipart upload, status poll, download #6

Description

@harrymove-ctrl

Context

There is no Harbor client today; storage is Tatum-only and plaintext (packages/walrus/src/storage.ts). Harbor's model is SPACES > BUCKETS > FILES, and bucket creation is a RESERVE → SIGN → FINALIZE handshake (an Enoki-sponsored Sui tx — no SUI balance needed). We need a pure-fetch, runtime-agnostic client that works in both the Worker and Node (the seal Worker spike decides which runtime actually signs/uploads).

Goal / user story

As ContextMEM, I want a HarborClient that can create a bucket (reserve→sign→finalize), upload a file (multipart ciphertext), poll its status to ready, and download it — so private namespaces can store encrypted blobs on Walrus via Sui.

Acceptance criteria

  • New packages/walrus/src/harbor.ts exports a HarborClient (class or factory) with NO node:fs/node:child_process/node:os imports (runs in Worker + Node).
  • createBucket(spaceId, name) performs RESERVE → sign the sponsored tx with Ed25519Keypair from decodeSuiPrivateKey(serviceKey) → FINALIZE as one tight back-to-back sequence; returns { bucketId, sealPolicyId }.
  • uploadFile(bucketId, { name, data, contentType }) does the multipart upload and returns a fileId; getFileStatus(fileId) polls to a terminal/ready state mirroring waitForWalrusStorageCertified (storage.ts:160).
  • A post-finalize 403 mirror_missing_grant is retried with backoff over the ~3s mirror window before surfacing an error.
  • download(fileId) returns ciphertext bytes.
  • Unit test with a mocked fetch covers reserve→sign→finalize ordering, the mirror_missing_grant retry, and an upload+poll happy path.

Implementation notes

  • Auth: Authorization: Bearer hbr_.... Inject config (apiKey, serviceKey, spaceId, baseUrl, optional fetch) the same way TatumStorageConfig is injected in storage.ts:21.
  • Sign with @mysten/sui/keypairs/ed25519 + decodeSuiPrivateKey from @mysten/sui/cryptography; build/execute the sponsored tx (Enoki sponsors gas). Sponsor sig expires fast — RESERVE and FINALIZE must be back-to-back with no awaiting of unrelated work between them.
  • Add @mysten/seal + @mysten/sui to packages/walrus/package.json (currently only fast-xml-parser + p-limit) and re-export from packages/walrus/src/index.ts.
  • This client takes ciphertext only — encryption lives in seal.ts (StorageProvider + SEAL issue). Mirror the polling/receipt shape already in storage.ts so the StorageProvider seam can wrap it.

Sui Overflow angle

The reserve→sign→finalize handshake is the FIRST time ContextMEM signs a real Sui tx. The resulting Bucket + Seal-policy objects are genuine on-chain artifacts (Enoki-sponsored, zero SUI), giving a concrete "where is the Sui contract" answer to show judges — with no custom Move authored yet.

Dependencies

The seal Worker spike decides whether sign/upload runs in Worker or Node. The namespace→Space/Bucket/Seal mapping issue supplies the service key + space id config this client consumes.

Part of the ContextMEM roadmap (#4) • Sui Overflow build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Demo-blocking: required for a working Sui Overflow demofeatureUser- or agent-facing capabilityharborHarbor encrypted Walrus storage on Sui (Space/Bucket/File)walrusWalrus blob storage / Sites / aggregator

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions