Observed since 21 Oct, 08:57 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Intuition 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.
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.
No authentication required:
https://mainnet.intuition.sh/v1/graphqlhttps://testnet.intuition.sh/v1/graphqlPinning mutations use a separate public gated endpoint:
https://pin.intuition.systems/v1/graphqlGenerated 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.
Explore the API interactively with Apollo Studio Sandbox:
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 are statements that connect atoms in subject-predicate-object relationships. For example: (Alice, knows, Bob) or (Document, hasTag, TypeScript).
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 represent user ownership (shares) in vaults. Each position tracks an account's shares in a specific vault.
Accounts are blockchain addresses participating in the protocol, including:
Deposits are transactions where users add assets to vaults and receive shares. Redemptions are the reverse: users burn shares to withdraw assets.
Events capture the complete on-chain event history, including deposits, redemptions, atom creation, triple creation, and more.
Stats provide protocol-wide statistics and aggregated metrics.
The Intuition GraphQL API works with any GraphQL client. Below are minimal examples for popular clients across different languages.
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'
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...' })
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...' },
})
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()
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()
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...'})
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
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
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?;
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.graphql
The Hasura-powered GraphQL schema provides:
where ClausesUse 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 logicorder_byquery GetTopAtoms {
atoms(
order_by: [{ term: { total_market_cap: desc } }, { created_at: desc }]
limit: 10
) {
term_id
label
}
}
query GetAtomsPage($limit: Int!, $offset: Int!) {
atoms(limit: $limit, offset: $offset, order_by: { created_at: desc }) {
term_id
label
}
atoms_aggregate {
aggregate {
count
}
}
}
query GetPositionStats($accountId: String!) {
positions_aggregate(where: { account_id: { _eq: $accountId } }) {
aggregate {
count
sum {
shares
}
}
}
}
GraphQL relationships allow nested queries:
query GetAtomWithCreator($id: String!) {
atom(term_id: $id) {
term_id
label
creator {
id
label
image
}
}
}
query GetAtom($id: String!) {
atom(term_id: $id) {
# Direct lookup by primary key
term_id
label
}
}
query GetAtom($id: String!) {
atom(term_id: $id) {
term_id
data
label
image
type
created_at
creator {
id
label
}
}
}
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
}
}
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
}
}
}
}
query GetTriple($id: String!) {
triple(term_id: $id) {
term_id
subject {
term_id
label
image
}
predicate {
term_id
label
}
object {
term_id
label
image
}
}
}
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
}
}
}
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
}
}
}
}
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
}
}
}
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.
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
}
}
}
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
}
}
}
query SemanticSearch($query: String!, $limit: Int) {
search_term(args: { query: $query }, limit: $limit) {
atom {
term_id
label
type
}
}
}
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.
The API provides backend functions for complex queries that would be inefficient to perform client-side.
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
}
}
}
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"
}
}
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
}
}
}
The API includes pre-computed time-series aggregations for efficient analytics.
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 aggregationsQuery 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
}
}
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.
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 sizeThe 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.
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 a Person entity to IPFS:
mutation PinPerson($person: PinPersonInput!) {
pinPerson(person: $person) {
uri
}
}
Variables:
{
"person": {
"name": "Vitalik Buterin",
"description": "Co-founder of Ethereum",
"email": "vitalik@ethereum.org",
"identifier": "vitalik.eth",
"image": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"url": "https://vitalik.ca"
}
}
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": "info@ethereum.org",
"image": "ipfs://QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy",
"url": "https://ethereum.foundation"
}
}
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"
}
}
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 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"
}
}
Typical workflow for creating an atom with pinned metadata:
pinThing, pinPerson, or pinOrganization on the public gated pinning endpointThe GraphQL API supports real-time subscriptions for live data updates using cursor-based streaming. All queryable entities support corresponding _stream subscriptions.
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
}
Subscriptions use cursors to enable resumable streams:
initial_value: Starting point for the stream (e.g., timestamp, ID)ordering: Sort direction (ASC or DESC)batch_size: Number of items per batchsubscription 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.
subscription WatchNewTriples($cursor: [triples_stream_cursor_input]!) {
triples_stream(cursor: $cursor, batch_size: 5) {
term_id
created_at
subject {
label
}
predicate {
label
}
object {
label
}
}
}
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
}
}
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
}
}
Use subscriptions when:
Use polling when:
❌ 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
}
}
❌ 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
}
}
}
✅ 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
}
}
}
❌ 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
}
}
}
❌ 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
}
}
✅ Efficient filtering:
query GetRecentPersonAtoms($since: timestamptz!) {
atoms(
where: { type: { _eq: Person }, created_at: { _gte: $since } }
limit: 100
) {
term_id
label
}
}
❌ 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
}
}
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
}
}
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:
Available time-series tables:
share_price_change_stats_daily, _hourly, _weekly, _monthlysignal_stats_daily, _hourly, _monthlyLeverage 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 accountsBenefits:
Use subscriptions for real-time features, polling for everything else:
✅ Use subscriptions when:
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:
Example polling pattern:
query GetStats {
stats {
total_accounts
total_atoms
total_triples
}
}
# Poll every 30 seconds or on user action
Subscription cursor management:
initial_value to start from a specific pointbatch_size to control data flow (typically 10-50)ordering: ASC for chronological updatesThis 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:
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:
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:
stats for claritySearch 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:
_or conditions for multi-field search_ilike appropriately for pattern matchingCalculate 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:
top_positions)The following examples demonstrate more complex use cases and advanced API features.
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:
positions_from_following, signals_from_following) instead of manual filteringAnalyze 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:
Analysis Notes:
(last_share_price - first_share_price) / first_share_price * 100change_count shows number of price changes in that time bucketshare_price_change_stats_weekly and share_price_change_stats_monthlyPerform 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:
search_fieldsBest Practices Used:
_text type for array of addressesBuild 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:
Implementation Pattern:
created_atBest Practices Used:
shares > 0)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:
uploadImageFromUrl or uploadImage if neededpinPerson, pinThing, or pinOrganization on the public gated pinning endpointuri from mutation responseError Handling Considerations:
Best Practices Used:
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.graphql
Once you have the schema, use it with your preferred code generation tool:
Each tool has specific configuration requirements - refer to their official documentation.
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
}
}
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 results
Solution: Use _aggregate queries.
# GOOD
query GetPositionCount($where: positions_bool_exp) {
positions_aggregate(where: $where) {
# ✅ Only returns count
aggregate {
count
sum {
shares
}
}
}
}
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
}
}
}
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
}
}
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
}
}
}
_ilike for Exact MatchesProblem: 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
}
}
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 detailsSolution: 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.
Please see the core CONTRIBUTING.md for guidelines.
MIT License - see the repository for details.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 3.0.1Registry latest | 30 Jun 2026 | 21 | |
| 3.0.0 | 25 Jun 2026 | 21 | |
| 2.0.2 | 8 Dec 2025 | 21 | |
| 2.0.1 | 8 Dec 2025 | 21 | |
| 2.0.0 | 8 Dec 2025 | 21 | |
| 2.0.0-alpha.4 | 10 Nov 2025 | 21 | |
| 2.0.0-alpha.3 | 30 Oct 2025 | 21 | |
| 2.0.0-alpha.2 | 17 Sept 2025 | 21 | |
| 2.0.0-alpha.0 | 11 Sept 2025 | 21 | |
| 1.0.0-alpha.1 | 12 Aug 2025 | 21 | |
| 1.0.0-alpha.0 | 25 Jun 2025 | 21 | |
| 0.8.0 | 1 Apr 2025 | 21 | |
| 0.7.0 | 28 Mar 2025 | 21 | |
| 0.6.0 | 17 Feb 2025 | 21 | |
| 0.5.0 | 9 Jan 2025 | 21 | |
| 0.4.0 | 2 Jan 2025 | 21 | |
| 0.3.0 | 27 Dec 2024 | 22 | |
| 0.2.0 | 5 Dec 2024 | 22 | |
| 0.0.5 | 27 Nov 2024 | 22 | |
| 0.0.4 | 27 Nov 2024 | 22 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 08:57 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:cd6bed078dfe5dde31b07b85291ea9e700433d6818c9b32cf444e77311daebd9