Web3 integration

The contract decides. The interface explains.

Wallet connection, stablecoin subscription, and the event indexer that builds the cap table a permissioned token deliberately refuses to store. Nothing in the interface enforces a rule — every restriction that matters lives in the transfer hook, and the application's job is to make it legible before anyone signs.

Next.jsTypeScriptviemwagmiPostgreSQLBaseUSDC

N° 01The read problem

A permissioned token hides its own holders

Ask the contract who owns this asset and it will not answer. Balances live in a mapping, and a mapping cannot be walked. REChain — our reference build, a tokenized real-estate system on Base Sepolia, unaudited — keeps the investor count as a single number because the holder cap needs one, and its distribution contract answers one question per call: what is this named address owed for this distribution. There is no holder array anywhere, on purpose — a list that grows with the raise is a list that eventually cannot be iterated inside a block.

So a cap table, a holder list sorted by position, one investor's holdings across four properties, the roster for a quarterly distribution, an audit trail of who was attested and when: none of it can be rendered from chain reads. These are not slow queries. They are not queries at all.

The indexer and the database are an architectural necessity rather than a stack preference. A proposal that treats the frontend as a thin wrapper over RPC calls has not got as far as the second screen.

N° 02The indexer

Events in, state you can query out

01

Ingestion

Subscribe to the contracts' logs and decode each one against its ABI — transfers, attestations, rule changes, subscriptions, claims. Every row written names the block, transaction and log index that produced it, so any number on a dashboard traces back to the event that caused it.

02

Reorg safety

Block numbers are not identifiers; block hashes are. Store the hash with each ingested event, check on every new block that its parent is the hash you already hold, and unwind to the last agreeing block when it is not. An indexer that tracks only numbers keeps an orphaned transfer in the cap table forever.

03

Idempotency

Every upsert is keyed on transaction hash plus log index, which is unique per event. That one constraint lets the ingester be restarted mid-batch, run twice, or replay an overlapping range without inventing a duplicate holder or double-counting a claim. Assume all three will happen, because they will.

04

The cursor

One row recording the last block processed, written in the same database transaction as the events from that block. Split them and a crash between the two either loses events or replays them. Keep them together and the worst case is a replay the idempotency key absorbs.

05

Backfill

History starts at the deploy block, not at the moment the service was first started. Backfill runs the same code path as live ingestion, in ranged batches, so there is one decoder to get right rather than two that disagree at the seam.

N° 03Wallet and payment

Approve, then subscribe. One intent, two signatures

Buying into a raise with a stablecoin is two transactions, and no investor knows that going in. The first grants the escrow an allowance. The second actually subscribes. Between them sits most of what goes wrong: the user approves and closes the tab, the approval confirms while the raise closes, or the wallet was on the wrong network and the allowance landed somewhere it does nothing.

We treat it as one intent with two steps and say so before the first signature — what is being approved, what the pair costs in gas, what the second step will do. A cost preview shown before signing is not a nicety. It is the difference between a subscriber who abandons at the wallet popup and one who does not.

Then the states, which is where most Web3 frontends give themselves away. Submitted is not pending, pending is not confirmed, and reverted needs a screen of its own rather than a toast that disappears in four seconds. Every transaction carries an explorer link, because the honest answer to whether it worked is a page the user can read without asking anyone.

N° 04Readable failures

Name the rule that failed, and name it first

A rule engine that rejects a transfer knows precisely why. In the reference build the compliance check returns the verdict and the reason together, and the token reverts carrying that reason forward, so the explanation exists at the instant of rejection. Most interfaces throw it away and render a revert blob, which is a decision somebody made rather than a limit of the chain.

Custom errors arrive at the wallet as four bytes and a payload: meaningless to a subscriber, fully specified by the ABI that defines them. The reference build's are typed rather than generic — one carries the rule engine's reason string, one carries the frozen address, one carries the balance available next to the balance required. Decoding those into your accreditation record expired on 4 March, or this transfer needs 400 shares and 250 of yours are frozen, takes an afternoon and turns a support ticket into something the investor settles without writing to anyone.

Better still, ask before signing. The compliance contract answers hypotheticals — can this address receive this amount, and if not, which rule stops it. Running that on every amount change turns a reverted transaction into a sentence the user reads for free: this purchase would take you past the holder cap, or your lockup runs another eleven days. Nobody should pay gas to be told no.

N° 05The boundary

The interface enforces nothing at all

Every restriction the frontend appears to apply is advisory. Hide the subscribe button and a determined user calls the contract from a block explorer. Gray out a jurisdiction and the ABI is still public, the function still there. A rule that lives only in the interface does not exist.

That is the correct division rather than a gap to be closed. The contract decides and the interface explains, which makes the interface's job to render the contract's rules legibly — the rule, the reason, the remedy — rather than to keep a second set that quietly disagrees with the first.

So we read the configuration instead of restating it. Lockup length, holder cap, jurisdiction list: on-chain values the dashboard fetches, not constants in a TypeScript file that were accurate in March. Where the two drift, the contract wins and your support inbox is how you find out.

Investment

Fixed scope. No drift.

From $25,000 — indexer, subscription flow, and the dashboards a chain read cannot give you.

  • Event indexer with reorg handling and backfill from the deploy block
  • PostgreSQL schema for cap table, holders and distributions
  • Wallet connection with network and account switching
  • Stablecoin subscription flow with allowance handling and cost preview
  • Compliance dry-run before any signature
  • Custom errors decoded into readable failures
  • Transaction states with explorer links
  • Issuer console scoped to each role's permissions
  • Fixed-scope proposal after a read of your contracts
  • 30-day defect warranty

Questions

The answers we give most often.

Can you build the dashboard if someone else wrote the contracts?
Yes. We need the ABIs, the deployed addresses, the deploy block for each contract, and an hour with whoever wrote them. We will also read the Solidity, because the indexer's correctness depends on which events actually fire — a state change that emits nothing is a cap table that drifts quietly. If that read turns up a problem we will tell you, but it is a side effect of the work rather than an audit, and we do not audit contracts.
Do you use a subgraph or a custom indexer?
It depends on the read pattern. A subgraph is faster to stand up and fits public, append-only history. A permissioned security token usually needs the opposite: joins against off-chain data that never touches the chain — investor names, KYC documents, subscription agreements, bank references — plus queries an issuer runs at nine in the morning and expects back immediately. That is a Postgres shape, and it is what we would default to for anything with an issuer console attached. Be clear about what that is: reasoning from your read pattern, not a portfolio piece. Our reference build is contracts and a public demonstration site — the indexed console is work we would be doing for the first time on your project, and we would rather say so than let a stack preference sound like a track record.
How do users pay?
In stablecoin, into an escrow contract, across two transactions: an allowance approval and then the subscription. The escrow holds until the raise clears its soft cap, at which point the issuer can withdraw and shares are issued. If it does not clear, subscribers refund themselves. The interface's job is to make the two-step shape and the escrow condition obvious before the first signature rather than after the second.
Do you handle fiat on-ramps?
We integrate one, we do not become one. Moving fiat into stablecoin requires a licensed provider — an on-ramp, an exchange, or your bank alongside an OTC desk — and which of those is available to you is a question for your counsel and your jurisdiction, not for us. We build the flow around whichever you pick, including the reconciliation between what the provider says it sent and what the chain says arrived. That reconciliation is the part teams underestimate: a provider reference, an on-chain transfer and a subscription record have to resolve to one investor, and when the three disagree somebody in your operations team needs a screen that shows which of them is wrong rather than a support thread that guesses.
What about gas for users?
On Base a claim costs a few cents, which is normally left with the holder, and saying so plainly is easier than the alternatives. If you want to cover it, the options are a paymaster with smart-account wallets or a meta-transaction relayer, and each adds a component that gets drained if the sponsorship rules are loose. It is a product decision with a real engineering cost, so we would rather price it deliberately than assume it. What we will not do is put pull-based claims on a chain where the gas to collect exceeds the payout.
Can you build the issuer admin console?
Yes, and it carries more screens than the investor side. Attesting an investor and setting their KYC expiry, configuring the rule engine, opening and closing a raise, funding a distribution, moving a holding for a lost key — each is a privileged call, and the console's job is to show what a call will do before it runs and to keep a record of who ran it afterwards. The reference build splits the token's privileged powers into four roles with separate blast radii — issue and redeem, freeze, pause, forced transfer — and the registry, compliance and sale contracts carry their own agent roles above that. A console built against a split like that shows an operator what their key can execute rather than a full menu that fails at the last step.

Next step

A permissioned token has no holder list. The cap table has to be built, not read.

Tell us what is already deployed, what your issuer team needs to see, and where the investors sit.

Web3 Integration & dApp Development for Tokenized Assets | DesignKompanie