Build your own integration
Reusable patterns for any Injective × Cascade product
Inscribe is one shape of an Injective × Cascade product. This page is the recipe book for building your own: the architecture every integration shares, the conventions that make it clean, and the pitfalls worth a deliberate decision up front.
The shape every integration takes
Regardless of what your dApp does, the architecture is the same three-layer split:
What you customise per product is what the bytes are: trade receipts, NFT media + metadata, governance proposals, oracle inputs, audit logs, archival data, off-chain order signatures, encrypted messages, attestations. Everything else is plumbing.
Decision: who signs the Cascade write?
The most important call you make. The options are detailed in Cascade from Injective; the short version:
| Choose Pattern A (user-signed) if… | Choose Pattern B (server-signed) if… | Choose Pattern C (contract-driven) if… |
|---|---|---|
| Provenance matters, the user must be the on-chain author | UX matters, one signature, faster path, no Lumera address for users | The artifact must exist whenever the state transition happens, with no user in the loop |
| Users already need a Lumera address for other reasons | You're OK funding ulume at the operator level | Indexers and trustless readers need the artifact guaranteed-present |
| You want zero server-side custody | You want a single key you can monitor and rotate | You accept the IBC round-trip latency and operational complexity |
Start with Pattern B. It's the fastest path to a working product and the one the reference implementation uses for everything; the table above covers when to deviate.
Contract-side conventions
Five conventions make an Injective contract Cascade-friendly. Adopt them and your indexer and frontend become trivial.
1. Every Cascade-bound field is a String
Not a Vec<u8>, not a custom type, a plain UTF-8 String the contract treats as opaque. Validation is !cid.trim().is_empty() and nothing more. Cascade action_ids are numeric strings; store them as-is to keep indexer queries simple.
2. Emit the CID as a wasm event attribute
Indexers reconstruct state from wasm events. Every transition that introduces a new CID should emit it as a discoverable attribute alongside action and submitter.
3. Expose CIDs in queries
A read-only client with no indexer should be able to drive a viewer from contract queries alone. Expose every CID-bearing record:
A developer can then query the chain, get every CID, fetch each from a Cascade gateway, and render the full state without an indexer.
4. State machines, not free-form
Model Cascade-bound workflows as a strict state machine where each state defines exactly which CIDs may be added. The reference implementation uses Open → Proposed → Challenged → Voting → Final, and an actor cannot, say, add a "vote justification" in the Open state, the contract rejects it, so the indexer never sees a confusing half-state.
5. Atomic cross-contract calls for funds + CIDs
If a CID submission also moves funds (a bond, a fee, a deposit), emit a CosmosMsg::Wasm(WasmMsg::Execute) in the same response. The whole tx succeeds or reverts together, the indexer never sees a state where the CID was registered but the bond was not.
Indexer responsibilities
Three jobs, in priority order:
- Mirror on-chain state into a queryable store. Smart-query each contract instance every N seconds into Postgres. The data volume is bounded by the number of objects, not by user traffic.
- Eagerly resolve every new CID. When you see a new CID attribute, fetch the artifact from a Cascade gateway in the background and cache the body locally. Frontend reads hit your cache, not Lumera.
- Serve joined views.
GET /things/{id}should return on-chain state and inlined Cascade content in one response.
The third is what makes the UX feel fast. A naive frontend doing 20 round trips per page (one for state, 19 for CIDs) is painful; the same data served as one JSON blob is instant. Because Cascade content is immutable, the cache is permanent: no invalidation strategy.
Frontend conventions
getOfflineSignerOnlyAminofor Injective signing. Direct-mode signing does not work witheth_secp256k1. Always Amino. See the primer.- Server Components for read paths. Fetch your joined
/things/{id}view and render server-side; no client-side fetching on initial load. - Client Components for write actions. Render only the actions valid in the current state, so the UI drives the user through the state machine without offering calls the contract would reject.
- Refresh after a delay. After a successful execute, schedule a
router.refresh()10–12 seconds out so the indexer has one full poll-tick to catch up before the UI re-reads. - Surface contract errors verbatim.
wasm execute failed: WrongState(...)is usually more actionable than anything you'd rewrite it into.
When to skip Cascade entirely
Cascade is the right home for content that needs to be permanent, verifiable, and large. It is not the right home for everything an Injective dApp produces.
| Use Cascade if… | Use plain on-chain state if… |
|---|---|
| The artifact is > a few hundred bytes | The artifact is a small enum or numeric value |
| The artifact must outlive the dApp's lifecycle | The artifact is intermediate / cache state |
| The artifact will be cited or audited in the future | The artifact is purely operational |
| You want pay-once economics | You don't need permanence at all |
A 32-byte hash, an enum, a counter; these belong in CosmWasm state. A 500-byte JSON blob with a claim and citations, a 5-MB PDF, a 3D NFT model. These belong on Cascade with a String pointer in CosmWasm state.
Things to design around
None are blockers, but each deserves a deliberate decision.
Gateway as a single point of failure
In Pattern B, your POST /cascade/upload depends on one cascade-api endpoint holding a signing key. If it's down, uploads are down. Either rely on the HA Lumera-operated cascade-api at api.lumera.help, or run your own behind a domain you control. The download path is more forgiving, it can fan out across multiple gateways via a URL resolver but the upload path is single-homed by nature.
ulume balance
Every Cascade write spends ~10–15k ulume; a 1M-ulume balance covers ~70 uploads. Monitor your service key's balance and alert before exhaustion. If you expose uploads to users, add per-user quotas, an open upload endpoint is a way to drain your key.
eth_secp256k1 vs secp256k1
Injective uses eth_secp256k1; Lumera uses standard secp256k1. Derived from the same Keplr mnemonic, the user has two different addresses (different coinType paths). Any UI that surfaces both must not confuse them.
Indexer lag
A 10-second poll interval means the UI can see stale state for up to 10 seconds after a tx confirms. Mitigate with optimistic local updates, a shorter interval (at the cost of more LCD load), or the simple refresh-after-12s pattern, which is fine for testnet UX but fragile under heavy load.
IBC latency (only if you use Pattern C)
A contract-driven Cascade write over ICA is a slow IBC round-trip that can stall on relayer hiccups (see the primer's note on IBC for the latency). Never put it on a user's hot path: defer it to canonical artifacts that don't gate user actions, or use Pattern B with a fast server, as the reference implementation does.
Re-litigation needs gateway redundancy
The "anyone can fetch the full record forever" property depends on at least one Cascade gateway being reachable. The bytes are permanent on the Supernode network, but a reader who only knows an HTTPS URL needs that URL to resolve. Plan for at least two gateways in production (your own + Lumera-operated), and for very long-horizon use cases consider a Cascade-native reader that talks to Supernodes directly.
Other product shapes that fit
Beyond prediction markets, the same Injective × Cascade pattern is a natural fit for:
- NFT marketplaces where metadata permanence matters: the contract holds the token + an
action_id; metadata and media live on Cascade. - DAOs where proposals, debate threads, and voting rationale need to be archival. Snapshot's "proposals on IPFS" pattern, but permanent.
- DeFi protocols publishing oracle inputs with a permanent receipts trail (Chainlink-style feeds where the input set is part of the audit).
- Audit / compliance logs that need decade-scale verifiability, signed by the on-chain contract.
- Encrypted messaging layered on Cascade's encrypted storage with Injective contracts handling access control.
- Long-horizon markets with settlement dates years out. Cascade's permanence outlives the underlying market.