Data-provider package & cascade-api
Reference for @lumera-protocol/data-provider-cascade and the cascade-api HTTP backend it talks to.
This page documents the two pieces of plumbing the Lukso integration uses: the @lumera-protocol/data-provider-cascade npm package (used by your dApp) and the cascade-api HTTP backend (which the package POSTs to).
Package overview
@lumera-protocol/data-provider-cascade is the Lumera-owned successor to @lukso/data-provider-cascade. It follows the same design pattern: extends BaseFormDataUploader from @lukso/data-provider-base, exposes the same .upload() returning { url, hash }, slots into any pipeline that already targets the Lukso data-provider interface.
Three things differ from the original:
- Endpoint is owned by Lumera, not the legacy Pastel gateway.
- No third-party API key. The package talks to a cascade-api backend (yours, or the public one), which performs the on-chain Cascade transaction using
@lumera-protocol/sdk-js. Authentication, signing, and gas are the backend's concern. - Configurable. Endpoint, paths, and an optional
Authorization: Bearertoken can all be set on the constructor.
Install
@lukso/data-provider-base is pulled in transitively. You don't import it directly unless you're writing your own data provider.
Quick start
The url returned is suitable as the URL field of an LSP-2 VerifiableURI. The hash is the keccak256 of the file bytes, computed client-side; readers verify it matches what's served by the gateway.
API
new CascadeUploader(options?)
uploader.upload(file, meta?)
Inherited from BaseFormDataUploader. Returns:
The hash is computed by the base class before upload using @ethersproject/keccak256. It's a 32-byte value whose hex form is what LSP-2 VerifiableURI verification checks against. When you pass { json, url } to erc725.js encodeData, it recomputes the hash from the JSON itself, so this returned hash is informational; when you pass { hash, url } directly (e.g. for individual verification entries on profileImage[]), this is the value you put in.
Throws if the cascade-api response is missing action_id.
uploader.uploadWithMetadata(file, meta?)
Lumera-specific extension that returns the cascade-api raw response on top of { url, hash }:
Use this when you want to surface the on-chain receipt to your UI (e.g. a Cascade ledger that shows tx_hash per upload, linkable to a block explorer).
uploadWithMetadata is not safe under concurrent calls on the same uploader instance. It uses a single-slot stash that the base upload() populates via resolveUrl. Sequential awaits are fine; parallel calls should use separate uploader instances.
uploadJSON(uploader, value, fileName?)
Convenience: serializes value with JSON.stringify, wraps it as a Blob, and forwards to uploader.upload. Returns the same { url, hash } shape, so the result plugs straight into LSP-2 VerifiableURI encoding via erc725.js.
uploader.buildUrl(actionId)
Resolves a previously-stored actionId back into the public HTTPS URL. Useful when re-encoding metadata from a cached actionId without re-uploading:
Hooks (overridable, inherited from BaseFormDataUploader)
For full alignment with the Lukso data-provider ecosystem, the standard hooks are exposed as override-able methods:
| Hook | What it does |
|---|---|
getEndpoint(): string | Returns POST URL: backendUrl + uploadPath |
getRequestOptions(form, meta): Promise<FormDataRequestOptions> | Adds Authorization: Bearer header when bearerToken is set |
resolveUrl(result): string | Reads result.action_id (with actionId fallback) and returns the gateway URL |
If you need to customise behaviour, e.g. swap the auth header for an OAuth flow, subclass CascadeUploader and override the relevant hook.
Comparison with @lukso/data-provider-cascade
@lukso/data-provider-cascade (legacy) | @lumera-protocol/data-provider-cascade | |
|---|---|---|
| Backend | Pastel gateway (legacy domain) | cascade-api (Lumera-owned) |
| Auth | Api-key header | Authorization: Bearer |
| Constructor | (apiKey: string) positional | ({ backendUrl?, bearerToken?, uploadPath?, downloadPath? }) options bag |
| Returns | { url: ipfs://<cid>, hash } | { url: https://api.lumera.help/download/<id>, hash } (plus uploadWithMetadata) |
| Maintained | Last released 2024 | Active |
To switch an existing call site:
The cascade-api HTTP surface
@lumera-protocol/data-provider-cascade is just a thin wrapper over an HTTP API. If you want to call it directly from another language, or you want to know what's actually happening behind the package, here's the full surface.
Base URL
CORS is permissive (Access-Control-Allow-Origin: *), so calls from any origin work in the browser.
POST /upload
Inscribes a file to Cascade. Returns the action_id you'll need to fetch it back.
Request:
Form field: file (the file bytes).
Response (200):
Errors:
| Status | Body | Meaning |
|---|---|---|
| 400 | {"error": "parse form: ..."} | Multipart form malformed or too large |
| 400 | {"error": "file required: ..."} | No file form field |
| 401 | unauthorized: ... (text) | Missing / wrong / expired / quota-exhausted bearer |
| 500 | {"error": "upload failed: ...", "hint": "..."} | Cascade upload failed (often: out of ulume) |
GET /download/{action_id}
Reconstructs the file from Cascade and streams it back. No auth. action_id must be a numeric string.
Response (200):
- Body: raw file bytes
Content-Typeis sniffed from the file extension or magic bytesContent-Disposition: attachment; filename="<original>"X-Cascade-Filename: <original>(same value, easier to read in browser fetch where Content-Disposition can be cross-origin-restricted)Cache-Control: public, max-age=31536000, immutable— Cascade artifacts are content-addressed, safe to cache forever
GET /healthz
Service liveness + uploader balance. No auth. Use this to fail fast when the backend is down or the funded balance has run dry.
GET /log and /log/stream
Live SSE stream of the cascade-api process log. Used by the operator for monitoring. No auth required on the public deployment.
Limits
| Value | Notes | |
|---|---|---|
| Max upload size | 256 MB | Per-request, multipart total |
| Upload latency | 30-60s typical | RaptorQ encoding + Lumera tx + Supernode replication |
| Download latency | ~10s warm, ~30s cold | Backend caches recent artifacts |
| Per-key quota | Set per key by the operator | Default for new keys is unlimited unless requested otherwise |
| Service-wide budget | Bounded by uploader's ulume balance | ~10-15k ulume per upload; check /healthz |
Permanence and reachability
A trust-model recap, since the package and the API both depend on it:
Cascade itself is permanent. The bytes you upload through cascade-api live on the Supernode network, erasure-coded across many nodes. They survive node churn, they survive operator turnover, they survive cascade-api itself going away.
The URL embedded in the on-chain VerifiableURI is a different question. That URL points at whichever cascade-api gateway you configured. If https://api.lumera.help ever became unreachable, the bytes would still exist on Cascade, but Lukso readers (which only know the URL, not how to ask Cascade directly) wouldn't be able to find them.
The hash binding in the VerifiableURI guarantees the gateway can't tamper with the bytes, only deny service. That's a strong guarantee but a meaningfully weaker one than "permanent and self-resolving."
In practice, three options for production:
- Trust the public deployment.
api.lumera.helpis operator-funded and stable for testnet/early-mainnet usage. No operational burden on you. - Self-host a cascade-api. Clone the cascade-api repo, point your dApp at your domain, control your own uptime. The HTTP surface is identical, so client code ports as-is.
- Use multiple gateways. Resolvers like
@lukso/data-provider-urlresolvercan be configured with multiple fallback hosts. Pair this with multiple cascade-api deployments (yours + Lumera's + maybe a community-run one) for redundancy.
For the LSP-3 demo and most early integrations, option 1 is the right call. For an NFT marketplace expecting decade-scale viability, plan for option 3.
Self-hosting cascade-api
The complete cascade-api source ships as a small Go service. Quick start:
First run derives your lumera1... address from the mnemonic, registers its pubkey on chain, opens a Cascade SDK client, and starts serving. Fund the address with at least 50000 ulume from the faucet before the first upload.
To point the data-provider at it:
Source code
- Package:
@lumera-protocol/data-provider-cascadeon npm - HTTP surface: cascade-api repo
- Reference dApp using both: github.com/kaleababayneh/permanent-up