> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tokenized equities & RWA data layer

> Build the holder registry, price feed, and corporate-action engine for tokenized stocks and RWAs with Subgraphs, Turbo, and Compose.

Tokenized equities are here. [Robinhood Chain](/chains/robinhood-chain) - an Arbitrum-based L2 - lists thousands of tokenized stocks and ETFs that trade 24/7 and self-custody in a wallet, and the broader RWA market runs largely on the [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643) permissioned-token standard.

A tokenized stock is not just an ERC-20 with a ticker. It is a security, and it needs the data infrastructure a security has always needed: a **holder registry** (who owns what, right now), a **price feed** that never closes, and a **corporate-actions engine** for dividends and splits. This guide builds all three on Goldsky.

## The three data services

```mermaid theme={null}
flowchart TB
    A[Tokenized stock<br/>ERC-3643 token] --> B[Subgraph<br/>holder registry / cap table]
    A --> C[Turbo<br/>24/7 trades & price]
    B --> D[Compose<br/>dividend & corporate actions]
    C --> D
    D -->|pay holders| E[Stablecoin distribution]
```

| Service                       | Product                                                                        | Output                                |
| ----------------------------- | ------------------------------------------------------------------------------ | ------------------------------------- |
| Holder registry / cap table   | [Subgraphs](/subgraphs/introduction) or [Turbo](/turbo-pipelines/introduction) | Current balance per holder, per token |
| 24/7 price & trade feed       | [Turbo](/turbo-pipelines/introduction)                                         | OHLC, VWAP, last trade                |
| Dividends & corporate actions | [Compose](/compose/introduction)                                               | Pro-rata stablecoin payouts, splits   |

## Step 1: Maintain a live holder registry

The cap table is the foundation - every downstream service (dividends, reporting, compliance) reads from it. Stream transfers of the tokenized stock and keep a running balance per holder.

```yaml holder-registry.yaml expandable theme={null}
name: tokenized-stock-holders
resource_size: s

sources:
  rh_transfers:
    type: dataset
    dataset_name: robinhood_testnet.erc20_transfers
    version: 1.0.0
    start_at: earliest

transforms:
  # Every transfer moves a balance: CREDIT the recipient (+amount) and
  # DEBIT the sender (-amount). Emitting both rows is what makes the
  # running balance net correctly - a credit-only stream overstates holdings.
  balance_deltas:
    type: sql
    primary_key: id
    sql: |
      -- Credit: tokens received (also covers mints, where the sender is the zero address)
      SELECT
        CONCAT(id, '-in')            AS id,
        lower(recipient)             AS holder,
        lower(address)               AS token,
        CAST(amount AS DECIMAL(38, 0)) AS delta,
        _gs_op
      FROM rh_transfers
      WHERE recipient <> '0x0000000000000000000000000000000000000000'

      UNION ALL

      -- Debit: tokens sent (also covers burns, where the recipient is the zero address)
      SELECT
        CONCAT(id, '-out')           AS id,
        lower(sender)                AS holder,
        lower(address)               AS token,
        -CAST(amount AS DECIMAL(38, 0)) AS delta,
        _gs_op
      FROM rh_transfers
      WHERE sender <> '0x0000000000000000000000000000000000000000'

sinks:
  # The aggregate sink sums delta per (holder, token) via a DB trigger,
  # maintaining the current balance incrementally.
  holdings:
    type: postgres_aggregate
    from: balance_deltas
    schema: registry
    landing_table: holdings_log
    agg_table: holdings
    primary_key: id
    secret_name: MY_POSTGRES
    group_by:
      holder:
        type: text
      token:
        type: text
    aggregate:
      balance:
        from: delta
        fn: sum
        type: numeric(38,0)
```

Two details make this correct: each emitted row gets a **unique `id`** (`-in`/`-out` suffix) so the landing table never dedupes distinct transfers, and the zero address is excluded so mints and burns adjust only the real holder. Filter out `balance = 0` rows when you read the `holdings` table to get the live holder set.

<Note>
  For ERC-3643 securities, decode the `raw_logs` instead and also capture identity events (`IdentityRegistered`) so your registry links each holder to a verified identity, not just an address. A [Subgraph](/subgraphs/introduction) is often the cleaner home for a registry you query by holder - it gives you a GraphQL API for statements and positions with no extra service. Confirm the dataset slug for your chain in the [Datasource explorer](https://app.goldsky.com/data-sources).
</Note>

The registry answers the questions a transfer agent lives on: *who holds this security, how much, and since when* - as of the latest block, not last night's snapshot.

## Step 2: Publish a 24/7 price feed

Tokenized equities trade around the clock, so the "closing price" is a live figure. Index trades from the venue (a DEX pool or the chain's matching contract) and roll them into OHLC/VWAP. This is the [DEX trades](/mirror/guides/stream-DEX-trades) pattern applied to a stock token; write to [ClickHouse](/turbo-pipelines/sinks/clickhouse) for fast time-series queries, or maintain a live last-price with the [PostgreSQL aggregate sink](/turbo-pipelines/sinks/postgres-aggregate).

```sql theme={null}
-- 1-minute OHLC from a trades table
SELECT
  token,
  toStartOfMinute(traded_at) AS minute,
  argMin(price, traded_at)    AS open,
  max(price)                  AS high,
  min(price)                  AS low,
  argMax(price, traded_at)    AS close,
  sum(quantity)               AS volume
FROM trades
GROUP BY token, minute;
```

## Step 3: Distribute dividends and corporate actions

This is where the cap table pays off - literally. A [Compose](/compose/introduction) task reads the holder registry at a record-date block and pays each holder their pro-rata dividend in a standard ERC-20 stablecoin, recording the batch in your ledger for reconciliation.

```typescript src/tasks/pay-dividend.ts theme={null}
import type { TaskContext, Chain } from "compose";

// Dividends must go out on the SAME network the stock token lives on - the
// chain this guide indexes holders from. Robinhood Chain (an Arbitrum Orbit L2)
// isn't a built-in Edge RPC chain yet, so define it as a custom chain and fill
// in its id/RPC. Swap for evm.chains.<name> once it's available on Edge.
const STOCK_CHAIN: Chain = {
  id: 0, // Robinhood Chain testnet chain ID
  name: "Robinhood Chain Testnet",
  testnet: true,
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://<robinhood-chain-rpc>"] },
    public: { http: ["https://<robinhood-chain-rpc>"] },
  },
  blockExplorers: { default: { name: "Explorer", url: "https://<explorer>" } },
};

export async function main(
  context: TaskContext,
  payload: { token: string; dividendPerToken: string; recordBlock: number },
) {
  const { fetch, evm } = context;

  // Holders as of the record-date block, from your registry service
  const holders = await fetch<{ address: string; balance: string }[]>(
    `https://registry.example.com/holders?token=${payload.token}&block=${payload.recordBlock}`,
  );

  const wallet = await evm.wallet({ name: "dividend-payer", sponsorGas: true });

  const results = await Promise.allSettled(
    holders.map((h) => {
      const amount = (BigInt(h.balance) * BigInt(payload.dividendPerToken)) / 10n ** 18n;
      // Robinhood Chain stablecoins are ERC-20 - pay with the standard transfer.
      return wallet.writeContract(
        STOCK_CHAIN,
        "0xDIVIDEND_STABLECOIN",
        "transfer(address,uint256)",
        [h.address, amount],
      );
    }),
  );

  const paid = results.filter((r) => r.status === "fulfilled").length;
  return { holders: holders.length, paid };
}
```

<Warning>
  Pay on the network your tokenized stock lives on. This task indexes holders from Robinhood Chain, so `STOCK_CHAIN` must be Robinhood Chain - sending to any other network (the example previously defaulted to Arbitrum) would deliver dividends to look-alike addresses that never held the stock.
</Warning>

Every payout is durable (it completes through failures) and [traced](/compose/debugging) end to end. For stock splits, reverse splits, and mergers, follow the [corporate-actions distributor guide](/compose/guides/build-a-corporate-actions-distributor), which handles the ratio math and multi-step distributions.

## Step 4: Reconcile with your transfer agent

Because the registry lives in *your* database, reconciling the on-chain holder list against your books of record is a query, not a project - the same [real-time reconciliation](/solutions/real-time-reconciliation) pattern, applied to positions instead of payments. Breaks (an address holding tokens with no matching book entry, or vice versa) surface immediately.

## Business outcomes

* **A real-time cap table** replaces end-of-day position files.
* **A market that never closes** gets a price feed that never closes.
* **Automated, auditable corporate actions** - dividends and splits execute as code, with a trace for every payment.
* **Chain-agnostic** - the same pattern covers Robinhood Chain, ERC-3643 securities on Ethereum/Polygon, and RWA chains like [Plume](/chains/plume) and [Mantra](/chains/mantra).

## Resources

* [Stream DEX trades](/mirror/guides/stream-DEX-trades)
* [Build a corporate-actions distributor](/compose/guides/build-a-corporate-actions-distributor)
* [Securities compliance & surveillance](/solutions/securities-compliance)

Can't find what you're looking for? Reach out to us at [support@goldsky.com](mailto:support@goldsky.com) for help.
