@0xintuition/graphql
v3.0.1
Published
Intuition GraphQL
Keywords
Readme
@0xintuition/graphql
GraphQL API Documentation for the Intuition Protocol
This package provides comprehensive documentation for querying the Intuition knowledge graph via GraphQL. Use any GraphQL client in any language to interact with atoms, triples, vaults, positions, and more.
Table of Contents
- Introduction
- Core Concepts
- Getting Started
- Schema Reference
- Common Query Patterns
- Mutations
- Subscriptions
- Best Practices
- Example Queries
- Advanced Examples
- Code Generation
- Anti-Patterns to Avoid
- Resources
Introduction
The Intuition GraphQL API provides access to the complete Intuition knowledge graph, including atoms (entities), triples (relationships), vaults (asset pools), and user positions. The API is powered by Hasura and offers rich querying capabilities with filtering, sorting, pagination, and aggregations.
Public Endpoints
No authentication required:
- Mainnet:
https://mainnet.intuition.sh/v1/graphql - Testnet:
https://testnet.intuition.sh/v1/graphql
Public Gated Pinning Endpoint
Pinning mutations use a separate public gated endpoint:
- Pinning:
https://pin.intuition.systems/v1/graphql
Generated pinning helpers such as usePinThingMutation.fetcher route to this endpoint automatically and require an apikey header. Keep this key on a server. Browser apps should call their own server route instead of embedding the key in client-side code.
Interactive Explorers
Explore the API interactively with Apollo Studio Sandbox:
Core Concepts
Atoms
Atoms are the fundamental entities in the Intuition knowledge graph. Each atom represents an identity, concept, or piece of data (e.g., a person, organization, tag, or blockchain address).
Triples
Triples are statements that connect atoms in subject-predicate-object relationships. For example: (Alice, knows, Bob) or (Document, hasTag, TypeScript).
Vaults
Vaults are asset pools associated with atoms and triples. Users deposit assets into vaults and receive shares based on bonding curves. See the @0xintuition/protocol documentation for details on bonding curves and vault mechanics.
Positions
Positions represent user ownership (shares) in vaults. Each position tracks an account's shares in a specific vault.
Accounts
Accounts are blockchain addresses participating in the protocol, including:
- User wallets
- Atom wallets (smart contract wallets for atoms)
- Protocol vaults
Deposits & Redemptions
Deposits are transactions where users add assets to vaults and receive shares. Redemptions are the reverse: users burn shares to withdraw assets.
Events
Events capture the complete on-chain event history, including deposits, redemptions, atom creation, triple creation, and more.
Stats
Stats provide protocol-wide statistics and aggregated metrics.
Getting Started
The Intuition GraphQL API works with any GraphQL client. Below are minimal examples for popular clients across different languages.
JavaScript / TypeScript
For JavaScript/TypeScript projects: Instead of hardcoding API endpoints, import them from this package:
import { API_URL_DEV, API_URL_PROD } from '@0xintuition/graphql'
// API_URL_PROD = 'https://mainnet.intuition.sh/v1/graphql'
// API_URL_DEV = 'https://testnet.intuition.sh/v1/graphql'graphql-request
import { API_URL_PROD } from '@0xintuition/graphql'
import { GraphQLClient } from 'graphql-request'
const client = new GraphQLClient(API_URL_PROD)
const query = `
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
label
image
}
}
`
const data = await client.request(query, { id: '0x...' })Apollo Client
import { API_URL_PROD } from '@0xintuition/graphql'
import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
const client = new ApolloClient({
uri: API_URL_PROD,
cache: new InMemoryCache(),
})
const { data } = await client.query({
query: gql`
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
label
}
}
`,
variables: { id: '0x...' },
})urql
import { API_URL_PROD } from '@0xintuition/graphql'
import { createClient } from 'urql'
const client = createClient({
url: API_URL_PROD,
})
const result = await client.query(query, { id: '0x...' }).toPromise()Plain fetch
import { API_URL_PROD } from '@0xintuition/graphql'
const response = await fetch(API_URL_PROD, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query GetAtom($id: String!) { atom(term_id: $id) { term_id label } }`,
variables: { id: '0x...' },
}),
})
const { data } = await response.json()Python
gql
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
transport = RequestsHTTPTransport(url='https://mainnet.intuition.sh/v1/graphql')
client = Client(transport=transport)
query = gql('''
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
label
}
}
''')
result = client.execute(query, variable_values={'id': '0x...'})python-graphql-client
from python_graphql_client import GraphqlClient
client = GraphqlClient(endpoint='https://mainnet.intuition.sh/v1/graphql')
query = '''
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
label
}
}
'''
data = client.execute(query=query, variables={'id': '0x...'})python-graphql-client documentation
Go
machinebox/graphql
package main
import (
"context"
"github.com/machinebox/graphql"
)
func main() {
client := graphql.NewClient("https://mainnet.intuition.sh/v1/graphql")
req := graphql.NewRequest(`
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
label
}
}
`)
req.Var("id", "0x...")
var response map[string]interface{}
client.Run(context.Background(), req, &response)
}machinebox/graphql documentation
Rust
graphql-client
use graphql_client::{GraphQLQuery, Response};
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "schema.graphql",
query_path = "get_atom.graphql",
)]
struct GetAtom;
let client = reqwest::Client::new();
let variables = get_atom::Variables {
id: "0x...".to_string(),
};
let response = client
.post("https://mainnet.intuition.sh/v1/graphql")
.json(&GetAtom::build_query(variables))
.send()
.await?;Schema Reference
Getting the Schema
Generate the GraphQL schema via introspection:
# Mainnet
npx get-graphql-schema https://mainnet.intuition.sh/v1/graphql > schema.graphql
# Testnet
npx get-graphql-schema https://testnet.intuition.sh/v1/graphql > schema.graphqlSchema Features
The Hasura-powered GraphQL schema provides:
Filtering with where Clauses
Use boolean expressions to filter results:
query GetRecentAtoms {
atoms(
where: { created_at: { _gte: "2024-01-01" }, type: { _eq: Person } }
limit: 10
) {
term_id
label
}
}Available operators:
_eq,_neq- Equality_gt,_gte,_lt,_lte- Comparisons_in,_nin- Array membership_like,_ilike- Pattern matching (case-sensitive/insensitive)_is_null- Null checks_and,_or,_not- Boolean logic
Sorting with order_by
query GetTopAtoms {
atoms(
order_by: [{ term: { total_market_cap: desc } }, { created_at: desc }]
limit: 10
) {
term_id
label
}
}Pagination
query GetAtomsPage($limit: Int!, $offset: Int!) {
atoms(limit: $limit, offset: $offset, order_by: { created_at: desc }) {
term_id
label
}
atoms_aggregate {
aggregate {
count
}
}
}Aggregations
query GetPositionStats($accountId: String!) {
positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
}
}
}Relationships
GraphQL relationships allow nested queries:
query GetAtomWithCreator($id: String!) {
atom(term_id: $id) {
term_id
label
creator {
id
label
image
}
}
}Primary Key Lookups
query GetAtom($id: String!) {
atom(term_id: $id) {
# Direct lookup by primary key
term_id
label
}
}Common Query Patterns
Querying Atoms
Get Single Atom
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
data
label
image
type
created_at
creator {
id
label
}
}
}List Atoms with Filtering
query GetAtomsByType($type: atom_type!, $limit: Int!) {
atoms(
where: { type: { _eq: $type } }
order_by: { created_at: desc }
limit: $limit
) {
term_id
label
image
created_at
}
}Get Atom with Vault Details
query GetAtomWithVault($id: String!, $curveId: numeric!) {
atom(term_id: $id) {
term_id
label
term {
vaults(where: { curve_id: { _eq: $curveId } }) {
term_id
curve_id
total_shares
current_share_price
position_count
}
}
}
}Querying Triples
Get Single Triple
query GetTriple($id: String!) {
triple(term_id: $id) {
term_id
subject {
term_id
label
image
}
predicate {
term_id
label
}
object {
term_id
label
image
}
}
}Filter Triples by Subject/Predicate/Object
query GetTriplesBySubject($subjectId: String!, $limit: Int!) {
triples(
where: { subject_id: { _eq: $subjectId } }
order_by: { created_at: desc }
limit: $limit
) {
term_id
predicate {
label
}
object {
label
}
}
}Get Triples with Positions
query GetTriplesWithPositions(
$where: triples_bool_exp!
$curveId: numeric!
$limit: Int!
) {
triples(where: $where, limit: $limit) {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
term {
vaults(where: { curve_id: { _eq: $curveId } }) {
total_shares
position_count
}
}
counter_term {
vaults(where: { curve_id: { _eq: $curveId } }) {
total_shares
position_count
}
}
}
}Querying Positions
Get User Positions
query GetUserPositions($accountId: String!, $limit: Int!) {
positions(
where: { account_id: { _eq: $accountId } }
order_by: { shares: desc }
limit: $limit
) {
id
shares
vault {
term_id
total_shares
current_share_price
}
}
}Get Aggregate Position Data
query GetPositionAggregates($accountId: String!) {
positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
}
}
}Best Practice: Use aggregates when you only need counts or sums - don't fetch all nodes just to count them.
Querying Vaults
Get Vault Details
query GetVault($termId: String!, $curveId: numeric!) {
vault(term_id: $termId, curve_id: $curveId) {
term_id
curve_id
total_shares
total_assets
current_share_price
position_count
positions(limit: 10, order_by: { shares: desc }) {
account {
id
label
}
shares
}
}
}Search Queries
Global Search
query GlobalSearch(
$searchTerm: String
$atomsLimit: Int
$accountsLimit: Int
$triplesLimit: Int
) {
accounts(
where: {
_or: [
{ label: { _ilike: $searchTerm } }
{ atom: { data: { _ilike: $searchTerm } } }
]
}
limit: $accountsLimit
) {
id
label
image
}
atoms(
where: {
_or: [
{ label: { _ilike: $searchTerm } }
{ data: { _ilike: $searchTerm } }
]
}
limit: $atomsLimit
) {
term_id
label
image
}
triples(
where: {
_or: [
{ subject: { label: { _ilike: $searchTerm } } }
{ predicate: { label: { _ilike: $searchTerm } } }
{ object: { label: { _ilike: $searchTerm } } }
]
}
limit: $triplesLimit
) {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
}
}Semantic Search
query SemanticSearch($query: String!, $limit: Int) {
search_term(args: { query: $query }, limit: $limit) {
atom {
term_id
label
type
}
}
}Pagination Patterns
Offset-Based Pagination
query GetAtomsPage($limit: Int!, $offset: Int!) {
total: atoms_aggregate {
aggregate {
count
}
}
atoms(limit: $limit, offset: $offset, order_by: { created_at: desc }) {
term_id
label
created_at
}
}Variables:
{
"limit": 20,
"offset": 40
}This fetches page 3 (items 41-60) when using 20 items per page.
Database Functions
The API provides backend functions for complex queries that would be inefficient to perform client-side.
Following/Social Queries
Get accounts a user follows:
query GetFollowing($address: String!) {
following(args: { address: $address }) {
id
label
image
atom {
term_id
label
}
}
following_aggregate(args: { address: $address }) {
aggregate {
count
}
}
}Variables:
{
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
}Get positions from accounts you follow:
query GetPositionsFromFollowing(
$address: String!
$limit: Int!
$offset: Int!
) {
positions_from_following(
args: { address: $address }
limit: $limit
offset: $offset
order_by: { shares: desc }
) {
id
shares
account {
id
label
}
vault {
term_id
current_share_price
}
}
}Get signals from followed accounts:
query GetSignalsFromFollowing($address: String!, $limit: Int!) {
signals_from_following(
args: { address: $address }
limit: $limit
order_by: { created_at: desc }
) {
id
event_type
created_at
account {
id
label
}
atom {
term_id
label
}
}
}Advanced Position Search
Search positions with complex filtering:
query SearchPositions($addresses: _text!, $searchFields: jsonb!) {
search_positions_on_subject(
args: { addresses: $addresses, search_fields: $searchFields }
) {
id
shares
account {
id
label
}
vault {
term_id
term {
atom {
label
image
}
}
}
}
}Variables:
{
"addresses": "{0xabc..., 0xdef...}",
"searchFields": {
"min_shares": "1000000000000000000",
"term_type": "atom"
}
}Social Search
Search terms within your social graph:
query SearchTermsFromFollowing(
$address: String!
$query: String!
$limit: Int
) {
search_term_from_following(
args: { address: $address, query: $query }
limit: $limit
) {
atom {
term_id
label
type
image
}
}
}Time-Series Analysis
The API includes pre-computed time-series aggregations for efficient analytics.
Price Trend Queries
Daily price trends:
query GetDailyPriceTrends($termId: String!, $curveId: numeric!, $limit: Int!) {
share_price_change_stats_daily(
where: { term_id: { _eq: $termId }, curve_id: { _eq: $curveId } }
order_by: { bucket: desc }
limit: $limit
) {
bucket
first_share_price
last_share_price
difference
change_count
}
}Variables:
{
"termId": "0x...",
"curveId": "1",
"limit": 30
}For different time granularities, use:
share_price_change_stats_hourly- Hourly aggregationsshare_price_change_stats_weekly- Weekly aggregationsshare_price_change_stats_monthly- Monthly aggregations
Signal Statistics
Query aggregated signal data over time:
query GetSignalStats($limit: Int!) {
signal_stats_daily(order_by: { bucket: desc }, limit: $limit) {
bucket
deposit_count
redemption_count
total_volume
}
}Denormalized Tables
Predicate-Object Aggregations
Query pre-aggregated collections grouped by (predicate, object):
query GetPopularCollections($predicateId: String!, $limit: Int!) {
predicate_objects(
where: { predicate_id: { _eq: $predicateId } }
order_by: { triple_count: desc }
limit: $limit
) {
predicate_id
object_id
triple_count
total_market_cap
total_position_count
object {
term_id
label
image
}
}
}Variables:
{
"predicateId": "0x...",
"limit": 20
}This is more efficient than manually aggregating triples with the same predicate and object.
Statistical Aggregations
Beyond count and sum, the API supports advanced statistical aggregations:
query GetPositionStatistics($accountId: String!) {
positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
avg {
shares
}
min {
shares
}
max {
shares
}
stddev {
shares
}
stddev_pop {
shares
}
stddev_samp {
shares
}
variance {
shares
}
var_pop {
shares
}
var_samp {
shares
}
}
}
}Use cases:
stddev- Identify outliers in share distributionsvariance- Measure position concentrationavg- Calculate average position size
Mutations
The public GraphQL API is read-oriented. Pinning mutations are available through the public gated pinning endpoint and require an Intuition pinning API key. Blockchain state changes (creating atoms, triples, deposits, redemptions) are performed through direct smart contract transactions, not GraphQL mutations.
The public gated pinning endpoint is used for pinThing, pinPerson, pinOrganization, uploadJsonToIpfs, uploadImage, and uploadImageFromUrl. Configure a key once with configureClient({ pinApiKey }) or pass an apikey header per pinning request.
Pinning Metadata to IPFS
Pin Thing
Pin a "Thing" object (general entity) to IPFS. The raw GraphQL mutation returns an IPFS URI:
mutation PinThing($thing: PinThingInput!) {
pinThing(thing: $thing) {
uri
}
}Variables:
{
"thing": {
"name": "TypeScript Programming Language",
"description": "A strongly typed programming language that builds on JavaScript",
"image": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"url": "https://www.typescriptlang.org"
}
}Response:
{
"data": {
"pinThing": {
"uri": "ipfs://bafkreib..."
}
}
}The generated package helper uses the same endpoint routing:
import {
configureClient,
requestPinThing,
usePinThingMutation,
} from '@0xintuition/graphql'
configureClient({ pinApiKey: process.env.INTUITION_PIN_API_KEY })
const uri = await requestPinThing({
name: 'TypeScript Programming Language',
description:
'A strongly typed programming language that builds on JavaScript',
image: 'ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy',
url: 'https://www.typescriptlang.org',
})
const data = await usePinThingMutation.fetcher({
name: 'TypeScript Programming Language',
description:
'A strongly typed programming language that builds on JavaScript',
image: 'ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy',
url: 'https://www.typescriptlang.org',
})()Use requestPinThing when you need a guaranteed URI string; it throws if the
pinning response does not include one. Generated mutation fetchers return the raw
GraphQL response payload.
You can also pass a key per request:
const data = await usePinThingMutation.fetcher(
{
name: 'TypeScript Programming Language',
description:
'A strongly typed programming language that builds on JavaScript',
image: 'ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy',
url: 'https://www.typescriptlang.org',
},
{ apikey: process.env.INTUITION_PIN_API_KEY },
)()Pin Person
Pin a Person entity to IPFS:
mutation PinPerson($person: PinPersonInput!) {
pinPerson(person: $person) {
uri
}
}Variables:
{
"person": {
"name": "Vitalik Buterin",
"description": "Co-founder of Ethereum",
"email": "[email protected]",
"identifier": "vitalik.eth",
"image": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"url": "https://vitalik.ca"
}
}Pin Organization
Pin an Organization entity to IPFS:
mutation PinOrganization($organization: PinOrganizationInput!) {
pinOrganization(organization: $organization) {
uri
}
}Variables:
{
"organization": {
"name": "Ethereum Foundation",
"description": "Non-profit organization supporting Ethereum development",
"email": "[email protected]",
"image": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"url": "https://ethereum.foundation"
}
}Uploading JSON to IPFS
Pin arbitrary JSON data to IPFS:
Requires the public gated pinning endpoint and an Intuition pinning API key.
mutation UploadJson($json: jsonb!) {
uploadJsonToIpfs(json: $json) {
hash
name
size
}
}Variables:
{
"json": {
"type": "custom_metadata",
"attributes": [
{ "trait_type": "Category", "value": "DeFi" },
{ "trait_type": "Chain", "value": "Base" }
],
"version": "1.0"
}
}Image Upload
Upload Image (Base64)
Upload an image from base64-encoded data:
Requires the public gated pinning endpoint and an Intuition pinning API key.
mutation UploadImage($image: UploadImageInput!) {
uploadImage(image: $image) {
url
classification
}
}Variables:
{
"image": {
"contentType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAUA...",
"filename": "avatar.png"
}
}Response:
{
"data": {
"uploadImage": {
"url": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"classification": "safe"
}
}
}The classification field indicates if the image passed content moderation.
Upload Image from URL
Upload an image from a public URL:
Requires the public gated pinning endpoint and an Intuition pinning API key.
mutation UploadImageFromUrl($image: UploadImageFromUrlInput!) {
uploadImageFromUrl(image: $image) {
url
classification
}
}Variables:
{
"image": {
"url": "https://example.com/images/logo.png"
}
}Mutation Workflow
Typical workflow for creating an atom with pinned metadata:
- Pin metadata using
pinThing,pinPerson, orpinOrganizationon the public gated pinning endpoint - Get IPFS URI from the mutation response
- Create atom on-chain via smart contract transaction using the IPFS URI
- Query the GraphQL API to fetch the newly created atom with resolved metadata
Subscriptions
The GraphQL API supports real-time subscriptions for live data updates using cursor-based streaming. All queryable entities support corresponding _stream subscriptions.
Basic Subscription Pattern
Subscribe to new atoms:
subscription WatchAtoms(
$cursor: [atoms_stream_cursor_input]!
$batchSize: Int!
) {
atoms_stream(cursor: $cursor, batch_size: $batchSize) {
term_id
label
image
created_at
}
}Variables:
{
"cursor": [
{
"initial_value": { "created_at": "2024-01-01T00:00:00Z" },
"ordering": "ASC"
}
],
"batchSize": 10
}Cursor-Based Streaming
Subscriptions use cursors to enable resumable streams:
Cursor Configuration
initial_value: Starting point for the stream (e.g., timestamp, ID)ordering: Sort direction (ASCorDESC)batch_size: Number of items per batch
Resumable Stream Example
subscription WatchPositions($cursor: [positions_stream_cursor_input]!) {
positions_stream(
cursor: $cursor
batch_size: 20
where: { shares: { _gt: "0" } }
) {
id
shares
account {
id
label
}
vault {
term_id
current_share_price
}
}
}Variables:
{
"cursor": [
{
"initial_value": { "created_at": "2024-12-01T00:00:00Z" },
"ordering": "DESC"
}
]
}To resume from where you left off, update initial_value to the last received item's cursor value.
Common Subscription Use Cases
Monitor New Triples
subscription WatchNewTriples($cursor: [triples_stream_cursor_input]!) {
triples_stream(cursor: $cursor, batch_size: 5) {
term_id
created_at
subject {
label
}
predicate {
label
}
object {
label
}
}
}Track Price Changes
subscription WatchPriceChanges(
$cursor: [share_price_changes_stream_cursor_input]!
$termId: String!
) {
share_price_changes_stream(
cursor: $cursor
batch_size: 10
where: { term_id: { _eq: $termId } }
) {
term_id
curve_id
old_price
new_price
price_change
created_at
}
}Live Signal Feed
subscription WatchSignals(
$cursor: [deposits_stream_cursor_input, redemptions_stream_cursor_input]!
) {
deposits_stream(cursor: $cursor, batch_size: 10) {
id
event_type
sender_id
receiver_id
assets_for_receiver
shares_for_receiver
created_at
}
redemptions_stream(cursor: $cursor, batch_size: 10) {
id
event_type
receiver_id
assets_for_receiver
shares_from_receiver
created_at
}
}Subscription Best Practices
Use subscriptions when:
- Building real-time dashboards
- Monitoring live protocol activity
- Tracking position changes
- Creating notification systems
Use polling when:
- Data updates infrequently
- Real-time updates aren't critical
- Minimizing server connections is important
Best Practices
1. Avoid Over-Fetching
❌ Bad: Fetching all fields when you only need a few
query GetAtoms {
atoms(limit: 10) {
term_id
data
label
image
emoji
type
wallet_id
block_number
created_at
transaction_hash
creator_id
creator {
id
label
image
atom_id
type
}
# ... many more fields you don't need
}
}✅ Good: Request only what you need
query GetAtoms {
atoms(limit: 10) {
term_id
label
image
}
}2. Use Aggregates Efficiently
❌ Bad: Fetching all nodes just to count
query CountPositions($accountId: String!) {
positions(where: { account_id: { _eq: $accountId } }) {
id # Fetching all data just to count
}
}✅ Good: Use aggregates
query CountPositions($accountId: String!) {
positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
}
}
}3. Combine Aggregates with Nodes When Needed
✅ Efficient pattern: Get both count and paginated data in one query
query GetPositionsWithCount($accountId: String!, $limit: Int!, $offset: Int!) {
total: positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
}
}
positions(
where: { account_id: { _eq: $accountId } }
limit: $limit
offset: $offset
) {
id
shares
vault {
term_id
current_share_price
}
}
}4. Use Fragments for Reusable Structures
❌ Bad: Duplicating field selections
query GetTriple($id: String!) {
triple(term_id: $id) {
subject {
term_id
label
image
creator {
id
label
}
}
predicate {
term_id
label
image
creator {
id
label
}
}
object {
term_id
label
image
creator {
id
label
}
}
}
}✅ Good: Using fragments
fragment AtomBasics on atoms {
term_id
label
image
creator {
id
label
}
}
query GetTriple($id: String!) {
triple(term_id: $id) {
subject {
...AtomBasics
}
predicate {
...AtomBasics
}
object {
...AtomBasics
}
}
}5. Use Variables for Dynamic Values
❌ Bad: Hardcoding values
query {
atoms(where: { type: { _eq: Person } }) {
term_id
label
}
}✅ Good: Using variables
query GetAtomsByType($type: atom_type!) {
atoms(where: { type: { _eq: $type } }) {
term_id
label
}
}6. Filter Early and Specifically
✅ Efficient filtering:
query GetRecentPersonAtoms($since: timestamptz!) {
atoms(
where: { type: { _eq: Person }, created_at: { _gte: $since } }
limit: 100
) {
term_id
label
}
}7. Use Appropriate Comparison Operators
❌ Bad: Using _ilike for exact matches
query GetAccount($address: String!) {
accounts(where: { id: { _ilike: $address } }) {
id
label
}
}✅ Good: Use _eq or primary key lookup
query GetAccount($address: String!) {
account(id: $address) {
id
label
}
}8. Paginate Large Result Sets
Always use limit and offset for queries that could return many results:
query GetAllAtoms($limit: Int!, $offset: Int!) {
atoms(limit: $limit, offset: $offset, order_by: { created_at: desc }) {
term_id
label
}
}9. Leverage Pre-Computed Statistics
Use time-series aggregation tables instead of computing statistics client-side:
❌ Bad: Computing trends from raw events
query GetPriceHistory($termId: String!) {
# Fetching all price changes then computing daily aggregates in app
share_price_changes(
where: { term_id: { _eq: $termId } }
order_by: { created_at: asc }
) {
created_at
old_price
new_price
}
}✅ Good: Using pre-computed daily statistics
query GetDailyPriceStats($termId: String!, $curveId: numeric!) {
share_price_change_stats_daily(
where: { term_id: { _eq: $termId }, curve_id: { _eq: $curveId } }
order_by: { bucket: desc }
limit: 30
) {
bucket
first_share_price
last_share_price
difference
change_count
}
}When to use pre-computed tables:
- Building charts/graphs for analytics dashboards
- Computing trends over time
- Displaying aggregate metrics by time period
Available time-series tables:
share_price_change_stats_daily,_hourly,_weekly,_monthlysignal_stats_daily,_hourly,_monthly
10. Use Database Functions for Complex Queries
Leverage backend functions instead of filtering large datasets client-side:
❌ Bad: Manual filtering for social queries
query GetFollowingManually($address: String!) {
# First get all positions for an address
my_positions: positions(where: { account_id: { _eq: $address } }) {
vault {
term {
triple {
# Check if it's a "follows" relationship...
# This is inefficient and complex
}
}
}
}
# Then filter in application code...
}✅ Good: Using database functions
query GetFollowingEfficiently($address: String!) {
following(args: { address: $address }) {
id
label
atom {
term_id
label
}
}
}Available database functions:
following- Get accounts a user followspositions_from_following- Social feed of positionssearch_positions_on_subject- Complex position filteringsearch_term- Full-text searchsearch_term_from_following- Search within social graphsignals_from_following- Activity from followed accounts
Benefits:
- Faster query execution (runs in database, not client)
- Less data transferred over network
- More maintainable code
11. Choose Subscriptions vs Polling Appropriately
Use subscriptions for real-time features, polling for everything else:
✅ Use subscriptions when:
- Building real-time dashboards
- Monitoring live protocol activity (e.g., new positions, price changes)
- Creating notification systems
- User expects immediate updates
- Data changes frequently (multiple times per minute)
Example subscription use cases:
subscription WatchMyPositions($cursor: [positions_stream_cursor_input]!) {
positions_stream(cursor: $cursor, batch_size: 10) {
id
shares
vault {
current_share_price
}
}
}✅ Use polling when:
- Data updates infrequently (e.g., daily statistics)
- Real-time updates aren't critical for UX
- Minimizing server connections is important
- Building static reports or analytics
Example polling pattern:
query GetStats {
stats {
total_accounts
total_atoms
total_triples
}
}
# Poll every 30 seconds or on user actionSubscription cursor management:
- Always provide
initial_valueto start from a specific point - Use
batch_sizeto control data flow (typically 10-50) - Store last received cursor to resume after disconnection
- Use
ordering: ASCfor chronological updates
Example Queries
Example 1: Get Atom Details with Vault Info
This example shows how to fetch atom metadata along with vault statistics.
query GetAtomWithVault($atomId: String!, $curveId: numeric!) {
atom(term_id: $atomId) {
term_id
label
image
type
created_at
creator {
id
label
}
term {
vaults(where: { curve_id: { _eq: $curveId } }) {
curve_id
total_shares
total_assets
current_share_price
position_count
}
}
}
}Variables:
{
"atomId": "0x57d94c116a33bb460428eced262b7ae2ec6f865e7aceef6357cec3d034e8ea21",
"curveId": "1"
}Best Practices Used:
- Uses variables for dynamic values
- Requests only needed fields
- Filters vault by curve_id using a variable
Example 2: List Triples with Pagination
Get a paginated list of triples with total count.
query GetTriplesPage($limit: Int!, $offset: Int!, $where: triples_bool_exp) {
total: triples_aggregate(where: $where) {
aggregate {
count
}
}
triples(
where: $where
limit: $limit
offset: $offset
order_by: { created_at: desc }
) {
term_id
created_at
subject {
term_id
label
image
}
predicate {
term_id
label
}
object {
term_id
label
image
}
}
}Variables:
{
"limit": 20,
"offset": 0,
"where": {
"predicate_id": {
"_eq": "0x..."
}
}
}Best Practices Used:
- Combines aggregate count with paginated nodes
- Uses variables for all dynamic values
- Includes ordering for consistent pagination
Example 3: Get User's Positions with Totals
Fetch user positions with aggregate statistics.
query GetUserPositions($accountId: String!, $limit: Int!, $offset: Int!) {
stats: positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
}
}
positions(
where: { account_id: { _eq: $accountId } }
order_by: { shares: desc }
limit: $limit
offset: $offset
) {
id
shares
vault {
term_id
curve_id
current_share_price
total_shares
term {
atom {
term_id
label
image
}
triple {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
}
}
}
}
}Variables:
{
"accountId": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"limit": 10,
"offset": 0
}Best Practices Used:
- Gets aggregate stats alongside paginated results
- Uses relationship traversal to get atom/triple details
- Aliases aggregate query as
statsfor clarity
Example 4: Global Search Across Types
Search for a term across accounts, atoms, and triples.
query GlobalSearch($searchTerm: String!) {
accounts(
where: {
_or: [
{ label: { _ilike: $searchTerm } }
{ atom: { label: { _ilike: $searchTerm } } }
]
}
limit: 5
) {
id
label
image
}
atoms(
where: { label: { _ilike: $searchTerm } }
order_by: { term: { total_market_cap: desc } }
limit: 10
) {
term_id
label
image
type
}
triples(
where: {
_or: [
{ subject: { label: { _ilike: $searchTerm } } }
{ predicate: { label: { _ilike: $searchTerm } } }
{ object: { label: { _ilike: $searchTerm } } }
]
}
limit: 10
) {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
}
}Variables:
{
"searchTerm": "%ethereum%"
}Best Practices Used:
- Uses
_orconditions for multi-field search - Limits results per type to avoid over-fetching
- Uses
_ilikeappropriately for pattern matching
Example 5: Get Vault Statistics
Calculate derived metrics from vault data.
query GetVaultStats($termId: String!, $curveId: numeric!) {
vault(term_id: $termId, curve_id: $curveId) {
term_id
curve_id
total_shares
total_assets
current_share_price
position_count
positions_aggregate {
aggregate {
count
sum {
shares
}
avg {
shares
}
}
}
top_positions: positions(limit: 5, order_by: { shares: desc }) {
account {
id
label
}
shares
}
}
}Variables:
{
"termId": "0x...",
"curveId": "1"
}Best Practices Used:
- Uses aggregates for statistics (count, sum, avg)
- Limits top positions query
- Uses aliases for clarity (
top_positions)
Advanced Examples
The following examples demonstrate more complex use cases and advanced API features.
Example 6: Social Graph - Following Feed
Build a social feed showing positions from accounts a user follows.
query GetFollowingFeed($address: String!, $limit: Int!, $offset: Int!) {
# Get total follower count
following_count: following_aggregate(args: { address: $address }) {
aggregate {
count
}
}
# Get positions from followed accounts
feed: positions_from_following(
args: { address: $address }
limit: $limit
offset: $offset
order_by: { created_at: desc }
) {
id
shares
created_at
account {
id
label
image
}
vault {
term_id
curve_id
current_share_price
total_shares
term {
atom {
term_id
label
image
type
}
triple {
term_id
subject {
label
image
}
predicate {
label
}
object {
label
image
}
}
}
}
}
# Also get signals (deposits/redemptions) from followed accounts
signals: signals_from_following(
args: { address: $address }
limit: $limit
order_by: { created_at: desc }
) {
id
event_type
created_at
account {
id
label
image
}
atom {
term_id
label
}
triple {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
}
}
}Variables:
{
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"limit": 20,
"offset": 0
}Use Case: Build a social activity feed showing what the accounts you follow are investing in, similar to Twitter/X feed but for protocol positions.
Best Practices Used:
- Uses database functions (
positions_from_following,signals_from_following) instead of manual filtering - Combines multiple related queries in one request
- Includes aggregate count for pagination UI
- Handles both atoms and triples in vault term relationship
Example 7: Time-Series Analytics - Price Trends
Analyze price trends over time using pre-computed aggregations.
query GetPriceTrendAnalysis($termId: String!, $curveId: numeric!, $days: Int!) {
# Get daily price changes for the last N days
daily_trends: share_price_change_stats_daily(
where: { term_id: { _eq: $termId }, curve_id: { _eq: $curveId } }
order_by: { bucket: desc }
limit: $days
) {
bucket
first_share_price
last_share_price
difference
change_count
}
# Get hourly data for the last 24 hours for granular view
hourly_trends: share_price_change_stats_hourly(
where: { term_id: { _eq: $termId }, curve_id: { _eq: $curveId } }
order_by: { bucket: desc }
limit: 24
) {
bucket
first_share_price
last_share_price
difference
}
# Get current vault state
current_state: vault(term_id: $termId, curve_id: $curveId) {
term_id
curve_id
current_share_price
total_shares
total_assets
position_count
}
# Get overall signal stats for context
signal_trends: signal_stats_daily(
where: { term_id: { _eq: $termId }, curve_id: { _eq: $curveId } }
order_by: { bucket: desc }
limit: $days
) {
bucket
deposit_count
redemption_count
total_volume
}
}Variables:
{
"termId": "0x57d94c116a33bb460428eced262b7ae2ec6f865e7aceef6357cec3d034e8ea21",
"curveId": "1",
"days": 30
}Use Case: Build analytics dashboards showing price trends, trading volume, and market activity over different time periods.
Best Practices Used:
- Leverages pre-computed time-series tables for performance
- Combines multiple time granularities (daily, hourly) in one query
- Includes current state for comparison
- Uses variables for all dynamic values
Analysis Notes:
- Calculate percentage change:
(last_share_price - first_share_price) / first_share_price * 100 change_countshows number of price changes in that time bucket- Weekly and monthly aggregations available via
share_price_change_stats_weeklyandshare_price_change_stats_monthly
Example 8: Advanced Search - Multi-Criteria Position Search
Perform complex position searches with custom filtering criteria.
query AdvancedPositionSearch(
$addresses: _text!
$searchFields: jsonb!
$limit: Int!
) {
# Search positions with custom criteria
results: search_positions_on_subject(
args: { addresses: $addresses, search_fields: $searchFields }
limit: $limit
) {
id
shares
created_at
account {
id
label
image
}
vault {
term_id
curve_id
current_share_price
total_shares
position_count
term {
atom {
term_id
label
image
type
}
triple {
term_id
subject {
term_id
label
image
}
predicate {
term_id
label
}
object {
term_id
label
image
}
}
}
}
}
# Get count for pagination
results_aggregate: search_positions_on_subject_aggregate(
args: { addresses: $addresses, search_fields: $searchFields }
) {
aggregate {
count
sum {
shares
}
}
}
}Variables:
{
"addresses": "{0xd8da6bf26964af9d7eed9e03e53415d37aa96045,0xabc123...}",
"searchFields": {
"min_shares": "1000000000000000000",
"term_type": "atom",
"atom_type": "Person"
},
"limit": 50
}Use Case: Build advanced search UIs where users can filter positions by:
- Multiple wallet addresses
- Minimum share amounts
- Term type (atom vs triple)
- Atom type (Person, Organization, Thing)
- Other custom criteria in
search_fields
Best Practices Used:
- Uses backend function for complex filtering (more efficient than client-side)
- Includes aggregate variant for totals
- Handles both atom and triple results
- Uses
_texttype for array of addresses
Example 9: Real-Time Updates - Subscription Pattern
Build a live dashboard with real-time position updates.
subscription LivePositionMonitor(
$cursor: [positions_stream_cursor_input]!
$accountId: String
$batchSize: Int!
) {
# Stream position updates
positions_stream(
cursor: $cursor
batch_size: $batchSize
where: { account_id: { _eq: $accountId }, shares: { _gt: "0" } }
) {
id
shares
created_at
account {
id
label
}
vault {
term_id
curve_id
current_share_price
term {
atom {
term_id
label
image
}
triple {
term_id
subject {
label
}
predicate {
label
}
object {
label
}
}
}
}
}
}Variables (Initial):
{
"cursor": [
{
"initial_value": { "created_at": "2024-12-01T00:00:00Z" },
"ordering": "ASC"
}
],
"accountId": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"batchSize": 10
}Variables (Resuming):
{
"cursor": [
{
"initial_value": { "created_at": "2024-12-12T15:30:00Z" },
"ordering": "ASC"
}
],
"accountId": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"batchSize": 10
}Use Case: Create real-time dashboards that update automatically when:
- New positions are created
- Existing positions change
- User performs deposits or redemptions
Implementation Pattern:
- Start subscription with initial cursor (e.g., last hour)
- Process incoming batches of updates
- Update UI state with new data
- If connection drops, resume from last received
created_at
Best Practices Used:
- Filters for active positions (
shares > 0) - Uses batch_size to control data flow
- Cursor enables resumable streams after disconnection
- Ascending order to get updates chronologically
Example 10: Mutation Flow - Pinning Content to IPFS
Complete workflow for creating an atom with metadata pinning.
# Step 1: Pin person metadata to IPFS
mutation PinPersonMetadata($person: PinPersonInput!) {
pinPerson(person: $person) {
uri
}
}
# Step 2: Query to verify atom after on-chain creation
query GetCreatedAtom($termId: String!, $curveId: numeric!) {
atom(term_id: $termId) {
term_id
data
label
image
emoji
type
created_at
creator {
id
label
}
term {
vaults(where: { curve_id: { _eq: $curveId } }) {
term_id
curve_id
total_shares
total_assets
current_share_price
position_count
}
}
}
}Step 1 Variables:
{
"person": {
"name": "Satoshi Nakamoto",
"description": "Creator of Bitcoin",
"identifier": "satoshi",
"image": "ipfs://QmPreviouslyUploadedImage...",
"url": "https://bitcoin.org"
}
}Step 1 Response:
{
"data": {
"pinPerson": {
"uri": "ipfs://bafkreib..."
}
}
}Step 2 Variables (after blockchain transaction):
{
"termId": "0x57d94c116a33bb460428eced262b7ae2ec6f865e7aceef6357cec3d034e8ea21",
"curveId": "1"
}Complete Workflow:
- Prepare metadata - Gather all person/thing/organization data
- Upload image (optional) - Use
uploadImageFromUrloruploadImageif needed - Pin metadata - Use
pinPerson,pinThing, orpinOrganizationon the public gated pinning endpoint - Get IPFS URI - Extract
urifrom mutation response - Create atom on-chain - Call smart contract with IPFS URI (via @0xintuition/protocol)
- Wait for indexing - GraphQL API will index the new atom (usually < 30 seconds)
- Query atom - Fetch complete atom data with vault information
- Cache metadata - Store IPFS URI for future reference
Error Handling Considerations:
- Mutation may fail if image classification fails (inappropriate content)
- IPFS pinning may fail without a valid pinning API key or may timeout - implement retry logic
- Blockchain transaction may fail - check gas and approval
- Atom may not appear immediately - poll or subscribe for updates
Best Practices Used:
- Separates IPFS operations from blockchain operations
- Uses appropriate mutation for entity type
- Queries include vault data for immediate display
- Workflow is idempotent (can retry safely)
Code Generation
Generating the Schema File
The GraphQL schema is not stored in the repository. Generate it via introspection:
# Mainnet
npx get-graphql-schema https://mainnet.intuition.sh/v1/graphql > schema.graphql
# Testnet
npx get-graphql-schema https://testnet.intuition.sh/v1/graphql > schema.graphqlCode Generation Tools
Once you have the schema, use it with your preferred code generation tool:
JavaScript/TypeScript
- GraphQL Code Generator: Generate TypeScript types, React hooks, and more
- Apollo CLI: Generate types for Apollo Client
Python
- Ariadne Codegen: Generate Python types and client code
- sgqlc: Generate Python types from schema
Go
Rust
- graphql-client: Typed GraphQL queries in Rust
- cynic: Type-safe GraphQL client for Rust
Each tool has specific configuration requirements - refer to their official documentation.
Anti-Patterns to Avoid
❌ Anti-Pattern 1: Hardcoding Values in Queries
Problem: Hardcoding values like curve IDs makes queries inflexible and violates DRY principles.
# BAD
fragment VaultDetails on atoms {
term {
vaults(where: { curve_id: { _eq: "1" } }) {
# ❌ Hardcoded!
total_shares
}
}
}Solution: Always use variables for dynamic values.
# GOOD
fragment VaultDetails on atoms {
term {
vaults(where: { curve_id: { _eq: $curveId } }) {
# ✅ Variable
total_shares
}
}
}
query GetAtom($id: String!, $curveId: numeric!) {
atom(term_id: $id) {
...VaultDetails
}
}❌ Anti-Pattern 2: Over-Fetching When Only Aggregates Needed
Problem: Fetching all nodes when you only need counts or sums wastes bandwidth and processing.
# BAD
query GetPositionCount($where: positions_bool_exp) {
positions(where: $where) {
# ❌ Fetches all data
id
shares
account_id
}
}
# Application code then counts the resultsSolution: Use _aggregate queries.
# GOOD
query GetPositionCount($where: positions_bool_exp) {
positions_aggregate(where: $where) {
# ✅ Only returns count
aggregate {
count
sum {
shares
}
}
}
}❌ Anti-Pattern 3: Unnecessary Deep Nesting
Problem: Over-nesting queries fetches data you don't need.
# BAD
query GetAtom($id: String!) {
atom(term_id: $id) {
label
creator {
id
atoms {
# ❌ Fetching all creator's atoms when not needed
term_id
label
as_subject_triples {
# ❌ Even deeper unnecessary nesting
term_id
}
}
}
}
}Solution: Only query what you actually need.
# GOOD
query GetAtom($id: String!) {
atom(term_id: $id) {
label
creator {
id
label
}
}
}❌ Anti-Pattern 4: Not Using Variables for Filters
Problem: Embedding filter values directly makes queries inflexible.
# BAD
query {
atoms(where: { type: { _eq: Person } }) {
# ❌ Hardcoded
term_id
label
}
}Solution: Always use variables.
# GOOD
query GetAtomsByType($type: atom_type!) {
atoms(where: { type: { _eq: $type } }) {
# ✅ Variable
term_id
label
}
}❌ Anti-Pattern 5: Fetching Same Data Multiple Times
Problem: Duplicating field selections across the query.
# BAD
query GetTriple($id: String!) {
triple(term_id: $id) {
subject {
term_id
label
creator {
id
label
} # ❌ Duplicated
}
predicate {
term_id
label
creator {
id
label
} # ❌ Duplicated
}
object {
term_id
label
creator {
id
label
} # ❌ Duplicated
}
}
}Solution: Use fragments for repeated structures.
# GOOD
fragment AtomBasics on atoms {
term_id
label
creator {
id
label
}
}
query GetTriple($id: String!) {
triple(term_id: $id) {
subject {
...AtomBasics
} # ✅ Reusable
predicate {
...AtomBasics
}
object {
...AtomBasics
}
}
}❌ Anti-Pattern 6: Using _ilike for Exact Matches
Problem: Pattern matching operators are slower than exact equality checks.
# BAD
query GetAccount($address: String!) {
accounts(where: { id: { _ilike: $address } }) {
# ❌ Inefficient
id
label
}
}Solution: Use _eq for exact matches or primary key lookups.
# GOOD
query GetAccount($address: String!) {
account(id: $address) {
# ✅ Primary key lookup
id
label
}
}
# Or with _eq
query GetAccounts($address: String!) {
accounts(where: { id: { _eq: $address } }) {
# ✅ Exact match
id
label
}
}❌ Anti-Pattern 7: Not Using Fragments from the Package
Problem: Re-writing common field selections instead of using existing fragments.
This package includes pre-built fragments in src/fragments/:
atom.graphql- Atom metadata, values, transactions, vault detailstriple.graphql- Triple metadata, vault detailsposition.graphql- Position details and aggregatesaccount.graphql- Account metadatavault.graphql- Vault details
Solution: Reference and use these fragments in your queries where appropriate, or create similar reusable fragments in your own codebase.
IMPORTANT NOTE: Many of these anti-patterns exist in packages/graphql/src/queries/*.graphql and need to be refactored. This README serves as the authoritative guide for how queries SHOULD be written.
Resources
Documentation
- GraphQL Official Docs: https://graphql.org/learn/
- Hasura GraphQL Docs: https://hasura.io/docs/latest/queries/postgres/index/
- Intuition Protocol Docs: https://docs.intuition.systems
Interactive Explorers
- Mainnet: Apollo Studio Sandbox
- Testnet: Apollo Studio Sandbox
Related Packages
- @0xintuition/protocol: Low-level smart contract interactions
- @0xintuition/sdk: High-level SDK combining on-chain and off-chain data
Block Explorers
- Mainnet: https://explorer.intuition.systems
- Testnet: https://testnet.explorer.intuition.systems
Repository
- GitHub: https://github.com/0xIntuition/intuition-ts
Contributing
Please see the core CONTRIBUTING.md for guidelines.
License
MIT License - see the repository for details.
