Skip to main content

Indexer & GraphQL API

Overview

A GraphQL indexer is available for integration, providing access to indexed on-chain data from EVM Security Token (v5) RestrictedLockupToken contracts. The indexer continuously monitors and indexes Transfer events, classifying them as mints, burns, or transfers, and linking contextual metadata from ForceTransferBetween, ScheduleFunded, and TimelockCanceled events.

Key Features

  • Transfer Classification: Every ERC-20 Transfer event is classified as mint (from zero address), burn (to zero address), or transfer
  • Contextual Metadata: Force transfers, vesting schedule funding, and timelock cancellations are linked to their corresponding ERC-20 transfers
  • On-Chain Enrichment: Release schedule parameters and timelock details are read from the contract and stored alongside event data
  • Account Balances: Real-time running balances per address per contract
  • GraphQL API: Unified GraphQL endpoint with filtering, sorting, and pagination
  • Multi-Contract: Indexes multiple RestrictedLockupToken contract addresses

Authentication

The API uses API key authentication via the X-API-Key header. The key is available upon request.

curl -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ _meta { status } }"}' \
https://evm-rwa-indexer.upside.gg/api/v1/graphql

Endpoints

Production

  • GraphQL Endpoint: POST https://evm-rwa-indexer.upside.gg/api/v1/graphql
  • GraphiQL UI: GET https://evm-rwa-indexer.upside.gg/api/v1/graphql (open in browser)
  • Health Check: GET https://evm-rwa-indexer.upside.gg/api/v1/health (no authentication required)
  • API Reference

Staging

  • GraphQL Endpoint: POST https://evm-rwa-indexer-staging.upside.gg/api/v1/graphql
  • GraphiQL UI: GET https://evm-rwa-indexer-staging.upside.gg/api/v1/graphql (open in browser)
  • Health Check: GET https://evm-rwa-indexer-staging.upside.gg/api/v1/health (no authentication required)

GraphQL Schema

The indexer exposes three main tables through its GraphQL API.

Transfers

Every ERC-20 Transfer event, with a transferType classification and optional link to contextual metadata.

query RecentTransfers {
transferss(
limit: 50
orderBy: "timestamp"
orderDirection: "desc"
) {
items {
id
contractAddress
from
to
value
transferType # "mint" | "burn" | "transfer"
timestamp
transactionHash
meta { # linked contextual event (if any)
eventType # "force_transfer" | "schedule_funded" | "timelock_canceled"
admin
scheduleId
scheduleReleaseCount
schedulePeriodBetweenReleasesInSeconds
canceledBy
canceledAmount
paidAmount
}
}
totalCount
pageInfo { hasNextPage endCursor }
}
}

Transfer Fields

FieldTypeDescription
idString${txHash}-${logIndex}
contractAddressStringToken contract address
fromStringSender (0x0 for mints)
toStringRecipient (0x0 for burns)
valueStringAmount as decimal string (uint256)
decimalsIntToken decimals from the contract (e.g. 18)
transferTypeString"mint", "burn", or "transfer"
blockNumberBigIntBlock number
timestampBigIntBlock timestamp
transactionHashStringTransaction hash
logIndexIntLog index within the transaction
metaTransferMeta?Linked contextual event

Transfer Metadata

Contextual events linked to their ERC-20 transfers. The eventType discriminator indicates which fields are populated.

Event TypeDescriptionKey Fields
force_transferAdmin-initiated forced transferadmin, from, to, amount
schedule_fundedVesting schedule fundedfrom, to, amount, scheduleId, timelockId, commencementTimestamp, cancelableBy, + unwrapped schedule & timelock fields
timelock_canceledTimelock cancellation (may produce 2 transfers)canceledBy, target, timelockIndex, reclaimTokenTo, canceledAmount, paidAmount, scheduleId, + unwrapped schedule & timelock fields

Release Schedule Fields

Populated for both schedule_funded and timelock_canceled events with the actual vesting parameters from the contract:

FieldTypeDescription
scheduleReleaseCountStringTotal number of release events in the schedule
scheduleDelayUntilFirstReleaseInSecondsStringDelay before the first token release (seconds)
scheduleInitialReleasePortionInBipsStringPortion released at commencement (basis points, 0-10000)
schedulePeriodBetweenReleasesInSecondsStringTime between subsequent releases (seconds)

Timelock Fields

Populated for both schedule_funded and timelock_canceled events with details from the corresponding timelock:

FieldTypeDescription
timelockTotalAmountStringTotal token amount locked in the timelock
timelockTokensTransferredStringTokens already transferred/claimed from the timelock

Accounts

Running token balances per address per contract, updated on every Transfer event.

query AccountBalances {
accountss(
where: { contractAddress: "0x5CFE56E1D83E70fe367780B0bFA0839A6F66D9DA" }
orderBy: "balance"
orderDirection: "desc"
limit: 100
) {
items {
address
balance
lastUpdatedTimestamp
}
totalCount
}
}

Example Queries

Filter Mints Only

query Mints {
transferss(
where: { transferType: "mint" }
orderBy: "timestamp"
orderDirection: "desc"
limit: 20
) {
items {
to
value
timestamp
meta {
eventType
scheduleId
commencementTimestamp
scheduleReleaseCount
schedulePeriodBetweenReleasesInSeconds
scheduleInitialReleasePortionInBips
}
}
totalCount
}
}

Query Vesting Events

query VestingSchedules {
transferMetas(
where: { eventType: "schedule_funded" }
orderBy: "timestamp"
orderDirection: "desc"
limit: 20
) {
items {
from
to
amount
scheduleId
timelockId
commencementTimestamp
cancelableBy
scheduleReleaseCount
scheduleDelayUntilFirstReleaseInSeconds
scheduleInitialReleasePortionInBips
schedulePeriodBetweenReleasesInSeconds
timelockTotalAmount
timelockTokensTransferred
transfers {
items { from to value transferType }
}
}
}
}

Query Force Transfers

query ForceTransfers {
transferMetas(
where: { eventType: "force_transfer" }
limit: 20
) {
items {
admin
from
to
amount
timestamp
transfers {
items { id transactionHash }
}
}
}
}

Transfers by Address

query TransfersByAddress {
transferss(
where: { from: "0x1234..." }
orderBy: "timestamp"
orderDirection: "desc"
limit: 50
) {
items {
to
value
transferType
timestamp
meta { eventType }
}
}
}

Pagination

Use limit and offset for cursor-based pagination:

# Page 1
query { transferss(limit: 100, offset: 0) { items { id } totalCount } }

# Page 2
query { transferss(limit: 100, offset: 100) { items { id } totalCount } }

Filtering

All list queries support the following filtering syntax:

OperatorExampleDescription
field{ transferType: "mint" }Exact match
field_not{ transferType_not: "burn" }Not equal
field_in{ transferType_in: ["mint", "burn"] }In list
field_gt / field_gte{ timestamp_gt: "1700000000" }Greater than
field_lt / field_lte{ timestamp_lt: "1700000000" }Less than
field_contains{ from_contains: "0xab" }Substring match

Combine filters with AND / OR:

{
transferss(where: {
AND: [
{ transferType: "mint" },
{ timestamp_gt: "1700000000" }
]
}) {
items { id }
}
}

Using for Cap Table Building

  1. Query transferss ordered by timestamp ASC to get all events in chronological order
  2. Check transferType to determine how to process each event:
    • mint: Add value to to address balance
    • burn: Subtract value from from address balance
    • transfer: Move value from from to to
  3. Use meta relation to get contextual information (vesting terms, force transfer admin, cancellation details)
  4. Or query accountss directly for current computed balances