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
Transferevent is classified asmint(from zero address),burn(to zero address), ortransfer - 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
| Field | Type | Description |
|---|---|---|
id | String | ${txHash}-${logIndex} |
contractAddress | String | Token contract address |
from | String | Sender (0x0 for mints) |
to | String | Recipient (0x0 for burns) |
value | String | Amount as decimal string (uint256) |
decimals | Int | Token decimals from the contract (e.g. 18) |
transferType | String | "mint", "burn", or "transfer" |
blockNumber | BigInt | Block number |
timestamp | BigInt | Block timestamp |
transactionHash | String | Transaction hash |
logIndex | Int | Log index within the transaction |
meta | TransferMeta? | Linked contextual event |
Transfer Metadata
Contextual events linked to their ERC-20 transfers. The eventType discriminator indicates which fields are populated.
| Event Type | Description | Key Fields |
|---|---|---|
force_transfer | Admin-initiated forced transfer | admin, from, to, amount |
schedule_funded | Vesting schedule funded | from, to, amount, scheduleId, timelockId, commencementTimestamp, cancelableBy, + unwrapped schedule & timelock fields |
timelock_canceled | Timelock 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:
| Field | Type | Description |
|---|---|---|
scheduleReleaseCount | String | Total number of release events in the schedule |
scheduleDelayUntilFirstReleaseInSeconds | String | Delay before the first token release (seconds) |
scheduleInitialReleasePortionInBips | String | Portion released at commencement (basis points, 0-10000) |
schedulePeriodBetweenReleasesInSeconds | String | Time between subsequent releases (seconds) |
Timelock Fields
Populated for both schedule_funded and timelock_canceled events with details from the corresponding timelock:
| Field | Type | Description |
|---|---|---|
timelockTotalAmount | String | Total token amount locked in the timelock |
timelockTokensTransferred | String | Tokens 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:
| Operator | Example | Description |
|---|---|---|
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
- Query
transferssordered bytimestamp ASCto get all events in chronological order - Check
transferTypeto determine how to process each event:mint: Addvaluetotoaddress balanceburn: Subtractvaluefromfromaddress balancetransfer: Movevaluefromfromtoto
- Use
metarelation to get contextual information (vesting terms, force transfer admin, cancellation details) - Or query
accountssdirectly for current computed balances