> ## 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.

# Securities compliance & market surveillance

> Monitor ERC-3643 identity and freeze events and run real-time market surveillance on tokenized securities with Turbo and Compose.

Tokenized stocks are securities, and securities carry obligations that payment stablecoins do not: only eligible, identity-verified investors may hold them, transfers must respect restrictions, and the market they trade in must be surveilled for abuse. The [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643) standard builds identity and compliance into the token itself, and every check it performs emits an onchain event.

This guide covers both halves of securities compliance: **transfer-level compliance** (identity, eligibility, freezes) and **market surveillance** (wash trading, best execution). It is the securities counterpart to the payments-focused [AML monitoring](/solutions/compliance-monitoring) guide.

## Part A - transfer & identity compliance

ERC-3643 tokens enforce rules through an identity registry and a compliance contract, and they can freeze holdings or force recovery. Stream those events to keep a real-time compliance picture.

```mermaid theme={null}
flowchart LR
    A[ERC-3643 token] -->|identity + freeze events| B[Turbo pipeline]
    B --> C[(Compliance ledger)]
    B -->|eligibility check needed| D[Compose task]
    D -->|verify + attest| E[Identity provider / registry]
```

### Stream identity and freeze events

```yaml erc3643-compliance.yaml expandable theme={null}
name: erc3643-compliance
resource_size: s

sources:
  security_logs:
    type: dataset
    dataset_name: ethereum.raw_logs
    version: 1.2.0
    start_at: latest
    filter: address = lower('0xTOKENIZED_SECURITY')

transforms:
  events:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        address AS token,
        _gs_log_decode(
          _gs_fetch_abi('https://raw.githubusercontent.com/your-org/abis/main/erc3643.json', 'raw'),
          topics,
          data
        ) AS decoded,
        block_timestamp,
        transaction_hash,
        _gs_op
      FROM security_logs

  compliance_events:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        token,
        decoded.event_signature AS event,
        decoded.event_params[1] AS subject,
        to_timestamp(block_timestamp) AS at,
        transaction_hash,
        _gs_op
      FROM events
      WHERE decoded IS NOT NULL
        AND decoded.event_signature IN (
          'IdentityRegistered', 'IdentityRemoved',
          'AddressFrozen', 'TokensFrozen', 'TokensUnfrozen',
          'RecoverySuccess', 'Paused', 'Unpaused'
        )

sinks:
  compliance_ledger:
    type: postgres
    from: compliance_events
    schema: compliance
    table: security_events
    secret_name: MY_POSTGRES
    primary_key: id
```

This gives compliance a live feed of every registration, freeze, and recovery - the events an examiner will ask you to produce.

### Attest eligibility decisions with Compose

When a new holder must be onboarded or a transfer needs an eligibility check, route it to a [Compose](/compose/introduction) task that verifies against your identity provider and records the outcome in a TEE. The pattern is identical to the [AML screening task](/solutions/compliance-monitoring#step-4-attest-every-decision-with-compose): fetch the verification, write an attestation on-chain, and keep the full trace. That attestation is what makes an eligibility decision defensible.

## Part B - market surveillance

Tokenized equities trade 24/7 on open venues, which means the market-integrity checks a broker-dealer runs - wash trading, spoofing, best execution - now run against public onchain data in real time.

### Detect wash trades

A wash trade is a sale with **no change in beneficial ownership** - the same owner on both sides, usually across two different addresses. The operative word is *same*: it is not enough for both parties to be known investors. You have to resolve each address to its beneficial owner and check that the two owners **match**.

<Warning>
  A membership test like `dynamic_table_check('watchlist', sender) AND dynamic_table_check('watchlist', recipient)` is **wrong** here - it fires on *any* trade between two listed investors, not just self-trades, flooding surveillance with false positives. Dynamic tables answer "is this address known?", not "do these two addresses share an owner?"
</Warning>

Resolve both counterparties to a beneficial-owner ID with an [HTTP handler](/turbo-pipelines/transforms/http-handler) that calls your identity/KYC system, then flag trades where the two owners are equal.

```yaml wash-trade-surveillance.yaml expandable theme={null}
name: wash-trade-surveillance
resource_size: s

sources:
  trades:
    type: dataset
    dataset_name: robinhood_testnet.erc20_transfers
    version: 1.0.0
    start_at: latest

transforms:
  # Resolve each side to its beneficial owner via your identity service.
  # The endpoint echoes each row and adds sender_owner + recipient_owner.
  with_owners:
    type: handler
    from: trades
    url: https://identity.example.com/resolve-beneficial-owners
    primary_key: id
    secret_name: IDENTITY_API_SECRET
    schema_override:
      sender_owner: Utf8
      recipient_owner: Utf8

  # Self-trade: both addresses resolve to the SAME (non-null) owner
  suspected_wash:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        address AS token,
        sender,
        recipient,
        sender_owner AS beneficial_owner,
        amount,
        block_timestamp,
        transaction_hash,
        _gs_op
      FROM with_owners
      WHERE sender_owner IS NOT NULL
        AND sender_owner = recipient_owner

sinks:
  surveillance_alerts:
    type: webhook
    from: suspected_wash
    url: https://surveillance.example.com/alerts/wash-trade
    one_row_per_request: true
    secret_name: SURVEILLANCE_WEBHOOK_AUTH
```

Your resolver's ownership graph is the source of truth and is queried live, so linking new addresses to an owner takes effect on the next trade with no redeploy.

<Tip>
  To cut volume, add a SQL pre-filter before the handler that drops trades where neither side is a known investor - a [dynamic table](/turbo-pipelines/transforms/dynamic-tables) membership check is fine for *that*. Just don't mistake membership for detection; the same-owner comparison is what identifies a wash trade. If you maintain the address→owner map as a table, you can equivalently detect downstream with a Postgres self-join: join trades to the mapping on both `sender` and `recipient` and keep rows where the owner IDs match.
</Tip>

### Best execution

Compare each fill to the prevailing market price at the moment of the trade. Join your trade stream to the VWAP price feed from the [tokenized-equities data layer](/solutions/tokenized-equities#step-2-publish-a-24-7-price-feed) and flag fills that executed materially outside the spread - the raw material for a best-execution report.

### Attest and report with Compose

A surveillance alert becomes a case, and a case becomes a filing. A [Compose](/compose/introduction) task enriches each alert, opens a case in your surveillance system, and - where required - files a regulatory report, with every step [traced](/compose/debugging) and TEE-attested. Durable execution means a required report is never dropped because a downstream API blipped.

## Business outcomes

* **Real-time, not T+1, surveillance** - abuse is flagged as it settles.
* **Provable eligibility and freezes** - a live compliance ledger plus TEE-attested decisions answer "who was allowed to hold this, and why?"
* **One widening net** - the identity graph updates a running pipeline in seconds.
* **Data stays in your boundary** - compliance events and identity data live in your own database.

## Resources

* [ERC-3643 standard](https://eips.ethereum.org/EIPS/eip-3643)
* [AML monitoring (payments)](/solutions/compliance-monitoring)
* [Tokenized equities & RWA data layer](/solutions/tokenized-equities)

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