Context
Phase 2 of the Trident PRD requires a GraphQL endpoint at /graphql. GraphQL is increasingly the preferred query interface for blockchain indexers (The Graph pioneered this, and it has become the standard) because it allows clients to request exactly the fields they need, compose complex filters in a single request, and use the same endpoint for both queries and subscriptions.
Adding GraphQL does not replace the REST API — both stay available. GraphQL is additive and targets developers who prefer it for its flexibility and self-documenting schema.
What needs to be built
Schema design
scalar JSON
scalar DateTime
enum EventType {
CONTRACT
SYSTEM
DIAGNOSTIC
}
type SorobanEvent {
id: ID!
contractId: String!
ledgerSequence: Int!
ledgerTimestamp: DateTime!
topic0: String
topic1: String
topic2: String
topic3: String
data: JSON
txHash: String!
eventType: EventType!
network: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
type EventEdge {
node: SorobanEvent!
cursor: String!
}
type EventConnection {
edges: [EventEdge!]!
pageInfo: PageInfo!
totalCount: Int
}
type Query {
events(
contractId: String
topic0: String
topic1: String
fromLedger: Int
toLedger: Int
network: String
first: Int
after: String
): EventConnection!
event(id: ID!): SorobanEvent
}
The EventConnection type follows the Relay Cursor Connection Specification — this is the de facto standard for GraphQL pagination and what Apollo Client, urql, and Relay all expect out of the box.
Library recommendation
99designs/gqlgen — schema-first, generates type-safe Go resolvers from the GraphQL SDL. The generated types eliminate a whole class of bugs where the Go struct and the schema drift apart. Alternatively, graph-gophers/graphql-go for a no-code-gen approach.
Resolver implementation
events resolver:
event resolver:
- Calls gRPC
GetEvent
- Returns null (not an error) when not found, per GraphQL convention
Authentication
Same X-API-Key header check as REST. The existing middleware should apply to /graphql without modification.
Error handling
GraphQL errors go in the errors array in the response body, NOT as HTTP 4xx/5xx. The HTTP status for a GraphQL request is almost always 200 (even for application errors). The only time to return non-200 is a malformed JSON body (400) or auth failure (401).
Transport
POST /graphql with Content-Type: application/json body:
{ "query": "{ events(contractId: \"C...\") { edges { node { id txHash } } } }" }
Also support GET /graphql?query=...&variables=... for simple queries (required by some GraphQL clients).
Acceptance criteria
Dependencies
Context
Phase 2 of the Trident PRD requires a GraphQL endpoint at
/graphql. GraphQL is increasingly the preferred query interface for blockchain indexers (The Graph pioneered this, and it has become the standard) because it allows clients to request exactly the fields they need, compose complex filters in a single request, and use the same endpoint for both queries and subscriptions.Adding GraphQL does not replace the REST API — both stay available. GraphQL is additive and targets developers who prefer it for its flexibility and self-documenting schema.
What needs to be built
Schema design
The
EventConnectiontype follows the Relay Cursor Connection Specification — this is the de facto standard for GraphQL pagination and what Apollo Client, urql, and Relay all expect out of the box.Library recommendation
99designs/gqlgen— schema-first, generates type-safe Go resolvers from the GraphQL SDL. The generated types eliminate a whole class of bugs where the Go struct and the schema drift apart. Alternatively,graph-gophers/graphql-gofor a no-code-gen approach.Resolver implementation
eventsresolver:ListEventsRequestaftercursor (same opaque encoding as REST pagination from feat(go-api): opaque pagination cursor encoding #44)EventConnectionwithPageInfoeventresolver:GetEventAuthentication
Same
X-API-Keyheader check as REST. The existing middleware should apply to/graphqlwithout modification.Error handling
GraphQL errors go in the
errorsarray in the response body, NOT as HTTP 4xx/5xx. The HTTP status for a GraphQL request is almost always 200 (even for application errors). The only time to return non-200 is a malformed JSON body (400) or auth failure (401).Transport
POST /graphqlwithContent-Type: application/jsonbody:{ "query": "{ events(contractId: \"C...\") { edges { node { id txHash } } } }" }Also support
GET /graphql?query=...&variables=...for simple queries (required by some GraphQL clients).Acceptance criteria
POST /graphqlresolveseventsquery with all filter combinations (contract, topic0, ledger range, network, pagination)POST /graphqlresolveseventquery by ID, returns null (not error) for missing IDpageInfo.endCursorproduces correct next page when passed asafter{ __schema { types { name } } })errorsarray formatDependencies