# Jb Bendystraw

> Bendystraw GraphQL API reference for querying Juicebox project data across all chains. Get project stats, payments, token holders, loans, NFT tiers, unified activity feeds, historical snapshots, and cross-chain aggregations.

- Skill: `modbender/jb-bendystraw` (Agent Skill)
- Install (CLI): `npx skillmds@latest add modbender/jb-bendystraw`
- Raw SKILL.md: https://api.skillmd.com/api/skills/modbender/jb-bendystraw/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: modbender (https://skillmd.com/u/modbender)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/modbender/jb-bendystraw

---


# Bendystraw: Cross-Chain Juicebox Data API

Bendystraw is a GraphQL indexer for Juicebox V5 events across all supported chains. It aggregates data and provides unified cross-chain queries for projects, payments, token holders, and NFTs.

## API Base URLs

```
Production: https://bendystraw.xyz/{API_KEY}/graphql
Testnet: https://testnet.bendystraw.xyz/{API_KEY}/graphql
Playground: https://bendystraw.xyz (browser-based GraphQL explorer)
```

## Authentication

**API key required.** Contact [@peripheralist](https://x.com/peripheralist) on Twitter/X to get one.

```javascript
const response = await fetch(`https://bendystraw.xyz/${API_KEY}/graphql`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: '...',
    variables: { ... }
  })
});
```

**Important:** Never expose API keys in frontend code. Use a server-side proxy.

---

## Supported Chains

| Chain | Chain ID | Network |
|-------|----------|---------|
| Ethereum | 1 | Mainnet |
| Optimism | 10 | Mainnet |
| Base | 8453 | Mainnet |
| Arbitrum | 42161 | Mainnet |
| Sepolia | 11155111 | Testnet |

---

## GraphQL Schema Reference

### Project Entity

```graphql
type Project {
  # Identifiers
  id: String!                    # "{chainId}-{projectId}-{version}"
  projectId: Int!
  chainId: Int!
  version: Int!                  # Protocol version (4 or 5)

  # Metadata
  handle: String
  name: String
  description: String
  logoUri: String
  infoUri: String
  owner: String!
  deployer: String

  # Financial
  balance: String!               # Current balance (wei)
  volume: String!                # Total received (wei)
  volumeUsd: String              # USD equivalent
  redeemVolume: String!          # Total redeemed (wei)
  redeemVolumeUsd: String

  # Tokens
  tokenSupply: String!           # Total token supply
  token: String                  # ERC20 address if deployed
  tokenSymbol: String

  # Activity counts
  paymentsCount: Int!
  redeemCount: Int!
  contributorsCount: Int!
  nftsMintedCount: Int!

  # Trending (7-day window)
  trendingScore: Float
  trendingVolume: String
  trendingPaymentsCount: Int

  # Omnichain
  suckerGroupId: String          # Linked cross-chain group

  # Timestamps
  createdAt: Int!
  deployedAt: Int
}
```

### SuckerGroup Entity (Omnichain Projects)

```graphql
type SuckerGroup {
  id: String!                    # Unique group identifier
  projects: [String!]!           # Array of project IDs

  # Aggregated totals across all chains
  volume: String!
  volumeUsd: String
  balance: String!
  tokenSupply: String!
  paymentsCount: Int!
  contributorsCount: Int!

  # Related projects (expanded)
  projects_rel: [Project!]!
}
```

### Participant Entity (Token Holders)

```graphql
type Participant {
  id: String!                    # "{chainId}-{projectId}-{address}"
  address: String!
  projectId: Int!
  chainId: Int!

  # Balances
  balance: String!               # Total balance (credits + ERC20)
  creditBalance: String!         # Unclaimed credits
  erc20Balance: String!          # Claimed ERC20 tokens

  # Activity
  volume: String!                # Total contributed
  volumeUsd: String
  paymentsCount: Int!
  redeemCount: Int!

  # Timestamps
  lastPaidAt: Int
  firstPaidAt: Int
}
```

### PayEvent Entity

```graphql
type PayEvent {
  id: String!
  projectId: Int!
  chainId: Int!
  rulesetId: Int!

  # Transaction
  txHash: String!
  timestamp: Int!
  logIndex: Int!
  blockNumber: Int!

  # Payment details
  from: String!                  # Payer address
  beneficiary: String!           # Token recipient
  amount: String!                # Payment amount (wei)
  amountUsd: String
  distributionFromPayAmount: String!

  # Tokens
  newlyIssuedTokenCount: String!
  beneficiaryTokenCount: String!

  # Metadata
  memo: String
  feeFromPayAmount: String
}
```

### CashOutEvent Entity

```graphql
type CashOutEvent {
  id: String!
  projectId: Int!
  chainId: Int!
  rulesetId: Int!

  # Transaction
  txHash: String!
  timestamp: Int!

  # Redemption details
  holder: String!
  beneficiary: String!
  cashOutCount: String!          # Tokens burned
  reclaimAmount: String!         # ETH received
  reclaimAmountUsd: String
  metadata: String
}
```

### NFT Entity

```graphql
type NFT {
  id: String!
  tokenId: Int!
  projectId: Int!
  chainId: Int!
  hook: String!                  # 721 hook address

  # Tier
  tierId: Int!
  tierCategory: Int

  # Ownership
  owner: String!
  createdAt: Int!

  # Metadata
  tokenUri: String
}
```

### ActivityEvent Entity (Unified Activity Feed)

A polymorphic event type that provides a unified view of all project activity. Query this instead of individual event types when building activity feeds.

```graphql
type ActivityEvent {
  id: String!
  chainId: Int!
  projectId: Int!
  suckerGroupId: String
  version: Int!

  # Transaction
  txHash: String!
  timestamp: Int!
  from: String!

  # Event type discriminator
  type: ActivityEventType!       # Determines which embedded event is populated

  # Embedded events (one will be non-null based on type)
  payEvent: PayEvent
  cashOutTokensEvent: CashOutTokensEvent
  mintNftEvent: MintNftEvent
  sendPayoutsEvent: SendPayoutsEvent
  sendPayoutToSplitEvent: SendPayoutToSplitEvent
  borrowLoanEvent: BorrowLoanEvent
  repayLoanEvent: RepayLoanEvent
  liquidateLoanEvent: LiquidateLoanEvent
  deployErc20Event: DeployErc20Event
  burnEvent: BurnEvent
  mintTokensEvent: MintTokensEvent
  projectCreateEvent: ProjectCreateEvent
  addToBalanceEvent: AddToBalanceEvent
  useAllowanceEvent: UseAllowanceEvent
  decorateBannyEvent: DecorateBannyEvent
  # ... and more

  # Relations
  project: Project
  suckerGroup: SuckerGroup
}

enum ActivityEventType {
  payEvent
  cashOutTokensEvent
  mintNftEvent
  sendPayoutsEvent
  sendPayoutToSplitEvent
  borrowLoanEvent
  repayLoanEvent
  liquidateLoanEvent
  reallocateLoanEvent
  deployErc20Event
  burnEvent
  mintTokensEvent
  manualMintTokensEvent
  manualBurnEvent
  autoIssueEvent
  projectCreateEvent
  addToBalanceEvent
  useAllowanceEvent
  sendReservedTokensToSplitEvent
  sendReservedTokensToSplitsEvent
  decorateBannyEvent
}
```

### Loan Entity (RevLoans)

Active loan state from the RevLoans protocol.

```graphql
type Loan {
  id: BigInt!                    # Loan ID (NFT token ID)
  projectId: Int!
  chainId: Int!
  version: Int!
  createdAt: Int!

  # Loan terms
  borrowAmount: BigInt!          # Amount borrowed (wei)
  collateral: BigInt!            # Collateral locked (project tokens)
  sourceFeeAmount: BigInt!       # Fee amount
  prepaidDuration: Int!          # Prepaid period in seconds
  prepaidFeePercent: Int!        # Fee percent (basis points)

  # Addresses
  beneficiary: String!           # Who receives borrowed funds
  owner: String!                 # Loan NFT owner
  token: String!                 # Collateral token address
  terminal: String!              # Terminal address

  # Metadata
  tokenUri: String               # Loan NFT metadata URI

  # Relations
  project: Project
  participant: Participant
  wallet: Wallet
}
```

### Wallet Entity

Wallet-level aggregation across all project participations.

```graphql
type Wallet {
  address: String!               # Wallet address

  # Aggregated stats
  volume: BigInt!                # Total volume across all projects
  volumeUsd: BigInt!             # USD equivalent (18 decimals)
  lastPaidTimestamp: Int         # Most recent payment timestamp

  # Relations
  participants: ParticipantPage  # All project participations
  nfts: NFTPage                  # All owned NFTs
}
```

### NFTTier Entity

NFT tier configuration with pricing and supply.

```graphql
type NFTTier {
  chainId: Int!
  projectId: Int!
  version: Int!
  tierId: Int!

  # Pricing
  price: BigInt!                 # Price in terminal token (wei)

  # Supply
  initialSupply: Int!            # Original supply
  remainingSupply: Int!          # Current available

  # Configuration
  allowOwnerMint: Boolean!       # Owner can mint without payment
  cannotBeRemoved: Boolean!      # Tier is permanent
  transfersPausable: Boolean!    # Transfers can be paused
  votingUnits: BigInt!           # Governance weight per NFT
  category: Int                  # Tier category
  reserveFrequency: Int          # Reserve rate
  reserveBeneficiary: String     # Reserve recipient

  # Metadata
  encodedIpfsUri: String         # IPFS hash (encoded)
  resolvedUri: String            # Full resolved URI
  metadata: JSON                 # Parsed metadata
  svg: String                    # SVG content if available

  createdAt: Int!

  # Relations
  hook: NFTHook
  nfts: NFTPage                  # Minted NFTs in this tier
  project: Project
}
```

### NFTHook Entity

721 hook contract configuration.

```graphql
type NFTHook {
  chainId: Int!
  projectId: Int!
  version: Int!
  createdAt: Int!

  address: String!               # Hook contract address
  name: String                   # Collection name
  symbol: String                 # Collection symbol

  # Relations
  nfts: NFTPage
  nftTiers: NFTTierPage
  project: Project
}
```

### ProjectMoment Entity (Historical Snapshots)

Point-in-time snapshots of project state. Useful for historical charts and analytics.

```graphql
type ProjectMoment {
  projectId: Int!
  chainId: Int!
  version: Int!

  # Snapshot point
  block: Int!                    # Block number
  timestamp: Int!                # Unix timestamp

  # State at snapshot
  volume: BigInt!
  volumeUsd: BigInt!
  balance: BigInt!
  trendingScore: BigInt!

  # Relations
  project: Project
}
```

### SuckerGroupMoment Entity

Point-in-time snapshots of cross-chain aggregated state.

```graphql
type SuckerGroupMoment {
  suckerGroupId: String!

  # Snapshot point
  block: Int!
  timestamp: Int!

  # Aggregated state
  volume: BigInt!
  volumeUsd: BigInt!
  balance: BigInt!
  tokenSupply: BigInt!

  # Relations
  suckerGroup: SuckerGroup
}
```

### SuckerTransaction Entity (Cross-Chain Bridging)

Token bridging transactions between chains via suckers.

```graphql
type SuckerTransaction {
  index: Int!                    # Transaction index
  projectId: Int!
  chainId: Int!                  # Source chain
  version: Int!
  suckerGroupId: String!
  createdAt: Int!

  # Bridge details
  token: String!                 # Token being bridged
  sucker: String!                # Source sucker address
  peer: String!                  # Destination sucker address
  peerChainId: Int!              # Destination chain
  beneficiary: String!           # Recipient address

  # Amounts
  projectTokenCount: BigInt!     # Project tokens bridged
  terminalTokenAmount: BigInt!   # Terminal tokens (if any)

  # State
  root: String                   # Merkle root
  status: SuckerTransactionStatus!

  # Relations
  suckerGroup: SuckerGroup
}

enum SuckerTransactionStatus {
  pending
  completed
  failed
}
```

### SendPayoutsEvent Entity

Payout distribution events from the terminal.

```graphql
type SendPayoutsEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  logIndex: Int!

  # Payout details
  caller: String!                # Who triggered payout
  from: String!                  # Source address
  rulesetId: Int!
  rulesetCycleNumber: Int!

  # Amounts
  amount: BigInt!                # Total payout amount
  amountUsd: BigInt!
  amountPaidOut: BigInt!         # Actually distributed
  amountPaidOutUsd: BigInt!
  netLeftoverPayoutAmount: BigInt!  # Remaining after splits
  fee: BigInt!                   # Protocol fee
  feeUsd: BigInt!

  # Relations
  project: Project
}
```

### UseAllowanceEvent Entity

Surplus allowance usage events.

```graphql
type UseAllowanceEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!

  # Transaction
  txHash: String!
  timestamp: Int!

  # Allowance details
  caller: String!
  beneficiary: String!           # Who receives funds
  rulesetId: Int!
  rulesetCycleNumber: Int!

  # Amounts
  amount: BigInt!                # Amount used
  amountUsd: BigInt!
  netAmount: BigInt!             # After fees
  netAmountUsd: BigInt!

  # Relations
  project: Project
}
```

### PermissionHolder Entity

Operator permissions granted to accounts.

```graphql
type PermissionHolder {
  chainId: Int!
  projectId: Int!
  version: Int!

  account: String!               # Account with permissions
  operator: String!              # Operator address
  permissions: [Int!]!           # Permission IDs granted
  isRevnetOperator: Boolean!     # Is a revnet operator

  # Relations
  project: Project
}
```

### BorrowLoanEvent Entity

Loan creation events from RevLoans.

```graphql
type BorrowLoanEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Loan details
  borrowAmount: BigInt!          # Amount borrowed
  collateral: BigInt!            # Collateral locked
  sourceFeeAmount: BigInt!       # Fee paid
  prepaidDuration: Int!          # Prepaid period (seconds)
  prepaidFeePercent: Int!        # Fee percent (basis points)
  beneficiary: String!           # Loan recipient
  token: String!                 # Collateral token
  terminal: String!              # Terminal address

  # Relations
  project: Project
}
```

### RepayLoanEvent Entity

Loan repayment events.

```graphql
type RepayLoanEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Repayment details
  loanId: BigInt!                # Loan being repaid
  paidOffLoanId: BigInt          # If fully paid off
  repayBorrowAmount: BigInt!     # Amount repaid
  collateralCountToReturn: BigInt!  # Collateral returned

  # Relations
  project: Project
}
```

### LiquidateLoanEvent Entity

Loan liquidation events.

```graphql
type LiquidateLoanEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Liquidation details
  borrowAmount: BigInt!          # Outstanding borrow
  collateral: BigInt!            # Collateral seized

  # Relations
  project: Project
}
```

### ReallocateLoanEvent Entity

Loan reallocation events (moving collateral between loans).

```graphql
type ReallocateLoanEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Reallocation details
  loanId: BigInt!                # Source loan
  reallocatedLoanId: BigInt!     # Target loan
  removedCollateralCount: BigInt!  # Collateral moved

  # Relations
  project: Project
}
```

### BurnEvent Entity

Token burn events (from cash outs).

```graphql
type BurnEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  from: String!

  # Burn details
  amount: BigInt!                # Total burned
  creditAmount: BigInt!          # Credits burned
  erc20Amount: BigInt!           # ERC20 burned

  # Relations
  project: Project
}
```

### MintTokensEvent Entity

Token minting events (from payments).

```graphql
type MintTokensEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Mint details
  beneficiary: String!           # Token recipient
  beneficiaryTokenCount: BigInt! # Tokens to beneficiary
  reservedPercent: BigInt!       # Reserved rate
  tokenCount: BigInt!            # Total minted
  memo: String                   # Payment memo

  # Relations
  project: Project
}
```

### ManualMintTokensEvent Entity

Manual token minting by project owner.

```graphql
type ManualMintTokensEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Mint details
  beneficiary: String!
  beneficiaryTokenCount: BigInt!
  reservedPercent: BigInt!
  tokenCount: BigInt!
  memo: String

  # Relations
  project: Project
}
```

### ManualBurnEvent Entity

Manual token burning.

```graphql
type ManualBurnEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  from: String!

  # Burn details
  amount: BigInt!
  creditAmount: BigInt!
  erc20Amount: BigInt!

  # Relations
  project: Project
}
```

### MintNftEvent Entity

NFT minting events.

```graphql
type MintNftEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Mint details
  hook: String!                  # 721 hook address
  beneficiary: String!           # NFT recipient
  tierId: Int!                   # Tier minted
  tokenId: BigInt!               # Token ID
  totalAmountPaid: BigInt!       # Amount paid

  # Relations
  project: Project
  tier: NFTTier
  nft: NFT
}
```

### DeployErc20Event Entity

ERC20 token deployment events.

```graphql
type DeployErc20Event {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Token details
  symbol: String!                # Token symbol
  name: String!                  # Token name
  token: String!                 # Token address

  # Relations
  project: Project
}
```

### ProjectCreateEvent Entity

Project creation events.

```graphql
type ProjectCreateEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Relations
  project: Project
}
```

### AddToBalanceEvent Entity

Direct balance addition events.

```graphql
type AddToBalanceEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Details
  amount: BigInt!                # Amount added
  memo: String                   # Memo
  metadata: String               # Additional metadata
  returnedFees: BigInt           # Fees returned

  # Relations
  project: Project
}
```

### SendPayoutToSplitEvent Entity

Individual split payout events.

```graphql
type SendPayoutToSplitEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Split details
  rulesetId: Int!
  group: BigInt!                 # Split group
  beneficiary: String!           # Split recipient
  splitProjectId: Int            # If split to project
  hook: String                   # Split hook if any

  # Amounts
  amount: BigInt!                # Gross amount
  netAmount: BigInt!             # After fees
  amountUsd: BigInt!
  percent: Int!                  # Split percent
  lockedUntil: BigInt            # Lock timestamp
  preferAddToBalance: Boolean!   # Add to balance vs pay

  # Relations
  project: Project
}
```

### SendReservedTokensToSplitEvent Entity

Reserved token distribution to individual split.

```graphql
type SendReservedTokensToSplitEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Split details
  rulesetId: Int!
  groupId: BigInt!
  beneficiary: String!
  splitProjectId: Int
  hook: String
  tokenCount: BigInt!            # Tokens sent
  percent: Int!
  lockedUntil: BigInt
  preferAddToBalance: Boolean!

  # Relations
  project: Project
}
```

### SendReservedTokensToSplitsEvent Entity

Batch reserved token distribution event.

```graphql
type SendReservedTokensToSplitsEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!
  suckerGroupId: String

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Distribution details
  rulesetId: Int!
  rulesetCycleNumber: Int!
  owner: String!                 # Project owner
  tokenCount: BigInt!            # Total distributed
  leftoverAmount: BigInt!        # Remaining after splits

  # Relations
  project: Project
}
```

### AutoIssueEvent Entity

Auto-issuance events (revnet stage transitions).

```graphql
type AutoIssueEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Issuance details
  stageId: BigInt!               # Revnet stage
  beneficiary: String!           # Token recipient
  count: BigInt!                 # Tokens issued

  # Relations
  project: Project
}
```

### StoreAutoIssuanceAmountEvent Entity

Auto-issuance configuration events.

```graphql
type StoreAutoIssuanceAmountEvent {
  id: String!
  chainId: Int!
  version: Int!
  projectId: Int!

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Config details
  stageId: BigInt!               # Revnet stage
  beneficiary: String!           # Configured recipient
  count: BigInt!                 # Amount to auto-issue

  # Relations
  project: Project
}
```

### DecorateBannyEvent Entity

Banny NFT decoration events.

```graphql
type DecorateBannyEvent {
  id: String!
  chainId: Int!
  version: Int!

  # Transaction
  txHash: String!
  timestamp: Int!
  caller: String!
  from: String!
  logIndex: Int!

  # Decoration details
  bannyBodyId: BigInt!           # Banny being decorated
  outfitIds: [BigInt!]!          # Outfit NFT IDs
  backgroundId: BigInt           # Background NFT ID
  tokenUri: String               # Updated token URI
  tokenUriMetadata: JSON         # Parsed metadata

  # Relations
  bannyNft: NFT
}
```

### CashOutTaxSnapshot Entity

Historical cash-out tax rate snapshots.

```graphql
type CashOutTaxSnapshot {
  chainId: Int!
  projectId: Int!
  suckerGroupId: String
  version: Int!

  # Snapshot period
  start: BigInt!                 # Period start timestamp
  duration: BigInt!              # Period duration
  rulesetId: BigInt!             # Ruleset ID

  # Tax rate
  cashOutTax: Int!               # Tax rate (basis points)
}
```

### ParticipantSnapshot Entity (GraphQL)

Historical participant balance snapshots via GraphQL (alternative to REST endpoint).

```graphql
type ParticipantSnapshot {
  chainId: Int!
  projectId: Int!
  suckerGroupId: String
  version: Int!

  # Snapshot point
  block: Int!
  timestamp: Int!

  # Participant
  address: String!

  # Balances at snapshot
  balance: BigInt!
  creditBalance: BigInt!
  erc20Balance: BigInt!
  volume: BigInt!
  volumeUsd: BigInt!
}
```

---

## Critical Concepts

### Project Identity

**A Juicebox project is uniquely identified by three fields: `projectId + chainId + version`.**

This is crucial because:
- **V4 and V5 are completely different protocols.** Project #64 on Ethereum V4 is NOT the same project as Project #64 on Ethereum V5.
- The same projectId can exist on multiple chains (via suckers/omnichain), but those ARE the same project.
- Always include `version` when querying or displaying projects.

```javascript
// WRONG: Groups V4 and V5 together
const groupKey = `${project.projectId}-${project.chainId}`;

// CORRECT: Keeps V4 and V5 separate
const groupKey = `${project.projectId}-v${project.version}`;
```

### Multi-Chain Grouping

When displaying "top projects" or aggregating stats:
- **Same projectId + version across chains** → Group together (same project via suckers)
- **Same projectId, different version** → Keep separate (completely different projects)

```javascript
// Group projects by projectId + version (V4 and V5 are different projects!)
const grouped = new Map();

for (const project of projects) {
  const groupKey = `${project.projectId}-v${project.version || 4}`;
  const existing = grouped.get(groupKey);

  if (existing) {
    // Add chain to existing group
    if (!existing.chainIds.includes(project.chainId)) {
      existing.chainIds.push(project.chainId);
    }
    // Sum volumes
    existing.totalVolumeUsd += parseFloat(project.volumeUsd || '0');
  } else {
    grouped.set(groupKey, {
      ...project,
      chainIds: [project.chainId],
      totalVolumeUsd: parseFloat(project.volumeUsd || '0')
    });
  }
}
```

### USD Value Formatting

The `volumeUsd`, `amountUsd`, and similar fields use **18 decimal format** (like wei). You must convert properly:

```javascript
function formatVolumeUsd(volumeUsd) {
  if (!volumeUsd || volumeUsd === '0') return '$0';

  try {
    // volumeUsd comes in 18 decimal format
    // Use BigInt to avoid precision loss on large numbers
    const raw = BigInt(volumeUsd.split('.')[0]);
    const usd = Number(raw / BigInt(1e12)) / 1e6; // Divide in steps

    if (usd >= 1_000_000) return `$${(usd / 1_000_000).toFixed(1)}M`;
    if (usd >= 1_000) return `$${(usd / 1_000).toFixed(1)}k`;
    if (usd >= 1) return `$${usd.toFixed(0)}`;
    return `$${usd.toFixed(2)}`;
  } catch {
    return '$0';
  }
}
```

**Warning:** Do NOT use `parseFloat()` directly on volumeUsd for large values—JavaScript loses precision beyond ~15 digits.

### Filtering by Version

When querying projects, filter by version to avoid mixing V4 and V5 data:

```graphql
# Get only V5 projects
query V5Projects($limit: Int!) {
  projects(
    where: { version: 5 }
    orderBy: "volumeUsd"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      projectId
      chainId
      version
      name
      volumeUsd
    }
  }
}
```

To display both versions, query them separately and handle grouping in your application.

---

## Query Examples

### Get Single Project

```graphql
query GetProject($projectId: Int!, $chainId: Int!) {
  project(projectId: $projectId, chainId: $chainId) {
    id
    name
    handle
    owner
    balance
    volume
    volumeUsd
    tokenSupply
    paymentsCount
    contributorsCount
    suckerGroupId
  }
}
```

### Get Participant (Token Holder)

```graphql
query GetParticipant($projectId: Int!, $chainId: Int!, $address: String!) {
  participant(projectId: $projectId, chainId: $chainId, address: $address) {
    balance
    creditBalance
    erc20Balance
    volume
    volumeUsd
    paymentsCount
  }
}
```

### Get Sucker Group (Omnichain Totals)

```graphql
query GetSuckerGroup($id: String!) {
  suckerGroup(id: $id) {
    id
    volume
    volumeUsd
    balance
    tokenSupply
    paymentsCount
    contributorsCount
    projects_rel {
      projectId
      chainId
      name
      balance
      volume
    }
  }
}
```

### List Projects

```graphql
query ListProjects($chainId: Int, $version: Int, $limit: Int!, $offset: Int!) {
  projects(
    where: { chainId: $chainId, version: $version }
    orderBy: "volumeUsd"
    orderDirection: "desc"
    limit: $limit
    offset: $offset
  ) {
    items {
      projectId
      chainId
      version
      name
      handle
      volumeUsd
      balance
      paymentsCount
    }
    totalCount
  }
}
```

### List Recent Payments

```graphql
query ListPayments($projectId: Int!, $chainId: Int!, $limit: Int!) {
  payEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      from
      beneficiary
      amount
      amountUsd
      memo
      newlyIssuedTokenCount
    }
  }
}
```

### List Top Token Holders

```graphql
query ListParticipants($projectId: Int!, $chainId: Int!, $limit: Int!) {
  participants(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "balance"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      address
      balance
      creditBalance
      erc20Balance
      volume
      paymentsCount
    }
    totalCount
  }
}
```

### Get Trending Projects

```graphql
query TrendingProjects($limit: Int!) {
  projects(
    orderBy: "trendingScore"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      projectId
      chainId
      name
      handle
      trendingScore
      trendingVolume
      trendingPaymentsCount
    }
  }
}
```

### List Cash Out Events

```graphql
query ListCashOuts($projectId: Int!, $chainId: Int!, $limit: Int!) {
  cashOutEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      holder
      beneficiary
      cashOutCount
      reclaimAmount
      reclaimAmountUsd
    }
  }
}
```

### List NFTs for Project

```graphql
query ListNFTs($projectId: Int!, $chainId: Int!, $limit: Int!) {
  nfts(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "createdAt"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      tokenId
      tierId
      tierCategory
      owner
      createdAt
      tokenUri
    }
  }
}
```

### Get Unified Activity Feed

The most powerful query for building activity feeds. Returns all event types in a single query.

```graphql
query GetActivityFeed($projectId: Int!, $chainId: Int!, $limit: Int!) {
  activityEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      id
      timestamp
      txHash
      from
      type                        # Discriminator for event type

      # Include fields from each possible event type
      payEvent {
        amount
        amountUsd
        beneficiary
        memo
      }
      cashOutTokensEvent {
        cashOutCount
        reclaimAmount
        holder
      }
      mintNftEvent {
        tierId
        tokenId
      }
      sendPayoutsEvent {
        amount
        amountPaidOut
        fee
      }
      borrowLoanEvent {
        borrowAmount
        collateral
      }
    }
  }
}
```

### Get Omnichain Activity Feed

Query activity across all chains for a sucker group.

```graphql
query GetOmnichainActivity($suckerGroupId: String!, $limit: Int!) {
  activityEvents(
    where: { suckerGroupId: $suckerGroupId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      chainId
      timestamp
      type
      txHash
      from
      payEvent { amount, memo }
      cashOutTokensEvent { cashOutCount, reclaimAmount }
    }
  }
}
```

### List Active Loans

```graphql
query ListLoans($projectId: Int!, $chainId: Int!, $limit: Int!) {
  loans(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "createdAt"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      id
      borrowAmount
      collateral
      prepaidDuration
      prepaidFeePercent
      owner
      beneficiary
      createdAt
    }
    totalCount
  }
}
```

### Get Loan by ID

```graphql
query GetLoan($id: BigInt!) {
  loan(id: $id) {
    id
    projectId
    chainId
    borrowAmount
    collateral
    sourceFeeAmount
    prepaidDuration
    prepaidFeePercent
    beneficiary
    owner
    token
    terminal
    tokenUri
    createdAt
  }
}
```

### Get Wallet Portfolio

```graphql
query GetWallet($address: String!) {
  wallet(address: $address) {
    address
    volume
    volumeUsd
    lastPaidTimestamp
    participants(limit: 100) {
      items {
        projectId
        chainId
        balance
        volume
      }
    }
    nfts(limit: 50) {
      items {
        projectId
        chainId
        tokenId
        tierId
      }
    }
  }
}
```

### List NFT Tiers

```graphql
query ListNFTTiers($projectId: Int!, $chainId: Int!, $limit: Int!) {
  nftTiers(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "tierId"
    orderDirection: "asc"
    limit: $limit
  ) {
    items {
      tierId
      price
      initialSupply
      remainingSupply
      category
      votingUnits
      resolvedUri
      metadata
      svg
    }
  }
}
```

### Get NFT Hook Details

```graphql
query GetNFTHook($projectId: Int!, $chainId: Int!) {
  nftHooks(
    where: { projectId: $projectId, chainId: $chainId }
    limit: 1
  ) {
    items {
      address
      name
      symbol
      nftTiers(limit: 100) {
        items {
          tierId
          price
          remainingSupply
        }
      }
    }
  }
}
```

### Get Historical Project Snapshots

```graphql
query GetProjectHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {
  projectMoments(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      block
      timestamp
      volume
      volumeUsd
      balance
      trendingScore
    }
  }
}
```

### Get Cross-Chain Bridge Transactions

```graphql
query GetSuckerTransactions($suckerGroupId: String!, $limit: Int!) {
  suckerTransactions(
    where: { suckerGroupId: $suckerGroupId }
    orderBy: "createdAt"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      index
      chainId
      peerChainId
      beneficiary
      projectTokenCount
      terminalTokenAmount
      status
      createdAt
    }
  }
}
```

### List Payout Events

```graphql
query ListPayouts($projectId: Int!, $chainId: Int!, $limit: Int!) {
  sendPayoutsEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      caller
      rulesetCycleNumber
      amount
      amountPaidOut
      fee
      netLeftoverPayoutAmount
    }
  }
}
```

### List Allowance Usage

```graphql
query ListAllowanceUsage($projectId: Int!, $chainId: Int!, $limit: Int!) {
  useAllowanceEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      caller
      beneficiary
      amount
      netAmount
      rulesetCycleNumber
    }
  }
}
```

### List Loan Events (Borrow/Repay/Liquidate)

```graphql
query ListBorrowEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {
  borrowLoanEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      borrowAmount
      collateral
      prepaidDuration
      beneficiary
    }
  }
}

query ListRepayEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {
  repayLoanEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      loanId
      repayBorrowAmount
      collateralCountToReturn
    }
  }
}

query ListLiquidations($projectId: Int!, $chainId: Int!, $limit: Int!) {
  liquidateLoanEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      borrowAmount
      collateral
    }
  }
}
```

### List Token Burns

```graphql
query ListBurns($projectId: Int!, $chainId: Int!, $limit: Int!) {
  burnEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      from
      amount
      creditAmount
      erc20Amount
    }
  }
}
```

### List NFT Mints

```graphql
query ListNFTMints($projectId: Int!, $chainId: Int!, $limit: Int!) {
  mintNftEvents(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      beneficiary
      tierId
      tokenId
      totalAmountPaid
    }
  }
}
```

### Get Permission Holders

```graphql
query ListPermissionHolders($projectId: Int!, $chainId: Int!) {
  permissionHolders(
    where: { projectId: $projectId, chainId: $chainId }
    limit: 100
  ) {
    items {
      account
      operator
      permissions
      isRevnetOperator
    }
  }
}
```

### List Project Creations

```graphql
query ListProjectCreations($chainId: Int!, $limit: Int!) {
  projectCreateEvents(
    where: { chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      projectId
      caller
      project {
        name
        handle
        owner
      }
    }
  }
}
```

### List ERC20 Deployments

```graphql
query ListERC20Deployments($chainId: Int!, $limit: Int!) {
  deployErc20Events(
    where: { chainId: $chainId }
    orderBy: "timestamp"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      timestamp
      txHash
      projectId
      name
      symbol
      token
    }
  }
}
```

### Get Cash Out Tax History

```graphql
query GetCashOutTaxHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {
  cashOutTaxSnapshots(
    where: { projectId: $projectId, chainId: $chainId }
    orderBy: "start"
    orderDirection: "desc"
    limit: $limit
  ) {
    items {
      start
      duration
      rulesetId
      cashOutTax
    }
  }
}
```

### Get Participant History (GraphQL Snapshots)

```graphql
query GetParticipantHistory(
  $projectId: Int!,
  $chainId: Int!,
  $address: String!,
  $limit: Int!
) 

…(truncated)
