[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FXEVZYJDMH1GSP7NTYXRQE":3,"skill-same:01M6FXEVZYJDMH1GSP7NTYXRQE":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":36,"linkOnly":37,"markdownContent":38,"markdownBody":39,"frontMatter":40,"refName":41,"commitSha":42,"skillDirectory":43,"forksCount":44,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":47,"install":48,"resources":61},"01M6FXEVZYJDMH1GSP7NTYXRQE","jb-bendystraw",null,"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.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fmejango\u002Fjuicy\u002Fjb-bendystraw\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"jb bendystraw","reference","activity","chain","token","get","project","nft","across","loans","snapshots","cross-chain","api","GraphQL","feeds","data","payments","stats","2026-11-10 10:12:30",false,"---\nname: jb-bendystraw\ndescription: 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.\n---\n\n# Bendystraw: Cross-Chain Juicebox Data API\n\nBendystraw 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.\n\n## API Base URLs\n\n```\nProduction: https:\u002F\u002Fbendystraw.xyz\u002F{API_KEY}\u002Fgraphql\nTestnet: https:\u002F\u002Ftestnet.bendystraw.xyz\u002F{API_KEY}\u002Fgraphql\nPlayground: https:\u002F\u002Fbendystraw.xyz (browser-based GraphQL explorer)\n```\n\n## Authentication\n\n**API key required.** Contact [@peripheralist](https:\u002F\u002Fx.com\u002Fperipheralist) on Twitter\u002FX to get one.\n\n```javascript\nconst response = await fetch(`https:\u002F\u002Fbendystraw.xyz\u002F${API_KEY}\u002Fgraphql`, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application\u002Fjson' },\n  body: JSON.stringify({\n    query: '...',\n    variables: { ... }\n  })\n});\n```\n\n**Important:** Never expose API keys in frontend code. Use a server-side proxy.\n\n---\n\n## Supported Chains\n\n| Chain | Chain ID | Network |\n|-------|----------|---------|\n| Ethereum | 1 | Mainnet |\n| Optimism | 10 | Mainnet |\n| Base | 8453 | Mainnet |\n| Arbitrum | 42161 | Mainnet |\n| Sepolia | 11155111 | Testnet |\n\n---\n\n## GraphQL Schema Reference\n\n### Project Entity\n\n```graphql\ntype Project {\n  # Identifiers\n  id: String!                    # \"{chainId}-{projectId}-{version}\"\n  projectId: Int!\n  chainId: Int!\n  version: Int!                  # Protocol version (4 or 5)\n\n  # Metadata\n  handle: String\n  name: String\n  description: String\n  logoUri: String\n  infoUri: String\n  owner: String!\n  deployer: String\n\n  # Financial\n  balance: String!               # Current balance (wei)\n  volume: String!                # Total received (wei)\n  volumeUsd: String              # USD equivalent\n  redeemVolume: String!          # Total redeemed (wei)\n  redeemVolumeUsd: String\n\n  # Tokens\n  tokenSupply: String!           # Total token supply\n  token: String                  # ERC20 address if deployed\n  tokenSymbol: String\n\n  # Activity counts\n  paymentsCount: Int!\n  redeemCount: Int!\n  contributorsCount: Int!\n  nftsMintedCount: Int!\n\n  # Trending (7-day window)\n  trendingScore: Float\n  trendingVolume: String\n  trendingPaymentsCount: Int\n\n  # Omnichain\n  suckerGroupId: String          # Linked cross-chain group\n\n  # Timestamps\n  createdAt: Int!\n  deployedAt: Int\n}\n```\n\n### SuckerGroup Entity (Omnichain Projects)\n\n```graphql\ntype SuckerGroup {\n  id: String!                    # Unique group identifier\n  projects: [String!]!           # Array of project IDs\n\n  # Aggregated totals across all chains\n  volume: String!\n  volumeUsd: String\n  balance: String!\n  tokenSupply: String!\n  paymentsCount: Int!\n  contributorsCount: Int!\n\n  # Related projects (expanded)\n  projects_rel: [Project!]!\n}\n```\n\n### Participant Entity (Token Holders)\n\n```graphql\ntype Participant {\n  id: String!                    # \"{chainId}-{projectId}-{address}\"\n  address: String!\n  projectId: Int!\n  chainId: Int!\n\n  # Balances\n  balance: String!               # Total balance (credits + ERC20)\n  creditBalance: String!         # Unclaimed credits\n  erc20Balance: String!          # Claimed ERC20 tokens\n\n  # Activity\n  volume: String!                # Total contributed\n  volumeUsd: String\n  paymentsCount: Int!\n  redeemCount: Int!\n\n  # Timestamps\n  lastPaidAt: Int\n  firstPaidAt: Int\n}\n```\n\n### PayEvent Entity\n\n```graphql\ntype PayEvent {\n  id: String!\n  projectId: Int!\n  chainId: Int!\n  rulesetId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  logIndex: Int!\n  blockNumber: Int!\n\n  # Payment details\n  from: String!                  # Payer address\n  beneficiary: String!           # Token recipient\n  amount: String!                # Payment amount (wei)\n  amountUsd: String\n  distributionFromPayAmount: String!\n\n  # Tokens\n  newlyIssuedTokenCount: String!\n  beneficiaryTokenCount: String!\n\n  # Metadata\n  memo: String\n  feeFromPayAmount: String\n}\n```\n\n### CashOutEvent Entity\n\n```graphql\ntype CashOutEvent {\n  id: String!\n  projectId: Int!\n  chainId: Int!\n  rulesetId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n\n  # Redemption details\n  holder: String!\n  beneficiary: String!\n  cashOutCount: String!          # Tokens burned\n  reclaimAmount: String!         # ETH received\n  reclaimAmountUsd: String\n  metadata: String\n}\n```\n\n### NFT Entity\n\n```graphql\ntype NFT {\n  id: String!\n  tokenId: Int!\n  projectId: Int!\n  chainId: Int!\n  hook: String!                  # 721 hook address\n\n  # Tier\n  tierId: Int!\n  tierCategory: Int\n\n  # Ownership\n  owner: String!\n  createdAt: Int!\n\n  # Metadata\n  tokenUri: String\n}\n```\n\n### ActivityEvent Entity (Unified Activity Feed)\n\nA polymorphic event type that provides a unified view of all project activity. Query this instead of individual event types when building activity feeds.\n\n```graphql\ntype ActivityEvent {\n  id: String!\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Event type discriminator\n  type: ActivityEventType!       # Determines which embedded event is populated\n\n  # Embedded events (one will be non-null based on type)\n  payEvent: PayEvent\n  cashOutTokensEvent: CashOutTokensEvent\n  mintNftEvent: MintNftEvent\n  sendPayoutsEvent: SendPayoutsEvent\n  sendPayoutToSplitEvent: SendPayoutToSplitEvent\n  borrowLoanEvent: BorrowLoanEvent\n  repayLoanEvent: RepayLoanEvent\n  liquidateLoanEvent: LiquidateLoanEvent\n  deployErc20Event: DeployErc20Event\n  burnEvent: BurnEvent\n  mintTokensEvent: MintTokensEvent\n  projectCreateEvent: ProjectCreateEvent\n  addToBalanceEvent: AddToBalanceEvent\n  useAllowanceEvent: UseAllowanceEvent\n  decorateBannyEvent: DecorateBannyEvent\n  # ... and more\n\n  # Relations\n  project: Project\n  suckerGroup: SuckerGroup\n}\n\nenum ActivityEventType {\n  payEvent\n  cashOutTokensEvent\n  mintNftEvent\n  sendPayoutsEvent\n  sendPayoutToSplitEvent\n  borrowLoanEvent\n  repayLoanEvent\n  liquidateLoanEvent\n  reallocateLoanEvent\n  deployErc20Event\n  burnEvent\n  mintTokensEvent\n  manualMintTokensEvent\n  manualBurnEvent\n  autoIssueEvent\n  projectCreateEvent\n  addToBalanceEvent\n  useAllowanceEvent\n  sendReservedTokensToSplitEvent\n  sendReservedTokensToSplitsEvent\n  decorateBannyEvent\n}\n```\n\n### Loan Entity (RevLoans)\n\nActive loan state from the RevLoans protocol.\n\n```graphql\ntype Loan {\n  id: BigInt!                    # Loan ID (NFT token ID)\n  projectId: Int!\n  chainId: Int!\n  version: Int!\n  createdAt: Int!\n\n  # Loan terms\n  borrowAmount: BigInt!          # Amount borrowed (wei)\n  collateral: BigInt!            # Collateral locked (project tokens)\n  sourceFeeAmount: BigInt!       # Fee amount\n  prepaidDuration: Int!          # Prepaid period in seconds\n  prepaidFeePercent: Int!        # Fee percent (basis points)\n\n  # Addresses\n  beneficiary: String!           # Who receives borrowed funds\n  owner: String!                 # Loan NFT owner\n  token: String!                 # Collateral token address\n  terminal: String!              # Terminal address\n\n  # Metadata\n  tokenUri: String               # Loan NFT metadata URI\n\n  # Relations\n  project: Project\n  participant: Participant\n  wallet: Wallet\n}\n```\n\n### Wallet Entity\n\nWallet-level aggregation across all project participations.\n\n```graphql\ntype Wallet {\n  address: String!               # Wallet address\n\n  # Aggregated stats\n  volume: BigInt!                # Total volume across all projects\n  volumeUsd: BigInt!             # USD equivalent (18 decimals)\n  lastPaidTimestamp: Int         # Most recent payment timestamp\n\n  # Relations\n  participants: ParticipantPage  # All project participations\n  nfts: NFTPage                  # All owned NFTs\n}\n```\n\n### NFTTier Entity\n\nNFT tier configuration with pricing and supply.\n\n```graphql\ntype NFTTier {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n  tierId: Int!\n\n  # Pricing\n  price: BigInt!                 # Price in terminal token (wei)\n\n  # Supply\n  initialSupply: Int!            # Original supply\n  remainingSupply: Int!          # Current available\n\n  # Configuration\n  allowOwnerMint: Boolean!       # Owner can mint without payment\n  cannotBeRemoved: Boolean!      # Tier is permanent\n  transfersPausable: Boolean!    # Transfers can be paused\n  votingUnits: BigInt!           # Governance weight per NFT\n  category: Int                  # Tier category\n  reserveFrequency: Int          # Reserve rate\n  reserveBeneficiary: String     # Reserve recipient\n\n  # Metadata\n  encodedIpfsUri: String         # IPFS hash (encoded)\n  resolvedUri: String            # Full resolved URI\n  metadata: JSON                 # Parsed metadata\n  svg: String                    # SVG content if available\n\n  createdAt: Int!\n\n  # Relations\n  hook: NFTHook\n  nfts: NFTPage                  # Minted NFTs in this tier\n  project: Project\n}\n```\n\n### NFTHook Entity\n\n721 hook contract configuration.\n\n```graphql\ntype NFTHook {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n  createdAt: Int!\n\n  address: String!               # Hook contract address\n  name: String                   # Collection name\n  symbol: String                 # Collection symbol\n\n  # Relations\n  nfts: NFTPage\n  nftTiers: NFTTierPage\n  project: Project\n}\n```\n\n### ProjectMoment Entity (Historical Snapshots)\n\nPoint-in-time snapshots of project state. Useful for historical charts and analytics.\n\n```graphql\ntype ProjectMoment {\n  projectId: Int!\n  chainId: Int!\n  version: Int!\n\n  # Snapshot point\n  block: Int!                    # Block number\n  timestamp: Int!                # Unix timestamp\n\n  # State at snapshot\n  volume: BigInt!\n  volumeUsd: BigInt!\n  balance: BigInt!\n  trendingScore: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### SuckerGroupMoment Entity\n\nPoint-in-time snapshots of cross-chain aggregated state.\n\n```graphql\ntype SuckerGroupMoment {\n  suckerGroupId: String!\n\n  # Snapshot point\n  block: Int!\n  timestamp: Int!\n\n  # Aggregated state\n  volume: BigInt!\n  volumeUsd: BigInt!\n  balance: BigInt!\n  tokenSupply: BigInt!\n\n  # Relations\n  suckerGroup: SuckerGroup\n}\n```\n\n### SuckerTransaction Entity (Cross-Chain Bridging)\n\nToken bridging transactions between chains via suckers.\n\n```graphql\ntype SuckerTransaction {\n  index: Int!                    # Transaction index\n  projectId: Int!\n  chainId: Int!                  # Source chain\n  version: Int!\n  suckerGroupId: String!\n  createdAt: Int!\n\n  # Bridge details\n  token: String!                 # Token being bridged\n  sucker: String!                # Source sucker address\n  peer: String!                  # Destination sucker address\n  peerChainId: Int!              # Destination chain\n  beneficiary: String!           # Recipient address\n\n  # Amounts\n  projectTokenCount: BigInt!     # Project tokens bridged\n  terminalTokenAmount: BigInt!   # Terminal tokens (if any)\n\n  # State\n  root: String                   # Merkle root\n  status: SuckerTransactionStatus!\n\n  # Relations\n  suckerGroup: SuckerGroup\n}\n\nenum SuckerTransactionStatus {\n  pending\n  completed\n  failed\n}\n```\n\n### SendPayoutsEvent Entity\n\nPayout distribution events from the terminal.\n\n```graphql\ntype SendPayoutsEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  logIndex: Int!\n\n  # Payout details\n  caller: String!                # Who triggered payout\n  from: String!                  # Source address\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n\n  # Amounts\n  amount: BigInt!                # Total payout amount\n  amountUsd: BigInt!\n  amountPaidOut: BigInt!         # Actually distributed\n  amountPaidOutUsd: BigInt!\n  netLeftoverPayoutAmount: BigInt!  # Remaining after splits\n  fee: BigInt!                   # Protocol fee\n  feeUsd: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### UseAllowanceEvent Entity\n\nSurplus allowance usage events.\n\n```graphql\ntype UseAllowanceEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n\n  # Allowance details\n  caller: String!\n  beneficiary: String!           # Who receives funds\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n\n  # Amounts\n  amount: BigInt!                # Amount used\n  amountUsd: BigInt!\n  netAmount: BigInt!             # After fees\n  netAmountUsd: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### PermissionHolder Entity\n\nOperator permissions granted to accounts.\n\n```graphql\ntype PermissionHolder {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n\n  account: String!               # Account with permissions\n  operator: String!              # Operator address\n  permissions: [Int!]!           # Permission IDs granted\n  isRevnetOperator: Boolean!     # Is a revnet operator\n\n  # Relations\n  project: Project\n}\n```\n\n### BorrowLoanEvent Entity\n\nLoan creation events from RevLoans.\n\n```graphql\ntype BorrowLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Loan details\n  borrowAmount: BigInt!          # Amount borrowed\n  collateral: BigInt!            # Collateral locked\n  sourceFeeAmount: BigInt!       # Fee paid\n  prepaidDuration: Int!          # Prepaid period (seconds)\n  prepaidFeePercent: Int!        # Fee percent (basis points)\n  beneficiary: String!           # Loan recipient\n  token: String!                 # Collateral token\n  terminal: String!              # Terminal address\n\n  # Relations\n  project: Project\n}\n```\n\n### RepayLoanEvent Entity\n\nLoan repayment events.\n\n```graphql\ntype RepayLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Repayment details\n  loanId: BigInt!                # Loan being repaid\n  paidOffLoanId: BigInt          # If fully paid off\n  repayBorrowAmount: BigInt!     # Amount repaid\n  collateralCountToReturn: BigInt!  # Collateral returned\n\n  # Relations\n  project: Project\n}\n```\n\n### LiquidateLoanEvent Entity\n\nLoan liquidation events.\n\n```graphql\ntype LiquidateLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Liquidation details\n  borrowAmount: BigInt!          # Outstanding borrow\n  collateral: BigInt!            # Collateral seized\n\n  # Relations\n  project: Project\n}\n```\n\n### ReallocateLoanEvent Entity\n\nLoan reallocation events (moving collateral between loans).\n\n```graphql\ntype ReallocateLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Reallocation details\n  loanId: BigInt!                # Source loan\n  reallocatedLoanId: BigInt!     # Target loan\n  removedCollateralCount: BigInt!  # Collateral moved\n\n  # Relations\n  project: Project\n}\n```\n\n### BurnEvent Entity\n\nToken burn events (from cash outs).\n\n```graphql\ntype BurnEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Burn details\n  amount: BigInt!                # Total burned\n  creditAmount: BigInt!          # Credits burned\n  erc20Amount: BigInt!           # ERC20 burned\n\n  # Relations\n  project: Project\n}\n```\n\n### MintTokensEvent Entity\n\nToken minting events (from payments).\n\n```graphql\ntype MintTokensEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  beneficiary: String!           # Token recipient\n  beneficiaryTokenCount: BigInt! # Tokens to beneficiary\n  reservedPercent: BigInt!       # Reserved rate\n  tokenCount: BigInt!            # Total minted\n  memo: String                   # Payment memo\n\n  # Relations\n  project: Project\n}\n```\n\n### ManualMintTokensEvent Entity\n\nManual token minting by project owner.\n\n```graphql\ntype ManualMintTokensEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  beneficiary: String!\n  beneficiaryTokenCount: BigInt!\n  reservedPercent: BigInt!\n  tokenCount: BigInt!\n  memo: String\n\n  # Relations\n  project: Project\n}\n```\n\n### ManualBurnEvent Entity\n\nManual token burning.\n\n```graphql\ntype ManualBurnEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Burn details\n  amount: BigInt!\n  creditAmount: BigInt!\n  erc20Amount: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### MintNftEvent Entity\n\nNFT minting events.\n\n```graphql\ntype MintNftEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  hook: String!                  # 721 hook address\n  beneficiary: String!           # NFT recipient\n  tierId: Int!                   # Tier minted\n  tokenId: BigInt!               # Token ID\n  totalAmountPaid: BigInt!       # Amount paid\n\n  # Relations\n  project: Project\n  tier: NFTTier\n  nft: NFT\n}\n```\n\n### DeployErc20Event Entity\n\nERC20 token deployment events.\n\n```graphql\ntype DeployErc20Event {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Token details\n  symbol: String!                # Token symbol\n  name: String!                  # Token name\n  token: String!                 # Token address\n\n  # Relations\n  project: Project\n}\n```\n\n### ProjectCreateEvent Entity\n\nProject creation events.\n\n```graphql\ntype ProjectCreateEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Relations\n  project: Project\n}\n```\n\n### AddToBalanceEvent Entity\n\nDirect balance addition events.\n\n```graphql\ntype AddToBalanceEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Details\n  amount: BigInt!                # Amount added\n  memo: String                   # Memo\n  metadata: String               # Additional metadata\n  returnedFees: BigInt           # Fees returned\n\n  # Relations\n  project: Project\n}\n```\n\n### SendPayoutToSplitEvent Entity\n\nIndividual split payout events.\n\n```graphql\ntype SendPayoutToSplitEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Split details\n  rulesetId: Int!\n  group: BigInt!                 # Split group\n  beneficiary: String!           # Split recipient\n  splitProjectId: Int            # If split to project\n  hook: String                   # Split hook if any\n\n  # Amounts\n  amount: BigInt!                # Gross amount\n  netAmount: BigInt!             # After fees\n  amountUsd: BigInt!\n  percent: Int!                  # Split percent\n  lockedUntil: BigInt            # Lock timestamp\n  preferAddToBalance: Boolean!   # Add to balance vs pay\n\n  # Relations\n  project: Project\n}\n```\n\n### SendReservedTokensToSplitEvent Entity\n\nReserved token distribution to individual split.\n\n```graphql\ntype SendReservedTokensToSplitEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Split details\n  rulesetId: Int!\n  groupId: BigInt!\n  beneficiary: String!\n  splitProjectId: Int\n  hook: String\n  tokenCount: BigInt!            # Tokens sent\n  percent: Int!\n  lockedUntil: BigInt\n  preferAddToBalance: Boolean!\n\n  # Relations\n  project: Project\n}\n```\n\n### SendReservedTokensToSplitsEvent Entity\n\nBatch reserved token distribution event.\n\n```graphql\ntype SendReservedTokensToSplitsEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Distribution details\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n  owner: String!                 # Project owner\n  tokenCount: BigInt!            # Total distributed\n  leftoverAmount: BigInt!        # Remaining after splits\n\n  # Relations\n  project: Project\n}\n```\n\n### AutoIssueEvent Entity\n\nAuto-issuance events (revnet stage transitions).\n\n```graphql\ntype AutoIssueEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Issuance details\n  stageId: BigInt!               # Revnet stage\n  beneficiary: String!           # Token recipient\n  count: BigInt!                 # Tokens issued\n\n  # Relations\n  project: Project\n}\n```\n\n### StoreAutoIssuanceAmountEvent Entity\n\nAuto-issuance configuration events.\n\n```graphql\ntype StoreAutoIssuanceAmountEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Config details\n  stageId: BigInt!               # Revnet stage\n  beneficiary: String!           # Configured recipient\n  count: BigInt!                 # Amount to auto-issue\n\n  # Relations\n  project: Project\n}\n```\n\n### DecorateBannyEvent Entity\n\nBanny NFT decoration events.\n\n```graphql\ntype DecorateBannyEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Decoration details\n  bannyBodyId: BigInt!           # Banny being decorated\n  outfitIds: [BigInt!]!          # Outfit NFT IDs\n  backgroundId: BigInt           # Background NFT ID\n  tokenUri: String               # Updated token URI\n  tokenUriMetadata: JSON         # Parsed metadata\n\n  # Relations\n  bannyNft: NFT\n}\n```\n\n### CashOutTaxSnapshot Entity\n\nHistorical cash-out tax rate snapshots.\n\n```graphql\ntype CashOutTaxSnapshot {\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Snapshot period\n  start: BigInt!                 # Period start timestamp\n  duration: BigInt!              # Period duration\n  rulesetId: BigInt!             # Ruleset ID\n\n  # Tax rate\n  cashOutTax: Int!               # Tax rate (basis points)\n}\n```\n\n### ParticipantSnapshot Entity (GraphQL)\n\nHistorical participant balance snapshots via GraphQL (alternative to REST endpoint).\n\n```graphql\ntype ParticipantSnapshot {\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Snapshot point\n  block: Int!\n  timestamp: Int!\n\n  # Participant\n  address: String!\n\n  # Balances at snapshot\n  balance: BigInt!\n  creditBalance: BigInt!\n  erc20Balance: BigInt!\n  volume: BigInt!\n  volumeUsd: BigInt!\n}\n```\n\n---\n\n## Critical Concepts\n\n### Project Identity\n\n**A Juicebox project is uniquely identified by three fields: `projectId + chainId + version`.**\n\nThis is crucial because:\n- **V4 and V5 are completely different protocols.** Project #64 on Ethereum V4 is NOT the same project as Project #64 on Ethereum V5.\n- The same projectId can exist on multiple chains (via suckers\u002Fomnichain), but those ARE the same project.\n- Always include `version` when querying or displaying projects.\n\n```javascript\n\u002F\u002F WRONG: Groups V4 and V5 together\nconst groupKey = `${project.projectId}-${project.chainId}`;\n\n\u002F\u002F CORRECT: Keeps V4 and V5 separate\nconst groupKey = `${project.projectId}-v${project.version}`;\n```\n\n### Multi-Chain Grouping\n\nWhen displaying \"top projects\" or aggregating stats:\n- **Same projectId + version across chains** → Group together (same project via suckers)\n- **Same projectId, different version** → Keep separate (completely different projects)\n\n```javascript\n\u002F\u002F Group projects by projectId + version (V4 and V5 are different projects!)\nconst grouped = new Map();\n\nfor (const project of projects) {\n  const groupKey = `${project.projectId}-v${project.version || 4}`;\n  const existing = grouped.get(groupKey);\n\n  if (existing) {\n    \u002F\u002F Add chain to existing group\n    if (!existing.chainIds.includes(project.chainId)) {\n      existing.chainIds.push(project.chainId);\n    }\n    \u002F\u002F Sum volumes\n    existing.totalVolumeUsd += parseFloat(project.volumeUsd || '0');\n  } else {\n    grouped.set(groupKey, {\n      ...project,\n      chainIds: [project.chainId],\n      totalVolumeUsd: parseFloat(project.volumeUsd || '0')\n    });\n  }\n}\n```\n\n### USD Value Formatting\n\nThe `volumeUsd`, `amountUsd`, and similar fields use **18 decimal format** (like wei). You must convert properly:\n\n```javascript\nfunction formatVolumeUsd(volumeUsd) {\n  if (!volumeUsd || volumeUsd === '0') return '$0';\n\n  try {\n    \u002F\u002F volumeUsd comes in 18 decimal format\n    \u002F\u002F Use BigInt to avoid precision loss on large numbers\n    const raw = BigInt(volumeUsd.split('.')[0]);\n    const usd = Number(raw \u002F BigInt(1e12)) \u002F 1e6; \u002F\u002F Divide in steps\n\n    if (usd >= 1_000_000) return `$${(usd \u002F 1_000_000).toFixed(1)}M`;\n    if (usd >= 1_000) return `$${(usd \u002F 1_000).toFixed(1)}k`;\n    if (usd >= 1) return `$${usd.toFixed(0)}`;\n    return `$${usd.toFixed(2)}`;\n  } catch {\n    return '$0';\n  }\n}\n```\n\n**Warning:** Do NOT use `parseFloat()` directly on volumeUsd for large values—JavaScript loses precision beyond ~15 digits.\n\n### Filtering by Version\n\nWhen querying projects, filter by version to avoid mixing V4 and V5 data:\n\n```graphql\n# Get only V5 projects\nquery V5Projects($limit: Int!) {\n  projects(\n    where: { version: 5 }\n    orderBy: \"volumeUsd\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      projectId\n      chainId\n      version\n      name\n      volumeUsd\n    }\n  }\n}\n```\n\nTo display both versions, query them separately and handle grouping in your application.\n\n---\n\n## Query Examples\n\n### Get Single Project\n\n```graphql\nquery GetProject($projectId: Int!, $chainId: Int!) {\n  project(projectId: $projectId, chainId: $chainId) {\n    id\n    name\n    handle\n    owner\n    balance\n    volume\n    volumeUsd\n    tokenSupply\n    paymentsCount\n    contributorsCount\n    suckerGroupId\n  }\n}\n```\n\n### Get Participant (Token Holder)\n\n```graphql\nquery GetParticipant($projectId: Int!, $chainId: Int!, $address: String!) {\n  participant(projectId: $projectId, chainId: $chainId, address: $address) {\n    balance\n    creditBalance\n    erc20Balance\n    volume\n    volumeUsd\n    paymentsCount\n  }\n}\n```\n\n### Get Sucker Group (Omnichain Totals)\n\n```graphql\nquery GetSuckerGroup($id: String!) {\n  suckerGroup(id: $id) {\n    id\n    volume\n    volumeUsd\n    balance\n    tokenSupply\n    paymentsCount\n    contributorsCount\n    projects_rel {\n      projectId\n      chainId\n      name\n      balance\n      volume\n    }\n  }\n}\n```\n\n### List Projects\n\n```graphql\nquery ListProjects($chainId: Int, $version: Int, $limit: Int!, $offset: Int!) {\n  projects(\n    where: { chainId: $chainId, version: $version }\n    orderBy: \"volumeUsd\"\n    orderDirection: \"desc\"\n    limit: $limit\n    offset: $offset\n  ) {\n    items {\n      projectId\n      chainId\n      version\n      name\n      handle\n      volumeUsd\n      balance\n      paymentsCount\n    }\n    totalCount\n  }\n}\n```\n\n### List Recent Payments\n\n```graphql\nquery ListPayments($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  payEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      from\n      beneficiary\n      amount\n      amountUsd\n      memo\n      newlyIssuedTokenCount\n    }\n  }\n}\n```\n\n### List Top Token Holders\n\n```graphql\nquery ListParticipants($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  participants(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"balance\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      address\n      balance\n      creditBalance\n      erc20Balance\n      volume\n      paymentsCount\n    }\n    totalCount\n  }\n}\n```\n\n### Get Trending Projects\n\n```graphql\nquery TrendingProjects($limit: Int!) {\n  projects(\n    orderBy: \"trendingScore\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      projectId\n      chainId\n      name\n      handle\n      trendingScore\n      trendingVolume\n      trendingPaymentsCount\n    }\n  }\n}\n```\n\n### List Cash Out Events\n\n```graphql\nquery ListCashOuts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  cashOutEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      holder\n      beneficiary\n      cashOutCount\n      reclaimAmount\n      reclaimAmountUsd\n    }\n  }\n}\n```\n\n### List NFTs for Project\n\n```graphql\nquery ListNFTs($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  nfts(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      tokenId\n      tierId\n      tierCategory\n      owner\n      createdAt\n      tokenUri\n    }\n  }\n}\n```\n\n### Get Unified Activity Feed\n\nThe most powerful query for building activity feeds. Returns all event types in a single query.\n\n```graphql\nquery GetActivityFeed($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  activityEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      id\n      timestamp\n      txHash\n      from\n      type                        # Discriminator for event type\n\n      # Include fields from each possible event type\n      payEvent {\n        amount\n        amountUsd\n        beneficiary\n        memo\n      }\n      cashOutTokensEvent {\n        cashOutCount\n        reclaimAmount\n        holder\n      }\n      mintNftEvent {\n        tierId\n        tokenId\n      }\n      sendPayoutsEvent {\n        amount\n        amountPaidOut\n        fee\n      }\n      borrowLoanEvent {\n        borrowAmount\n        collateral\n      }\n    }\n  }\n}\n```\n\n### Get Omnichain Activity Feed\n\nQuery activity across all chains for a sucker group.\n\n```graphql\nquery GetOmnichainActivity($suckerGroupId: String!, $limit: Int!) {\n  activityEvents(\n    where: { suckerGroupId: $suckerGroupId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      chainId\n      timestamp\n      type\n      txHash\n      from\n      payEvent { amount, memo }\n      cashOutTokensEvent { cashOutCount, reclaimAmount }\n    }\n  }\n}\n```\n\n### List Active Loans\n\n```graphql\nquery ListLoans($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  loans(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      id\n      borrowAmount\n      collateral\n      prepaidDuration\n      prepaidFeePercent\n      owner\n      beneficiary\n      createdAt\n    }\n    totalCount\n  }\n}\n```\n\n### Get Loan by ID\n\n```graphql\nquery GetLoan($id: BigInt!) {\n  loan(id: $id) {\n    id\n    projectId\n    chainId\n    borrowAmount\n    collateral\n    sourceFeeAmount\n    prepaidDuration\n    prepaidFeePercent\n    beneficiary\n    owner\n    token\n    terminal\n    tokenUri\n    createdAt\n  }\n}\n```\n\n### Get Wallet Portfolio\n\n```graphql\nquery GetWallet($address: String!) {\n  wallet(address: $address) {\n    address\n    volume\n    volumeUsd\n    lastPaidTimestamp\n    participants(limit: 100) {\n      items {\n        projectId\n        chainId\n        balance\n        volume\n      }\n    }\n    nfts(limit: 50) {\n      items {\n        projectId\n        chainId\n        tokenId\n        tierId\n      }\n    }\n  }\n}\n```\n\n### List NFT Tiers\n\n```graphql\nquery ListNFTTiers($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  nftTiers(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"tierId\"\n    orderDirection: \"asc\"\n    limit: $limit\n  ) {\n    items {\n      tierId\n      price\n      initialSupply\n      remainingSupply\n      category\n      votingUnits\n      resolvedUri\n      metadata\n      svg\n    }\n  }\n}\n```\n\n### Get NFT Hook Details\n\n```graphql\nquery GetNFTHook($projectId: Int!, $chainId: Int!) {\n  nftHooks(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: 1\n  ) {\n    items {\n      address\n      name\n      symbol\n      nftTiers(limit: 100) {\n        items {\n          tierId\n          price\n          remainingSupply\n        }\n      }\n    }\n  }\n}\n```\n\n### Get Historical Project Snapshots\n\n```graphql\nquery GetProjectHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  projectMoments(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      block\n      timestamp\n      volume\n      volumeUsd\n      balance\n      trendingScore\n    }\n  }\n}\n```\n\n### Get Cross-Chain Bridge Transactions\n\n```graphql\nquery GetSuckerTransactions($suckerGroupId: String!, $limit: Int!) {\n  suckerTransactions(\n    where: { suckerGroupId: $suckerGroupId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      index\n      chainId\n      peerChainId\n      beneficiary\n      projectTokenCount\n      terminalTokenAmount\n      status\n      createdAt\n    }\n  }\n}\n```\n\n### List Payout Events\n\n```graphql\nquery ListPayouts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendPayoutsEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      caller\n      rulesetCycleNumber\n      amount\n      amountPaidOut\n      fee\n      netLeftoverPayoutAmount\n    }\n  }\n}\n```\n\n### List Allowance Usage\n\n```graphql\nquery ListAllowanceUsage($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  useAllowanceEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      caller\n      beneficiary\n      amount\n      netAmount\n      rulesetCycleNumber\n    }\n  }\n}\n```\n\n### List Loan Events (Borrow\u002FRepay\u002FLiquidate)\n\n```graphql\nquery ListBorrowEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  borrowLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      borrowAmount\n      collateral\n      prepaidDuration\n      beneficiary\n    }\n  }\n}\n\nquery ListRepayEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  repayLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      loanId\n      repayBorrowAmount\n      collateralCountToReturn\n    }\n  }\n}\n\nquery ListLiquidations($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  liquidateLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      borrowAmount\n      collateral\n    }\n  }\n}\n```\n\n### List Token Burns\n\n```graphql\nquery ListBurns($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  burnEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      from\n      amount\n      creditAmount\n      erc20Amount\n    }\n  }\n}\n```\n\n### List NFT Mints\n\n```graphql\nquery ListNFTMints($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  mintNftEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      beneficiary\n      tierId\n      tokenId\n      totalAmountPaid\n    }\n  }\n}\n```\n\n### Get Permission Holders\n\n```graphql\nquery ListPermissionHolders($projectId: Int!, $chainId: Int!) {\n  permissionHolders(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: 100\n  ) {\n    items {\n      account\n      operator\n      permissions\n      isRevnetOperator\n    }\n  }\n}\n```\n\n### List Project Creations\n\n```graphql\nquery ListProjectCreations($chainId: Int!, $limit: Int!) {\n  projectCreateEvents(\n    where: { chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      projectId\n      caller\n      project {\n        name\n        handle\n        owner\n      }\n    }\n  }\n}\n```\n\n### List ERC20 Deployments\n\n```graphql\nquery ListERC20Deployments($chainId: Int!, $limit: Int!) {\n  deployErc20Events(\n    where: { chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      projectId\n      name\n      symbol\n      token\n    }\n  }\n}\n```\n\n### Get Cash Out Tax History\n\n```graphql\nquery GetCashOutTaxHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  cashOutTaxSnapshots(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"start\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      start\n      duration\n      rulesetId\n      cashOutTax\n    }\n  }\n}\n```\n\n### Get Participant History (GraphQL Snapshots)\n\n```graphql\nquery GetParticipantHistory(\n  $projectId: Int!,\n  $chainId: Int!,\n  $address: String!,\n  $limit: Int!\n) {\n  participantSnapshots(\n    where: { projectId: $projectId, chainId: $chainId, address: $address }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      block\n      timestamp\n      balance\n      creditBalance\n      erc20Balance\n      volume\n      volumeUsd\n    }\n  }\n}\n```\n\n### List Reserved Token Distributions\n\n```graphql\nquery ListReservedDistributions($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendReservedTokensToSplitsEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      rulesetCycleNumber\n      tokenCount\n      leftoverAmount\n      owner\n    }\n  }\n}\n```\n\n### List Split Payouts\n\n```graphql\nquery ListSplitPayouts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendPayoutToSplitEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      beneficiary\n      splitProjectId\n      amount\n      netAmount\n      percent\n    }\n  }\n}\n```\n\n---\n\n## Filtering\n\nThe `where` clause supports these operators:\n\n```graphql\nwhere: {\n  # Exact match\n  projectId: 1\n  chainId: 1\n\n  # Multiple values (OR)\n  chainId_in: [1, 10, 8453]\n\n  # Comparison operators\n  balance_gt: \"1000000000000000000\"\n  balance_gte: \"1000000000000000000\"\n  balance_lt: \"100000000000000000000\"\n  balance_lte: \"100000000000000000000\"\n  timestamp_gte: 1704067200\n\n  # Text search\n  name_contains: \"dao\"\n  handle_starts_with: \"jb\"\n}\n```\n\n---\n\n## Sorting\n\n```graphql\norderBy: \"volume\"           # Field to sort by\norderDirection: \"desc\"      # \"asc\" or \"desc\"\n```\n\n**Sortable Fields by Entity:**\n\n| Entity | Sortable Fields |\n|--------|-----------------|\n| Project | `volume`, `balance`, `tokenSupply`, `paymentsCount`, `createdAt`, `trendingScore` |\n| Participant | `balance`, `volume`, `paymentsCount` |\n| PayEvent | `timestamp`, `amount` |\n| CashOutEvent | `timestamp`, `reclaimAmount` |\n| NFT | `createdAt`, `tokenId` |\n| ActivityEvent | `timestamp` |\n| Loan | `createdAt`, `borrowAmount`, `collateral` |\n| Wallet | `volume`, `volumeUsd`, `lastPaidTimestamp` |\n| NFTTier | `tierId`, `price`, `createdAt` |\n| ProjectMoment | `timestamp`, `block` |\n| SuckerTransaction | `createdAt` |\n| SendPayoutsEvent | `timestamp`, `amount` |\n| UseAllowanceEvent | `timestamp`, `amount` |\n| BorrowLoanEvent | `timestamp`, `borrowAmount` |\n| RepayLoanEvent | `timestamp`, `repayBorrowAmount` |\n| LiquidateLoanEvent | `timestamp` |\n| BurnEvent | `timestamp`, `amount` |\n| MintTokensEvent | `timestamp`, `tokenCount` |\n| MintNftEvent | `timestamp`, `tokenId` |\n| DeployErc20Event | `timestamp` |\n| ProjectCreateEvent | `timestamp` |\n| SendPayoutToSplitEvent | `timestamp`, `amount` |\n| CashOutTaxSnapshot | `start` |\n| ParticipantSnapshot | `timestamp`, `block` |\n\n---\n\n## Pagination\n\nAll list queries support pagination:\n\n```graphql\nquery PaginatedPayments(\n  $projectId: Int!,\n  $chainId: Int!,\n  $limit: Int!,\n  $offset: Int!\n) {\n  payEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: $limit\n    offset: $offset\n  ) {\n    items { ... }\n    totalCount\n  }\n}\n```\n\n**Parameters:**\n- `limit`: Max items to return (default: 100, max: 1000)\n- `offset`: Number of items to skip\n\n---\n\n## Special Endpoints\n\n### Participant Snapshots\n\nGet historical participant balances at a specific timestamp. Useful for governance snapshots and airdrops.\n\n```\nPOST https:\u002F\u002Fbendystraw.xyz\u002F{API_KEY}\u002Fparticipants\n```\n\n**Request:**\n```json\n{\n  \"suckerGroupId\": \"0x...\",\n  \"timestamp\": 1704067200\n}\n```\n\n**Response:**\n```json\n{\n  \"participants\": [\n    {\n      \"address\": \"0x...\",\n      \"balance\": \"1000000000000000000000\",\n      \"chains\": {\n        \"1\": \"600000000000000000000\",\n        \"10\": \"400000000000000000000\"\n      }\n    }\n  ]\n}\n```\n\n---\n\n## Complete JavaScript Example\n\n```javascript\nconst BENDYSTRAW_URL = 'https:\u002F\u002Fbendystraw.xyz';\nconst API_KEY = process.env.BENDYSTRAW_API_KEY;\n\n\u002F**\n * Execute GraphQL query\n *\u002F\nasync function query(graphql, variables = {}) {\n  const response = await fetch(`${BENDYSTRAW_URL}\u002F${API_KEY}\u002Fgraphql`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ query: graphql, variables })\n  });\n\n  const result = await response.json();\n\n  if (result.errors) {\n    throw new Error(result.errors[0].message);\n  }\n\n  return result.data;\n}\n\n\u002F**\n * Get project stats\n *\u002F\nasync function getProject(projectId, chainId) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!) {\n      project(projectId: $projectId, chainId: $chainId) {\n        name\n        handle\n        owner\n        balance\n        volume\n        volumeUsd\n        tokenSupply\n        paymentsCount\n        contributorsCount\n        suckerGroupId\n      }\n    }\n  `, { projectId, chainId });\n\n  return data.project;\n}\n\n\u002F**\n * Get omnichain totals for sucker group\n *\u002F\nasync function getOmnichainStats(suckerGroupId) {\n  const data = await query(`\n    query($id: String!) {\n      suckerGroup(id: $id) {\n        volume\n        volumeUsd\n        balance\n        tokenSupply\n        contributorsCount\n        projects_rel {\n          chainId\n          name\n          balance\n          volume\n        }\n      }\n    }\n  `, { id: suckerGroupId });\n\n  return data.suckerGroup;\n}\n\n\u002F**\n * Get recent payments\n *\u002F\nasync function getRecentPayments(projectId, chainId, limit = 20) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      payEvents(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          timestamp\n          from\n          beneficiary\n          amount\n          amountUsd\n          memo\n          newlyIssuedTokenCount\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.payEvents.items;\n}\n\n\u002F**\n * Get top token holders\n *\u002F\nasync function getTopHolders(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      participants(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"balance\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          address\n          balance\n          creditBalance\n          erc20Balance\n          volume\n        }\n        totalCount\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return {\n    holders: data.participants.items,\n    total: data.participants.totalCount\n  };\n}\n\n\u002F**\n * Get participant balance\n *\u002F\nasync function getParticipant(projectId, chainId, address) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $address: String!) {\n      participant(projectId: $projectId, chainId: $chainId, address: $address) {\n        balance\n        creditBalance\n        erc20Balance\n        volume\n        volumeUsd\n        paymentsCount\n      }\n    }\n  `, { projectId, chainId, address });\n\n  return data.participant;\n}\n\n\u002F**\n * Get trending projects\n *\u002F\nasync function getTrendingProjects(chainId = null, limit = 10) {\n  const where = chainId ? { chainId } : {};\n\n  const data = await query(`\n    query($where: ProjectWhereInput, $limit: Int!) {\n      projects(\n        where: $where\n        orderBy: \"trendingScore\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          projectId\n          chainId\n          name\n          handle\n          trendingScore\n          trendingVolume\n          trendingPaymentsCount\n        }\n      }\n    }\n  `, { where, limit });\n\n  return data.projects.items;\n}\n\n\u002F**\n * Get historical snapshot for governance\n *\u002F\nasync function getSnapshot(suckerGroupId, timestamp) {\n  const response = await fetch(`${BENDYSTRAW_URL}\u002F${API_KEY}\u002Fparticipants`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ suckerGroupId, timestamp })\n  });\n\n  return await response.json();\n}\n\n\u002F**\n * Get unified activity feed (all event types)\n *\u002F\nasync function getActivityFeed(projectId, chainId, limit = 50) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      activityEvents(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          id\n          timestamp\n          txHash\n          from\n          type\n          payEvent { amount, amountUsd, beneficiary, memo }\n          cashOutTokensEvent { cashOutCount, reclaimAmount, holder }\n          mintNftEvent { tierId, tokenId }\n          sendPayoutsEvent { amount, amountPaidOut, fee }\n          borrowLoanEvent { borrowAmount, collateral }\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.activityEvents.items;\n}\n\n\u002F**\n * Get active loans for a project\n *\u002F\nasync function getLoans(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      loans(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"createdAt\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          id\n          borrowAmount\n          collateral\n          prepaidDuration\n          prepaidFeePercent\n          owner\n          beneficiary\n          createdAt\n        }\n        totalCount\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return {\n    loans: data.loans.items,\n    total: data.loans.totalCount\n  };\n}\n\n\u002F**\n * Get wallet portfolio across all projects\n *\u002F\nasync function getWalletPortfolio(address) {\n  const data = await query(`\n    query($address: String!) {\n      wallet(address: $address) {\n        address\n        volume\n        volumeUsd\n        lastPaidTimestamp\n        participants(limit: 100) {\n          items {\n            projectId\n            chainId\n            balance\n            volume\n            project { name, handle }\n          }\n        }\n      }\n    }\n  `, { address });\n\n  return data.wallet;\n}\n\n\u002F**\n * Get NFT tier details with metadata\n *\u002F\nasync function getNFTTiers(projectId, chainId) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!) {\n      nftTiers(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"tierId\"\n        orderDirection: \"asc\"\n        limit: 100\n      ) {\n        items {\n          tierId\n          price\n          initialSupply\n          remainingSupply\n          category\n          votingUnits\n          resolvedUri\n          metadata\n          svg\n        }\n      }\n    }\n  `, { projectId, chainId });\n\n  return data.nftTiers.items;\n}\n\n\u002F**\n * Get historical project snapshots for charting\n *\u002F\nasync function getProjectHistory(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      projectMoments(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          block\n          timestamp\n          volume\n          volumeUsd\n          balance\n          trendingScore\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.projectMoments.items;\n}\n\n\u002F\u002F Example usage\nasync function main() {\n  \u002F\u002F Get project on Ethereum mainnet\n  const project = await getProject(1, 1);\n  console.log(`${project.name}: ${project.balance} wei balance`);\n\n  \u002F\u002F If omnichain, get aggregated stats\n  if (project.suckerGroupId) {\n    const omni = await getOmnichainStats(project.suckerGroupId);\n    console.log(`Omnichain total: ${omni.volume} wei volume across ${omni.projects_rel.length} chains`);\n  }\n\n  \u002F\u002F Get recent activity\n  const payments = await getRecentPayments(1, 1, 5);\n  console.log(`Last ${payments.length} payments:`);\n  payments.forEach(p => {\n    console.log(`  ${p.from.slice(0,8)}... paid ${p.amount} wei`);\n  });\n\n  \u002F\u002F Get top holders\n  const { holders, total } = await getTopHolders(1, 1, 10);\n  console.log(`Top 10 of ${total} holders:`);\n  holders.forEach((h, i) => {\n    console.log(`  ${i + 1}. ${h.address.slice(0,8)}... - ${h.balance} tokens`);\n  });\n}\n\nmain().catch(console.error);\n```\n\n---\n\n## BendystrawClient Class\n\n```javascript\nclass BendystrawClient {\n  constructor(apiKey, isTestnet = false) {\n    this.baseUrl = isTestnet\n      ? 'https:\u002F\u002Ftestnet.bendystraw.xyz'\n      : 'https:\u002F\u002Fbendystraw.xyz';\n    this.apiKey = apiKey;\n  }\n\n  async query(graphql, variables = {}) {\n    const response = await fetch(`${this.baseUrl}\u002F${this.apiKey}\u002Fgraphql`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({ query: graphql, variables })\n    });\n\n    if (!response.ok) {\n      throw new Error(`Bendystraw request failed: ${response.statusText}`);\n    }\n\n    const result = await response.json();\n    if (result.errors) {\n      throw new Error(result.errors[0].message);\n    }\n\n    return result.data;\n  }\n\n  \u002F\u002F Convenience methods\n  async getProject(projectId, chainId) { ... }\n  async getSuckerGroup(id) { ... }\n  async getPayments(projectId, chainId, limit) { ... }\n  async getParticipants(projectId, chainId, limit) { ... }\n  async getSnapshot(suckerGroupId, timestamp) { ... }\n}\n```\n\n---\n\n## Server-Side Proxy\n\nSince the API key must be kept secret, use a server-side proxy:\n\n### Next.js API Route\n\n```typescript\n\u002F\u002F pages\u002Fapi\u002Fbendystraw.ts\nexport default async function handler(req, res) {\n  if (req.method !== 'POST') {\n    return res.status(405).json({ error: 'Method not allowed' });\n  }\n\n  const response = await fetch(\n    `https:\u002F\u002Fbendystraw.xyz\u002F${process.env.BENDYSTRAW_API_KEY}\u002Fgraphql`,\n    {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(req.body)\n    }\n  );\n\n  const data = await response.json();\n  res.json(data);\n}\n```\n\n### Express Middleware\n\n```javascript\napp.post('\u002Fapi\u002Fbendystraw', async (req, res) => {\n  const response = await fetch(\n    `https:\u002F\u002Fbendystraw.xyz\u002F${process.env.BENDYSTRAW_API_KEY}\u002Fgraphql`,\n    {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(req.body)\n    }\n  );\n\n  res.json(await response.json());\n});\n```\n\n---\n\n## Error Handling\n\n| Error | Cause | Solution |\n|-------|-------|----------|\n| `Unauthorized` | Invalid API key | Verify API key is correct |\n| `Rate limited` | Too many requests | Add backoff\u002Fretry logic |\n| `Invalid query` | GraphQL syntax error | Check query structure |\n| `Not found` | Entity doesn't exist | Verify projectId\u002FchainId |\n| `Timeout` | Query too complex | Reduce limit, add filters |\n\n---\n\n## Gotchas & Common Pitfalls\n\n### V4 vs V5 Protocol Confusion\n\n**CRITICAL:** V4 and V5 are completely different protocols with different contract addresses.\nProject #1 on V4 is NOT the same as Project #1 on V5. Never mix V4 and V5 addresses.\n\n### V5.0 vs V5.1 Contract Confusion\n\n**CRITICAL:** Within V5, there are two contract versions: V5.0 and V5.1. Contracts that have\nboth versions MUST NOT be mixed. A project using JBController5_1 MUST use JBMultiTerminal5_1.\n\n**Shared contracts (work with both V5.0 and V5.1):**\n\n| Contract | Address |\n|----------|---------|\n| JBProjects | `0x885f707efa18d2cb12f05a3a8eba6b4b26c8c1d4` |\n| JBTokens | `0x4d0edd347fb1fa21589c1e109b3474924be87636` |\n| JBDirectory | `0x0061e516886a0540f63157f112c0588ee0651dcf` |\n| JBSplits | `0x7160a322fea44945a6ef9adfd65c322258df3c5e` |\n\n**V5.0 contracts (for revnets and older projects):**\n\n| Contract | Address |\n|----------|---------|\n| JBController | `0x27da30646502e2f642be5281322ae8c394f7668a` |\n| JBMultiTerminal | `0x2db6d704058e552defe415753465df8df0361846` |\n| JBRulesets | `0x6292281d69c3593fcf6ea074e5797341476ab428` |\n| REVDeployer | `0x2ca27bde7e7d33e353b44c27acfcf6c78dde251d` |\n| JB721TiersHookDeployer | `0x7e4f7bfeab74bbae3eb12a62f2298bf2be16fc93` |\n\n**V5.1 contracts (for new projects):**\n\n| Contract | Address |\n|----------|---------|\n| JBController5_1 | `0xf3cc99b11bd73a2e3b8815fb85fe0381b29987e1` |\n| JBMultiTerminal5_1 | `0x52869db3d61dde1e391967f2ce5039ad0ecd371c` |\n| JBRulesets5_1 | `0xd4257005ca8d27bbe11f356453b0e4692414b056` |\n| JBOmnichainDeployer5_1 | `0x587bf86677ec0d1b766d9ba0d7ac2a51c6c2fc71` |\n| JB721TiersHookDeployer5_1 | `0x7e6e7db5081c59f2df3c83b54eb0c4d029e9898e` |\n\n**Determining project version:** Query `JBDirectory.controllerOf(projectId)` and compare:\n- `0x27da30646502e2f642be5281322ae8c394f7668a` = V5.0 (use JBMultiTerminal, JBRulesets)\n- `0xf3cc99b11bd73a2e3b8815fb85fe0381b29987e1` = V5.1 (use JBMultiTerminal5_1, JBRulesets5_1)\n\nSee `\u002Fjb-v5-v51-contracts` for complete reference and code patterns.\n\n### Token Symbol Confusion\n\n**CRITICAL:** The `tokenSymbol` field in Bendystraw returns the **base\u002Faccounting token** (e.g., \"ETH\" or \"USDC\"), NOT the project's issued ERC20 token symbol (e.g., \"NANA\" for Bananapus).\n\nTo get the project's issued token symbol, you must query the blockchain directly:\n\n```javascript\nimport { createPublicClient, http } from 'viem'\nimport { mainnet } from 'viem\u002Fchains'\n\n\u002F\u002F JBTokens address (same on all V5 chains)\nconst JB_TOKENS = '0x4d0edd347fb1fa21589c1e109b3474924be87636'\n\nconst TOKEN_ABI = [\n  {\n    name: 'tokenOf',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'projectId', type: 'uint256' }],\n    outputs: [{ name: '', type: 'address' }],\n  },\n  {\n    name: 'symbol',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [],\n    outputs: [{ name: '', type: 'string' }],\n  },\n]\n\nasync function getProjectTokenSymbol(projectId: number, chainId: number) {\n  const client = createPublicClient({ chain: mainnet, transport: http() })\n\n  \u002F\u002F Get the token address from JBTokens\n  const tokenAddress = await client.readContract({\n    address: JB_TOKENS,\n    abi: TOKEN_ABI,\n    functionName: 'tokenOf',\n    args: [BigInt(projectId)],\n  })\n\n  \u002F\u002F Zero address means no ERC20 deployed yet (using credits only)\n  if (tokenAddress === '0x0000000000000000000000000000000000000000') {\n    return null\n  }\n\n  \u002F\u002F Get symbol from the token contract\n  return await client.readContract({\n    address: tokenAddress,\n    abi: TOKEN_ABI,\n    functionName: 'symbol',\n  })\n}\n```\n\n### GraphQL Type Inconsistencies\n\nDifferent queries expect different GraphQL types for the same conceptual values. This causes silent failures if you use the wrong type:\n\n| Query | projectId type | chainId type | version type |\n|-------|---------------|--------------|--------------|\n| `project()` | `Float!` | `Float!` | `Float!` |\n| `payEvents()` | `Int!` | `Int!` | `Int!` |\n| `participants()` | `Int!` | `Int` | - |\n| `projects()` | - | - | - |\n\n**Example of the issue:**\n\n```graphql\n# This works (project query uses Float)\nquery GetProject($projectId: Float!, $chainId: Float!, $version: Float!) {\n  project(projectId: $projectId, chainId: $chainId, version: $version) { ... }\n}\n\n# This fails silently if you pass Float instead of Int\nquery GetPayEvents($projectId: Int!, $chainId: Int!, $version: Int!) {\n  payEvents(where: { projectId: $projectId, chainId: $chainId, version: $version }) { ... }\n}\n```\n\n**Best practice:** Check the schema or use the GraphQL playground to verify expected types for each query.\n\n### SuckerGroup Cross-Chain Aggregation\n\nFor omnichain projects, use `suckerGroupId` to get aggregated data across all chains instead of querying each chain separately:\n\n```javascript\n\u002F\u002F INEFFICIENT: Query each chain separately\nconst chains = [1, 10, 8453, 42161]\nconst balances = await Promise.all(\n  chains.map(chainId => getProjectBalance(projectId, chainId))\n)\nconst totalBalance = balances.reduce((sum, b) => sum + b, 0n)\n\n\u002F\u002F EFFICIENT: Use suckerGroup for cross-chain totals\nconst project = await getProject(projectId, chainId)\nif (project.suckerGroupId) {\n  const group = await getSuckerGroup(project.suckerGroupId)\n  \u002F\u002F group.balance is already the cross-chain total\n  \u002F\u002F group.projects_rel has per-chain breakdown\n}\n```\n\nThe `suckerGroup` query returns:\n- Pre-aggregated totals (`balance`, `volume`, `tokenSupply`, etc.)\n- Per-chain breakdown via `projects_rel`\n- Consistent data without race conditions from parallel queries\n\n### ETH vs USDC Project Currency\n\n**CRITICAL:** Projects can use different base currencies (ETH or USDC). The `amount`, `balance`, and `volume` fields use different decimal precision depending on the currency:\n\n| Currency | Decimals | Code |\n|----------|----------|------|\n| ETH | 18 | 1 |\n| USDC | 6 | 2 |\n\nThe currency info is available from the `suckerGroup` or `participants` query:\n\n```graphql\nquery GetSuckerGroup($id: String!) {\n  suckerGroup(id: $id) {\n    projects_rel {\n      projectId\n      chainId\n      decimals       # 18 for ETH, 6 for USDC\n      currency       # 1 for ETH, 2 for USDC\n      balance\n    }\n  }\n}\n```\n\n**Formatting amounts correctly:**\n\n```javascript\nimport { formatUnits } from 'viem'\n\nfunction formatAmount(wei, decimals, currency) {\n  const num = parseFloat(formatUnits(BigInt(wei), decimals))\n  const symbol = currency === 2 ? 'USDC' : 'ETH'\n  \u002F\u002F USDC uses fewer decimal places for display\n  const precision = currency === 2 ? 2 : 4\n  return `${num.toFixed(precision)} ${symbol}`\n}\n\n\u002F\u002F Example: USDC project (6 decimals)\nformatAmount('1000000', 6, 2)    \u002F\u002F \"1.00 USDC\"\n\n\u002F\u002F Example: ETH project (18 decimals)\nformatAmount('1000000000000000000', 18, 1)  \u002F\u002F \"1.0000 ETH\"\n```\n\n**Common mistake:** Using `formatEther()` (assumes 18 decimals) for USDC projects will show wildly incorrect values. Always detect the currency first and use `formatUnits()` with the correct decimals.\n\n---\n\n## Best Practices\n\n1. **Use server-side proxy** - Never expose API key in frontend code\n2. **Cache responses** - Data updates every ~1 minute, cache accordingly\n3. **Query only needed fields** - Reduces payload size and latency\n4. **Use pagination** - Don't fetch thousands of records at once\n5. **Handle nulls** - Fields like `volumeUsd`, `handle` may be null\n6. **Consider freshness** - Indexer may lag 1-2 blocks behind chain\n7. **Use filters** - Narrow queries by chainId, projectId when possible\n8. **Always include version** - V4 and V5 projects with the same projectId are completely different\n9. **Use BigInt for USD values** - volumeUsd is 18 decimals; parseFloat loses precision on large values\n10. **Group by projectId + version** - Same project across chains should be grouped, but different versions should not\n11. **Check GraphQL types** - Different queries expect Float vs Int for the same fields\n12. **Use suckerGroup for cross-chain data** - More efficient than querying each chain separately\n13. **Fetch token symbols from chain** - Bendystraw's tokenSymbol is the accounting token, not the project's issued token\n14. **Detect currency before formatting** - Use `formatUnits(wei, decimals)` not `formatEther(wei)` since USDC projects use 6 decimals\n\n---\n\n## Use Cases\n\n- **Project dashboards** - Display stats, activity, holders\n- **Unified activity feeds** - Use `activityEvents` for all-in-one activity streams\n- **Governance snapshots** - Get token balances at specific timestamps\n- **Analytics** - Track trends, volumes, contributor growth via `projectMoments`\n- **Portfolio tracking** - Use `wallet` query for user positions across all projects\n- **Omnichain aggregation** - Unified view via `suckerGroup` and `suckerGroupMoments`\n- **Airdrops** - Generate recipient lists from holder data\n- **Loan dashboards** - Track RevLoans borrowing, repayments, liquidations\n- **NFT galleries** - Full tier metadata via `nftTiers` including SVGs and pricing\n- **Cross-chain tracking** - Monitor token bridging via `suckerTransactions`\n- **Treasury management** - Track payouts and allowance usage\n\n---\n\n## Related Skills\n\n- `\u002Fjb-relayr` - Execute multi-chain transactions\n- `\u002Fjb-omnichain-ui` - Build UIs with Bendystraw data\n- `\u002Fjb-query` - Direct on-chain queries via cast\u002Fethers\n- `\u002Fjb-revloans` - RevLoans protocol integration\n- `\u002Fjb-loan-queries` - Loan-specific query patterns\n- `\u002Fjb-nft-gallery-ui` - Build NFT galleries with tier data\n","# Bendystraw: Cross-Chain Juicebox Data API\n\nBendystraw 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.\n\n## API Base URLs\n\n```\nProduction: https:\u002F\u002Fbendystraw.xyz\u002F{API_KEY}\u002Fgraphql\nTestnet: https:\u002F\u002Ftestnet.bendystraw.xyz\u002F{API_KEY}\u002Fgraphql\nPlayground: https:\u002F\u002Fbendystraw.xyz (browser-based GraphQL explorer)\n```\n\n## Authentication\n\n**API key required.** Contact [@peripheralist](https:\u002F\u002Fx.com\u002Fperipheralist) on Twitter\u002FX to get one.\n\n```javascript\nconst response = await fetch(`https:\u002F\u002Fbendystraw.xyz\u002F${API_KEY}\u002Fgraphql`, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application\u002Fjson' },\n  body: JSON.stringify({\n    query: '...',\n    variables: { ... }\n  })\n});\n```\n\n**Important:** Never expose API keys in frontend code. Use a server-side proxy.\n\n---\n\n## Supported Chains\n\n| Chain | Chain ID | Network |\n|-------|----------|---------|\n| Ethereum | 1 | Mainnet |\n| Optimism | 10 | Mainnet |\n| Base | 8453 | Mainnet |\n| Arbitrum | 42161 | Mainnet |\n| Sepolia | 11155111 | Testnet |\n\n---\n\n## GraphQL Schema Reference\n\n### Project Entity\n\n```graphql\ntype Project {\n  # Identifiers\n  id: String!                    # \"{chainId}-{projectId}-{version}\"\n  projectId: Int!\n  chainId: Int!\n  version: Int!                  # Protocol version (4 or 5)\n\n  # Metadata\n  handle: String\n  name: String\n  description: String\n  logoUri: String\n  infoUri: String\n  owner: String!\n  deployer: String\n\n  # Financial\n  balance: String!               # Current balance (wei)\n  volume: String!                # Total received (wei)\n  volumeUsd: String              # USD equivalent\n  redeemVolume: String!          # Total redeemed (wei)\n  redeemVolumeUsd: String\n\n  # Tokens\n  tokenSupply: String!           # Total token supply\n  token: String                  # ERC20 address if deployed\n  tokenSymbol: String\n\n  # Activity counts\n  paymentsCount: Int!\n  redeemCount: Int!\n  contributorsCount: Int!\n  nftsMintedCount: Int!\n\n  # Trending (7-day window)\n  trendingScore: Float\n  trendingVolume: String\n  trendingPaymentsCount: Int\n\n  # Omnichain\n  suckerGroupId: String          # Linked cross-chain group\n\n  # Timestamps\n  createdAt: Int!\n  deployedAt: Int\n}\n```\n\n### SuckerGroup Entity (Omnichain Projects)\n\n```graphql\ntype SuckerGroup {\n  id: String!                    # Unique group identifier\n  projects: [String!]!           # Array of project IDs\n\n  # Aggregated totals across all chains\n  volume: String!\n  volumeUsd: String\n  balance: String!\n  tokenSupply: String!\n  paymentsCount: Int!\n  contributorsCount: Int!\n\n  # Related projects (expanded)\n  projects_rel: [Project!]!\n}\n```\n\n### Participant Entity (Token Holders)\n\n```graphql\ntype Participant {\n  id: String!                    # \"{chainId}-{projectId}-{address}\"\n  address: String!\n  projectId: Int!\n  chainId: Int!\n\n  # Balances\n  balance: String!               # Total balance (credits + ERC20)\n  creditBalance: String!         # Unclaimed credits\n  erc20Balance: String!          # Claimed ERC20 tokens\n\n  # Activity\n  volume: String!                # Total contributed\n  volumeUsd: String\n  paymentsCount: Int!\n  redeemCount: Int!\n\n  # Timestamps\n  lastPaidAt: Int\n  firstPaidAt: Int\n}\n```\n\n### PayEvent Entity\n\n```graphql\ntype PayEvent {\n  id: String!\n  projectId: Int!\n  chainId: Int!\n  rulesetId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  logIndex: Int!\n  blockNumber: Int!\n\n  # Payment details\n  from: String!                  # Payer address\n  beneficiary: String!           # Token recipient\n  amount: String!                # Payment amount (wei)\n  amountUsd: String\n  distributionFromPayAmount: String!\n\n  # Tokens\n  newlyIssuedTokenCount: String!\n  beneficiaryTokenCount: String!\n\n  # Metadata\n  memo: String\n  feeFromPayAmount: String\n}\n```\n\n### CashOutEvent Entity\n\n```graphql\ntype CashOutEvent {\n  id: String!\n  projectId: Int!\n  chainId: Int!\n  rulesetId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n\n  # Redemption details\n  holder: String!\n  beneficiary: String!\n  cashOutCount: String!          # Tokens burned\n  reclaimAmount: String!         # ETH received\n  reclaimAmountUsd: String\n  metadata: String\n}\n```\n\n### NFT Entity\n\n```graphql\ntype NFT {\n  id: String!\n  tokenId: Int!\n  projectId: Int!\n  chainId: Int!\n  hook: String!                  # 721 hook address\n\n  # Tier\n  tierId: Int!\n  tierCategory: Int\n\n  # Ownership\n  owner: String!\n  createdAt: Int!\n\n  # Metadata\n  tokenUri: String\n}\n```\n\n### ActivityEvent Entity (Unified Activity Feed)\n\nA polymorphic event type that provides a unified view of all project activity. Query this instead of individual event types when building activity feeds.\n\n```graphql\ntype ActivityEvent {\n  id: String!\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Event type discriminator\n  type: ActivityEventType!       # Determines which embedded event is populated\n\n  # Embedded events (one will be non-null based on type)\n  payEvent: PayEvent\n  cashOutTokensEvent: CashOutTokensEvent\n  mintNftEvent: MintNftEvent\n  sendPayoutsEvent: SendPayoutsEvent\n  sendPayoutToSplitEvent: SendPayoutToSplitEvent\n  borrowLoanEvent: BorrowLoanEvent\n  repayLoanEvent: RepayLoanEvent\n  liquidateLoanEvent: LiquidateLoanEvent\n  deployErc20Event: DeployErc20Event\n  burnEvent: BurnEvent\n  mintTokensEvent: MintTokensEvent\n  projectCreateEvent: ProjectCreateEvent\n  addToBalanceEvent: AddToBalanceEvent\n  useAllowanceEvent: UseAllowanceEvent\n  decorateBannyEvent: DecorateBannyEvent\n  # ... and more\n\n  # Relations\n  project: Project\n  suckerGroup: SuckerGroup\n}\n\nenum ActivityEventType {\n  payEvent\n  cashOutTokensEvent\n  mintNftEvent\n  sendPayoutsEvent\n  sendPayoutToSplitEvent\n  borrowLoanEvent\n  repayLoanEvent\n  liquidateLoanEvent\n  reallocateLoanEvent\n  deployErc20Event\n  burnEvent\n  mintTokensEvent\n  manualMintTokensEvent\n  manualBurnEvent\n  autoIssueEvent\n  projectCreateEvent\n  addToBalanceEvent\n  useAllowanceEvent\n  sendReservedTokensToSplitEvent\n  sendReservedTokensToSplitsEvent\n  decorateBannyEvent\n}\n```\n\n### Loan Entity (RevLoans)\n\nActive loan state from the RevLoans protocol.\n\n```graphql\ntype Loan {\n  id: BigInt!                    # Loan ID (NFT token ID)\n  projectId: Int!\n  chainId: Int!\n  version: Int!\n  createdAt: Int!\n\n  # Loan terms\n  borrowAmount: BigInt!          # Amount borrowed (wei)\n  collateral: BigInt!            # Collateral locked (project tokens)\n  sourceFeeAmount: BigInt!       # Fee amount\n  prepaidDuration: Int!          # Prepaid period in seconds\n  prepaidFeePercent: Int!        # Fee percent (basis points)\n\n  # Addresses\n  beneficiary: String!           # Who receives borrowed funds\n  owner: String!                 # Loan NFT owner\n  token: String!                 # Collateral token address\n  terminal: String!              # Terminal address\n\n  # Metadata\n  tokenUri: String               # Loan NFT metadata URI\n\n  # Relations\n  project: Project\n  participant: Participant\n  wallet: Wallet\n}\n```\n\n### Wallet Entity\n\nWallet-level aggregation across all project participations.\n\n```graphql\ntype Wallet {\n  address: String!               # Wallet address\n\n  # Aggregated stats\n  volume: BigInt!                # Total volume across all projects\n  volumeUsd: BigInt!             # USD equivalent (18 decimals)\n  lastPaidTimestamp: Int         # Most recent payment timestamp\n\n  # Relations\n  participants: ParticipantPage  # All project participations\n  nfts: NFTPage                  # All owned NFTs\n}\n```\n\n### NFTTier Entity\n\nNFT tier configuration with pricing and supply.\n\n```graphql\ntype NFTTier {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n  tierId: Int!\n\n  # Pricing\n  price: BigInt!                 # Price in terminal token (wei)\n\n  # Supply\n  initialSupply: Int!            # Original supply\n  remainingSupply: Int!          # Current available\n\n  # Configuration\n  allowOwnerMint: Boolean!       # Owner can mint without payment\n  cannotBeRemoved: Boolean!      # Tier is permanent\n  transfersPausable: Boolean!    # Transfers can be paused\n  votingUnits: BigInt!           # Governance weight per NFT\n  category: Int                  # Tier category\n  reserveFrequency: Int          # Reserve rate\n  reserveBeneficiary: String     # Reserve recipient\n\n  # Metadata\n  encodedIpfsUri: String         # IPFS hash (encoded)\n  resolvedUri: String            # Full resolved URI\n  metadata: JSON                 # Parsed metadata\n  svg: String                    # SVG content if available\n\n  createdAt: Int!\n\n  # Relations\n  hook: NFTHook\n  nfts: NFTPage                  # Minted NFTs in this tier\n  project: Project\n}\n```\n\n### NFTHook Entity\n\n721 hook contract configuration.\n\n```graphql\ntype NFTHook {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n  createdAt: Int!\n\n  address: String!               # Hook contract address\n  name: String                   # Collection name\n  symbol: String                 # Collection symbol\n\n  # Relations\n  nfts: NFTPage\n  nftTiers: NFTTierPage\n  project: Project\n}\n```\n\n### ProjectMoment Entity (Historical Snapshots)\n\nPoint-in-time snapshots of project state. Useful for historical charts and analytics.\n\n```graphql\ntype ProjectMoment {\n  projectId: Int!\n  chainId: Int!\n  version: Int!\n\n  # Snapshot point\n  block: Int!                    # Block number\n  timestamp: Int!                # Unix timestamp\n\n  # State at snapshot\n  volume: BigInt!\n  volumeUsd: BigInt!\n  balance: BigInt!\n  trendingScore: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### SuckerGroupMoment Entity\n\nPoint-in-time snapshots of cross-chain aggregated state.\n\n```graphql\ntype SuckerGroupMoment {\n  suckerGroupId: String!\n\n  # Snapshot point\n  block: Int!\n  timestamp: Int!\n\n  # Aggregated state\n  volume: BigInt!\n  volumeUsd: BigInt!\n  balance: BigInt!\n  tokenSupply: BigInt!\n\n  # Relations\n  suckerGroup: SuckerGroup\n}\n```\n\n### SuckerTransaction Entity (Cross-Chain Bridging)\n\nToken bridging transactions between chains via suckers.\n\n```graphql\ntype SuckerTransaction {\n  index: Int!                    # Transaction index\n  projectId: Int!\n  chainId: Int!                  # Source chain\n  version: Int!\n  suckerGroupId: String!\n  createdAt: Int!\n\n  # Bridge details\n  token: String!                 # Token being bridged\n  sucker: String!                # Source sucker address\n  peer: String!                  # Destination sucker address\n  peerChainId: Int!              # Destination chain\n  beneficiary: String!           # Recipient address\n\n  # Amounts\n  projectTokenCount: BigInt!     # Project tokens bridged\n  terminalTokenAmount: BigInt!   # Terminal tokens (if any)\n\n  # State\n  root: String                   # Merkle root\n  status: SuckerTransactionStatus!\n\n  # Relations\n  suckerGroup: SuckerGroup\n}\n\nenum SuckerTransactionStatus {\n  pending\n  completed\n  failed\n}\n```\n\n### SendPayoutsEvent Entity\n\nPayout distribution events from the terminal.\n\n```graphql\ntype SendPayoutsEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  logIndex: Int!\n\n  # Payout details\n  caller: String!                # Who triggered payout\n  from: String!                  # Source address\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n\n  # Amounts\n  amount: BigInt!                # Total payout amount\n  amountUsd: BigInt!\n  amountPaidOut: BigInt!         # Actually distributed\n  amountPaidOutUsd: BigInt!\n  netLeftoverPayoutAmount: BigInt!  # Remaining after splits\n  fee: BigInt!                   # Protocol fee\n  feeUsd: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### UseAllowanceEvent Entity\n\nSurplus allowance usage events.\n\n```graphql\ntype UseAllowanceEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n\n  # Allowance details\n  caller: String!\n  beneficiary: String!           # Who receives funds\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n\n  # Amounts\n  amount: BigInt!                # Amount used\n  amountUsd: BigInt!\n  netAmount: BigInt!             # After fees\n  netAmountUsd: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### PermissionHolder Entity\n\nOperator permissions granted to accounts.\n\n```graphql\ntype PermissionHolder {\n  chainId: Int!\n  projectId: Int!\n  version: Int!\n\n  account: String!               # Account with permissions\n  operator: String!              # Operator address\n  permissions: [Int!]!           # Permission IDs granted\n  isRevnetOperator: Boolean!     # Is a revnet operator\n\n  # Relations\n  project: Project\n}\n```\n\n### BorrowLoanEvent Entity\n\nLoan creation events from RevLoans.\n\n```graphql\ntype BorrowLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Loan details\n  borrowAmount: BigInt!          # Amount borrowed\n  collateral: BigInt!            # Collateral locked\n  sourceFeeAmount: BigInt!       # Fee paid\n  prepaidDuration: Int!          # Prepaid period (seconds)\n  prepaidFeePercent: Int!        # Fee percent (basis points)\n  beneficiary: String!           # Loan recipient\n  token: String!                 # Collateral token\n  terminal: String!              # Terminal address\n\n  # Relations\n  project: Project\n}\n```\n\n### RepayLoanEvent Entity\n\nLoan repayment events.\n\n```graphql\ntype RepayLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Repayment details\n  loanId: BigInt!                # Loan being repaid\n  paidOffLoanId: BigInt          # If fully paid off\n  repayBorrowAmount: BigInt!     # Amount repaid\n  collateralCountToReturn: BigInt!  # Collateral returned\n\n  # Relations\n  project: Project\n}\n```\n\n### LiquidateLoanEvent Entity\n\nLoan liquidation events.\n\n```graphql\ntype LiquidateLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Liquidation details\n  borrowAmount: BigInt!          # Outstanding borrow\n  collateral: BigInt!            # Collateral seized\n\n  # Relations\n  project: Project\n}\n```\n\n### ReallocateLoanEvent Entity\n\nLoan reallocation events (moving collateral between loans).\n\n```graphql\ntype ReallocateLoanEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Reallocation details\n  loanId: BigInt!                # Source loan\n  reallocatedLoanId: BigInt!     # Target loan\n  removedCollateralCount: BigInt!  # Collateral moved\n\n  # Relations\n  project: Project\n}\n```\n\n### BurnEvent Entity\n\nToken burn events (from cash outs).\n\n```graphql\ntype BurnEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Burn details\n  amount: BigInt!                # Total burned\n  creditAmount: BigInt!          # Credits burned\n  erc20Amount: BigInt!           # ERC20 burned\n\n  # Relations\n  project: Project\n}\n```\n\n### MintTokensEvent Entity\n\nToken minting events (from payments).\n\n```graphql\ntype MintTokensEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  beneficiary: String!           # Token recipient\n  beneficiaryTokenCount: BigInt! # Tokens to beneficiary\n  reservedPercent: BigInt!       # Reserved rate\n  tokenCount: BigInt!            # Total minted\n  memo: String                   # Payment memo\n\n  # Relations\n  project: Project\n}\n```\n\n### ManualMintTokensEvent Entity\n\nManual token minting by project owner.\n\n```graphql\ntype ManualMintTokensEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  beneficiary: String!\n  beneficiaryTokenCount: BigInt!\n  reservedPercent: BigInt!\n  tokenCount: BigInt!\n  memo: String\n\n  # Relations\n  project: Project\n}\n```\n\n### ManualBurnEvent Entity\n\nManual token burning.\n\n```graphql\ntype ManualBurnEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  from: String!\n\n  # Burn details\n  amount: BigInt!\n  creditAmount: BigInt!\n  erc20Amount: BigInt!\n\n  # Relations\n  project: Project\n}\n```\n\n### MintNftEvent Entity\n\nNFT minting events.\n\n```graphql\ntype MintNftEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Mint details\n  hook: String!                  # 721 hook address\n  beneficiary: String!           # NFT recipient\n  tierId: Int!                   # Tier minted\n  tokenId: BigInt!               # Token ID\n  totalAmountPaid: BigInt!       # Amount paid\n\n  # Relations\n  project: Project\n  tier: NFTTier\n  nft: NFT\n}\n```\n\n### DeployErc20Event Entity\n\nERC20 token deployment events.\n\n```graphql\ntype DeployErc20Event {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Token details\n  symbol: String!                # Token symbol\n  name: String!                  # Token name\n  token: String!                 # Token address\n\n  # Relations\n  project: Project\n}\n```\n\n### ProjectCreateEvent Entity\n\nProject creation events.\n\n```graphql\ntype ProjectCreateEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Relations\n  project: Project\n}\n```\n\n### AddToBalanceEvent Entity\n\nDirect balance addition events.\n\n```graphql\ntype AddToBalanceEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Details\n  amount: BigInt!                # Amount added\n  memo: String                   # Memo\n  metadata: String               # Additional metadata\n  returnedFees: BigInt           # Fees returned\n\n  # Relations\n  project: Project\n}\n```\n\n### SendPayoutToSplitEvent Entity\n\nIndividual split payout events.\n\n```graphql\ntype SendPayoutToSplitEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Split details\n  rulesetId: Int!\n  group: BigInt!                 # Split group\n  beneficiary: String!           # Split recipient\n  splitProjectId: Int            # If split to project\n  hook: String                   # Split hook if any\n\n  # Amounts\n  amount: BigInt!                # Gross amount\n  netAmount: BigInt!             # After fees\n  amountUsd: BigInt!\n  percent: Int!                  # Split percent\n  lockedUntil: BigInt            # Lock timestamp\n  preferAddToBalance: Boolean!   # Add to balance vs pay\n\n  # Relations\n  project: Project\n}\n```\n\n### SendReservedTokensToSplitEvent Entity\n\nReserved token distribution to individual split.\n\n```graphql\ntype SendReservedTokensToSplitEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Split details\n  rulesetId: Int!\n  groupId: BigInt!\n  beneficiary: String!\n  splitProjectId: Int\n  hook: String\n  tokenCount: BigInt!            # Tokens sent\n  percent: Int!\n  lockedUntil: BigInt\n  preferAddToBalance: Boolean!\n\n  # Relations\n  project: Project\n}\n```\n\n### SendReservedTokensToSplitsEvent Entity\n\nBatch reserved token distribution event.\n\n```graphql\ntype SendReservedTokensToSplitsEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n  suckerGroupId: String\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Distribution details\n  rulesetId: Int!\n  rulesetCycleNumber: Int!\n  owner: String!                 # Project owner\n  tokenCount: BigInt!            # Total distributed\n  leftoverAmount: BigInt!        # Remaining after splits\n\n  # Relations\n  project: Project\n}\n```\n\n### AutoIssueEvent Entity\n\nAuto-issuance events (revnet stage transitions).\n\n```graphql\ntype AutoIssueEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Issuance details\n  stageId: BigInt!               # Revnet stage\n  beneficiary: String!           # Token recipient\n  count: BigInt!                 # Tokens issued\n\n  # Relations\n  project: Project\n}\n```\n\n### StoreAutoIssuanceAmountEvent Entity\n\nAuto-issuance configuration events.\n\n```graphql\ntype StoreAutoIssuanceAmountEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n  projectId: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Config details\n  stageId: BigInt!               # Revnet stage\n  beneficiary: String!           # Configured recipient\n  count: BigInt!                 # Amount to auto-issue\n\n  # Relations\n  project: Project\n}\n```\n\n### DecorateBannyEvent Entity\n\nBanny NFT decoration events.\n\n```graphql\ntype DecorateBannyEvent {\n  id: String!\n  chainId: Int!\n  version: Int!\n\n  # Transaction\n  txHash: String!\n  timestamp: Int!\n  caller: String!\n  from: String!\n  logIndex: Int!\n\n  # Decoration details\n  bannyBodyId: BigInt!           # Banny being decorated\n  outfitIds: [BigInt!]!          # Outfit NFT IDs\n  backgroundId: BigInt           # Background NFT ID\n  tokenUri: String               # Updated token URI\n  tokenUriMetadata: JSON         # Parsed metadata\n\n  # Relations\n  bannyNft: NFT\n}\n```\n\n### CashOutTaxSnapshot Entity\n\nHistorical cash-out tax rate snapshots.\n\n```graphql\ntype CashOutTaxSnapshot {\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Snapshot period\n  start: BigInt!                 # Period start timestamp\n  duration: BigInt!              # Period duration\n  rulesetId: BigInt!             # Ruleset ID\n\n  # Tax rate\n  cashOutTax: Int!               # Tax rate (basis points)\n}\n```\n\n### ParticipantSnapshot Entity (GraphQL)\n\nHistorical participant balance snapshots via GraphQL (alternative to REST endpoint).\n\n```graphql\ntype ParticipantSnapshot {\n  chainId: Int!\n  projectId: Int!\n  suckerGroupId: String\n  version: Int!\n\n  # Snapshot point\n  block: Int!\n  timestamp: Int!\n\n  # Participant\n  address: String!\n\n  # Balances at snapshot\n  balance: BigInt!\n  creditBalance: BigInt!\n  erc20Balance: BigInt!\n  volume: BigInt!\n  volumeUsd: BigInt!\n}\n```\n\n---\n\n## Critical Concepts\n\n### Project Identity\n\n**A Juicebox project is uniquely identified by three fields: `projectId + chainId + version`.**\n\nThis is crucial because:\n- **V4 and V5 are completely different protocols.** Project #64 on Ethereum V4 is NOT the same project as Project #64 on Ethereum V5.\n- The same projectId can exist on multiple chains (via suckers\u002Fomnichain), but those ARE the same project.\n- Always include `version` when querying or displaying projects.\n\n```javascript\n\u002F\u002F WRONG: Groups V4 and V5 together\nconst groupKey = `${project.projectId}-${project.chainId}`;\n\n\u002F\u002F CORRECT: Keeps V4 and V5 separate\nconst groupKey = `${project.projectId}-v${project.version}`;\n```\n\n### Multi-Chain Grouping\n\nWhen displaying \"top projects\" or aggregating stats:\n- **Same projectId + version across chains** → Group together (same project via suckers)\n- **Same projectId, different version** → Keep separate (completely different projects)\n\n```javascript\n\u002F\u002F Group projects by projectId + version (V4 and V5 are different projects!)\nconst grouped = new Map();\n\nfor (const project of projects) {\n  const groupKey = `${project.projectId}-v${project.version || 4}`;\n  const existing = grouped.get(groupKey);\n\n  if (existing) {\n    \u002F\u002F Add chain to existing group\n    if (!existing.chainIds.includes(project.chainId)) {\n      existing.chainIds.push(project.chainId);\n    }\n    \u002F\u002F Sum volumes\n    existing.totalVolumeUsd += parseFloat(project.volumeUsd || '0');\n  } else {\n    grouped.set(groupKey, {\n      ...project,\n      chainIds: [project.chainId],\n      totalVolumeUsd: parseFloat(project.volumeUsd || '0')\n    });\n  }\n}\n```\n\n### USD Value Formatting\n\nThe `volumeUsd`, `amountUsd`, and similar fields use **18 decimal format** (like wei). You must convert properly:\n\n```javascript\nfunction formatVolumeUsd(volumeUsd) {\n  if (!volumeUsd || volumeUsd === '0') return '$0';\n\n  try {\n    \u002F\u002F volumeUsd comes in 18 decimal format\n    \u002F\u002F Use BigInt to avoid precision loss on large numbers\n    const raw = BigInt(volumeUsd.split('.')[0]);\n    const usd = Number(raw \u002F BigInt(1e12)) \u002F 1e6; \u002F\u002F Divide in steps\n\n    if (usd >= 1_000_000) return `$${(usd \u002F 1_000_000).toFixed(1)}M`;\n    if (usd >= 1_000) return `$${(usd \u002F 1_000).toFixed(1)}k`;\n    if (usd >= 1) return `$${usd.toFixed(0)}`;\n    return `$${usd.toFixed(2)}`;\n  } catch {\n    return '$0';\n  }\n}\n```\n\n**Warning:** Do NOT use `parseFloat()` directly on volumeUsd for large values—JavaScript loses precision beyond ~15 digits.\n\n### Filtering by Version\n\nWhen querying projects, filter by version to avoid mixing V4 and V5 data:\n\n```graphql\n# Get only V5 projects\nquery V5Projects($limit: Int!) {\n  projects(\n    where: { version: 5 }\n    orderBy: \"volumeUsd\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      projectId\n      chainId\n      version\n      name\n      volumeUsd\n    }\n  }\n}\n```\n\nTo display both versions, query them separately and handle grouping in your application.\n\n---\n\n## Query Examples\n\n### Get Single Project\n\n```graphql\nquery GetProject($projectId: Int!, $chainId: Int!) {\n  project(projectId: $projectId, chainId: $chainId) {\n    id\n    name\n    handle\n    owner\n    balance\n    volume\n    volumeUsd\n    tokenSupply\n    paymentsCount\n    contributorsCount\n    suckerGroupId\n  }\n}\n```\n\n### Get Participant (Token Holder)\n\n```graphql\nquery GetParticipant($projectId: Int!, $chainId: Int!, $address: String!) {\n  participant(projectId: $projectId, chainId: $chainId, address: $address) {\n    balance\n    creditBalance\n    erc20Balance\n    volume\n    volumeUsd\n    paymentsCount\n  }\n}\n```\n\n### Get Sucker Group (Omnichain Totals)\n\n```graphql\nquery GetSuckerGroup($id: String!) {\n  suckerGroup(id: $id) {\n    id\n    volume\n    volumeUsd\n    balance\n    tokenSupply\n    paymentsCount\n    contributorsCount\n    projects_rel {\n      projectId\n      chainId\n      name\n      balance\n      volume\n    }\n  }\n}\n```\n\n### List Projects\n\n```graphql\nquery ListProjects($chainId: Int, $version: Int, $limit: Int!, $offset: Int!) {\n  projects(\n    where: { chainId: $chainId, version: $version }\n    orderBy: \"volumeUsd\"\n    orderDirection: \"desc\"\n    limit: $limit\n    offset: $offset\n  ) {\n    items {\n      projectId\n      chainId\n      version\n      name\n      handle\n      volumeUsd\n      balance\n      paymentsCount\n    }\n    totalCount\n  }\n}\n```\n\n### List Recent Payments\n\n```graphql\nquery ListPayments($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  payEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      from\n      beneficiary\n      amount\n      amountUsd\n      memo\n      newlyIssuedTokenCount\n    }\n  }\n}\n```\n\n### List Top Token Holders\n\n```graphql\nquery ListParticipants($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  participants(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"balance\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      address\n      balance\n      creditBalance\n      erc20Balance\n      volume\n      paymentsCount\n    }\n    totalCount\n  }\n}\n```\n\n### Get Trending Projects\n\n```graphql\nquery TrendingProjects($limit: Int!) {\n  projects(\n    orderBy: \"trendingScore\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      projectId\n      chainId\n      name\n      handle\n      trendingScore\n      trendingVolume\n      trendingPaymentsCount\n    }\n  }\n}\n```\n\n### List Cash Out Events\n\n```graphql\nquery ListCashOuts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  cashOutEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      holder\n      beneficiary\n      cashOutCount\n      reclaimAmount\n      reclaimAmountUsd\n    }\n  }\n}\n```\n\n### List NFTs for Project\n\n```graphql\nquery ListNFTs($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  nfts(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      tokenId\n      tierId\n      tierCategory\n      owner\n      createdAt\n      tokenUri\n    }\n  }\n}\n```\n\n### Get Unified Activity Feed\n\nThe most powerful query for building activity feeds. Returns all event types in a single query.\n\n```graphql\nquery GetActivityFeed($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  activityEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      id\n      timestamp\n      txHash\n      from\n      type                        # Discriminator for event type\n\n      # Include fields from each possible event type\n      payEvent {\n        amount\n        amountUsd\n        beneficiary\n        memo\n      }\n      cashOutTokensEvent {\n        cashOutCount\n        reclaimAmount\n        holder\n      }\n      mintNftEvent {\n        tierId\n        tokenId\n      }\n      sendPayoutsEvent {\n        amount\n        amountPaidOut\n        fee\n      }\n      borrowLoanEvent {\n        borrowAmount\n        collateral\n      }\n    }\n  }\n}\n```\n\n### Get Omnichain Activity Feed\n\nQuery activity across all chains for a sucker group.\n\n```graphql\nquery GetOmnichainActivity($suckerGroupId: String!, $limit: Int!) {\n  activityEvents(\n    where: { suckerGroupId: $suckerGroupId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      chainId\n      timestamp\n      type\n      txHash\n      from\n      payEvent { amount, memo }\n      cashOutTokensEvent { cashOutCount, reclaimAmount }\n    }\n  }\n}\n```\n\n### List Active Loans\n\n```graphql\nquery ListLoans($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  loans(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      id\n      borrowAmount\n      collateral\n      prepaidDuration\n      prepaidFeePercent\n      owner\n      beneficiary\n      createdAt\n    }\n    totalCount\n  }\n}\n```\n\n### Get Loan by ID\n\n```graphql\nquery GetLoan($id: BigInt!) {\n  loan(id: $id) {\n    id\n    projectId\n    chainId\n    borrowAmount\n    collateral\n    sourceFeeAmount\n    prepaidDuration\n    prepaidFeePercent\n    beneficiary\n    owner\n    token\n    terminal\n    tokenUri\n    createdAt\n  }\n}\n```\n\n### Get Wallet Portfolio\n\n```graphql\nquery GetWallet($address: String!) {\n  wallet(address: $address) {\n    address\n    volume\n    volumeUsd\n    lastPaidTimestamp\n    participants(limit: 100) {\n      items {\n        projectId\n        chainId\n        balance\n        volume\n      }\n    }\n    nfts(limit: 50) {\n      items {\n        projectId\n        chainId\n        tokenId\n        tierId\n      }\n    }\n  }\n}\n```\n\n### List NFT Tiers\n\n```graphql\nquery ListNFTTiers($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  nftTiers(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"tierId\"\n    orderDirection: \"asc\"\n    limit: $limit\n  ) {\n    items {\n      tierId\n      price\n      initialSupply\n      remainingSupply\n      category\n      votingUnits\n      resolvedUri\n      metadata\n      svg\n    }\n  }\n}\n```\n\n### Get NFT Hook Details\n\n```graphql\nquery GetNFTHook($projectId: Int!, $chainId: Int!) {\n  nftHooks(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: 1\n  ) {\n    items {\n      address\n      name\n      symbol\n      nftTiers(limit: 100) {\n        items {\n          tierId\n          price\n          remainingSupply\n        }\n      }\n    }\n  }\n}\n```\n\n### Get Historical Project Snapshots\n\n```graphql\nquery GetProjectHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  projectMoments(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      block\n      timestamp\n      volume\n      volumeUsd\n      balance\n      trendingScore\n    }\n  }\n}\n```\n\n### Get Cross-Chain Bridge Transactions\n\n```graphql\nquery GetSuckerTransactions($suckerGroupId: String!, $limit: Int!) {\n  suckerTransactions(\n    where: { suckerGroupId: $suckerGroupId }\n    orderBy: \"createdAt\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      index\n      chainId\n      peerChainId\n      beneficiary\n      projectTokenCount\n      terminalTokenAmount\n      status\n      createdAt\n    }\n  }\n}\n```\n\n### List Payout Events\n\n```graphql\nquery ListPayouts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendPayoutsEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      caller\n      rulesetCycleNumber\n      amount\n      amountPaidOut\n      fee\n      netLeftoverPayoutAmount\n    }\n  }\n}\n```\n\n### List Allowance Usage\n\n```graphql\nquery ListAllowanceUsage($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  useAllowanceEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      caller\n      beneficiary\n      amount\n      netAmount\n      rulesetCycleNumber\n    }\n  }\n}\n```\n\n### List Loan Events (Borrow\u002FRepay\u002FLiquidate)\n\n```graphql\nquery ListBorrowEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  borrowLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      borrowAmount\n      collateral\n      prepaidDuration\n      beneficiary\n    }\n  }\n}\n\nquery ListRepayEvents($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  repayLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      loanId\n      repayBorrowAmount\n      collateralCountToReturn\n    }\n  }\n}\n\nquery ListLiquidations($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  liquidateLoanEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      borrowAmount\n      collateral\n    }\n  }\n}\n```\n\n### List Token Burns\n\n```graphql\nquery ListBurns($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  burnEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      from\n      amount\n      creditAmount\n      erc20Amount\n    }\n  }\n}\n```\n\n### List NFT Mints\n\n```graphql\nquery ListNFTMints($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  mintNftEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      beneficiary\n      tierId\n      tokenId\n      totalAmountPaid\n    }\n  }\n}\n```\n\n### Get Permission Holders\n\n```graphql\nquery ListPermissionHolders($projectId: Int!, $chainId: Int!) {\n  permissionHolders(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: 100\n  ) {\n    items {\n      account\n      operator\n      permissions\n      isRevnetOperator\n    }\n  }\n}\n```\n\n### List Project Creations\n\n```graphql\nquery ListProjectCreations($chainId: Int!, $limit: Int!) {\n  projectCreateEvents(\n    where: { chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      projectId\n      caller\n      project {\n        name\n        handle\n        owner\n      }\n    }\n  }\n}\n```\n\n### List ERC20 Deployments\n\n```graphql\nquery ListERC20Deployments($chainId: Int!, $limit: Int!) {\n  deployErc20Events(\n    where: { chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      projectId\n      name\n      symbol\n      token\n    }\n  }\n}\n```\n\n### Get Cash Out Tax History\n\n```graphql\nquery GetCashOutTaxHistory($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  cashOutTaxSnapshots(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"start\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      start\n      duration\n      rulesetId\n      cashOutTax\n    }\n  }\n}\n```\n\n### Get Participant History (GraphQL Snapshots)\n\n```graphql\nquery GetParticipantHistory(\n  $projectId: Int!,\n  $chainId: Int!,\n  $address: String!,\n  $limit: Int!\n) {\n  participantSnapshots(\n    where: { projectId: $projectId, chainId: $chainId, address: $address }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      block\n      timestamp\n      balance\n      creditBalance\n      erc20Balance\n      volume\n      volumeUsd\n    }\n  }\n}\n```\n\n### List Reserved Token Distributions\n\n```graphql\nquery ListReservedDistributions($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendReservedTokensToSplitsEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      rulesetCycleNumber\n      tokenCount\n      leftoverAmount\n      owner\n    }\n  }\n}\n```\n\n### List Split Payouts\n\n```graphql\nquery ListSplitPayouts($projectId: Int!, $chainId: Int!, $limit: Int!) {\n  sendPayoutToSplitEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    orderBy: \"timestamp\"\n    orderDirection: \"desc\"\n    limit: $limit\n  ) {\n    items {\n      timestamp\n      txHash\n      beneficiary\n      splitProjectId\n      amount\n      netAmount\n      percent\n    }\n  }\n}\n```\n\n---\n\n## Filtering\n\nThe `where` clause supports these operators:\n\n```graphql\nwhere: {\n  # Exact match\n  projectId: 1\n  chainId: 1\n\n  # Multiple values (OR)\n  chainId_in: [1, 10, 8453]\n\n  # Comparison operators\n  balance_gt: \"1000000000000000000\"\n  balance_gte: \"1000000000000000000\"\n  balance_lt: \"100000000000000000000\"\n  balance_lte: \"100000000000000000000\"\n  timestamp_gte: 1704067200\n\n  # Text search\n  name_contains: \"dao\"\n  handle_starts_with: \"jb\"\n}\n```\n\n---\n\n## Sorting\n\n```graphql\norderBy: \"volume\"           # Field to sort by\norderDirection: \"desc\"      # \"asc\" or \"desc\"\n```\n\n**Sortable Fields by Entity:**\n\n| Entity | Sortable Fields |\n|--------|-----------------|\n| Project | `volume`, `balance`, `tokenSupply`, `paymentsCount`, `createdAt`, `trendingScore` |\n| Participant | `balance`, `volume`, `paymentsCount` |\n| PayEvent | `timestamp`, `amount` |\n| CashOutEvent | `timestamp`, `reclaimAmount` |\n| NFT | `createdAt`, `tokenId` |\n| ActivityEvent | `timestamp` |\n| Loan | `createdAt`, `borrowAmount`, `collateral` |\n| Wallet | `volume`, `volumeUsd`, `lastPaidTimestamp` |\n| NFTTier | `tierId`, `price`, `createdAt` |\n| ProjectMoment | `timestamp`, `block` |\n| SuckerTransaction | `createdAt` |\n| SendPayoutsEvent | `timestamp`, `amount` |\n| UseAllowanceEvent | `timestamp`, `amount` |\n| BorrowLoanEvent | `timestamp`, `borrowAmount` |\n| RepayLoanEvent | `timestamp`, `repayBorrowAmount` |\n| LiquidateLoanEvent | `timestamp` |\n| BurnEvent | `timestamp`, `amount` |\n| MintTokensEvent | `timestamp`, `tokenCount` |\n| MintNftEvent | `timestamp`, `tokenId` |\n| DeployErc20Event | `timestamp` |\n| ProjectCreateEvent | `timestamp` |\n| SendPayoutToSplitEvent | `timestamp`, `amount` |\n| CashOutTaxSnapshot | `start` |\n| ParticipantSnapshot | `timestamp`, `block` |\n\n---\n\n## Pagination\n\nAll list queries support pagination:\n\n```graphql\nquery PaginatedPayments(\n  $projectId: Int!,\n  $chainId: Int!,\n  $limit: Int!,\n  $offset: Int!\n) {\n  payEvents(\n    where: { projectId: $projectId, chainId: $chainId }\n    limit: $limit\n    offset: $offset\n  ) {\n    items { ... }\n    totalCount\n  }\n}\n```\n\n**Parameters:**\n- `limit`: Max items to return (default: 100, max: 1000)\n- `offset`: Number of items to skip\n\n---\n\n## Special Endpoints\n\n### Participant Snapshots\n\nGet historical participant balances at a specific timestamp. Useful for governance snapshots and airdrops.\n\n```\nPOST https:\u002F\u002Fbendystraw.xyz\u002F{API_KEY}\u002Fparticipants\n```\n\n**Request:**\n```json\n{\n  \"suckerGroupId\": \"0x...\",\n  \"timestamp\": 1704067200\n}\n```\n\n**Response:**\n```json\n{\n  \"participants\": [\n    {\n      \"address\": \"0x...\",\n      \"balance\": \"1000000000000000000000\",\n      \"chains\": {\n        \"1\": \"600000000000000000000\",\n        \"10\": \"400000000000000000000\"\n      }\n    }\n  ]\n}\n```\n\n---\n\n## Complete JavaScript Example\n\n```javascript\nconst BENDYSTRAW_URL = 'https:\u002F\u002Fbendystraw.xyz';\nconst API_KEY = process.env.BENDYSTRAW_API_KEY;\n\n\u002F**\n * Execute GraphQL query\n *\u002F\nasync function query(graphql, variables = {}) {\n  const response = await fetch(`${BENDYSTRAW_URL}\u002F${API_KEY}\u002Fgraphql`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ query: graphql, variables })\n  });\n\n  const result = await response.json();\n\n  if (result.errors) {\n    throw new Error(result.errors[0].message);\n  }\n\n  return result.data;\n}\n\n\u002F**\n * Get project stats\n *\u002F\nasync function getProject(projectId, chainId) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!) {\n      project(projectId: $projectId, chainId: $chainId) {\n        name\n        handle\n        owner\n        balance\n        volume\n        volumeUsd\n        tokenSupply\n        paymentsCount\n        contributorsCount\n        suckerGroupId\n      }\n    }\n  `, { projectId, chainId });\n\n  return data.project;\n}\n\n\u002F**\n * Get omnichain totals for sucker group\n *\u002F\nasync function getOmnichainStats(suckerGroupId) {\n  const data = await query(`\n    query($id: String!) {\n      suckerGroup(id: $id) {\n        volume\n        volumeUsd\n        balance\n        tokenSupply\n        contributorsCount\n        projects_rel {\n          chainId\n          name\n          balance\n          volume\n        }\n      }\n    }\n  `, { id: suckerGroupId });\n\n  return data.suckerGroup;\n}\n\n\u002F**\n * Get recent payments\n *\u002F\nasync function getRecentPayments(projectId, chainId, limit = 20) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      payEvents(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          timestamp\n          from\n          beneficiary\n          amount\n          amountUsd\n          memo\n          newlyIssuedTokenCount\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.payEvents.items;\n}\n\n\u002F**\n * Get top token holders\n *\u002F\nasync function getTopHolders(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      participants(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"balance\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          address\n          balance\n          creditBalance\n          erc20Balance\n          volume\n        }\n        totalCount\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return {\n    holders: data.participants.items,\n    total: data.participants.totalCount\n  };\n}\n\n\u002F**\n * Get participant balance\n *\u002F\nasync function getParticipant(projectId, chainId, address) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $address: String!) {\n      participant(projectId: $projectId, chainId: $chainId, address: $address) {\n        balance\n        creditBalance\n        erc20Balance\n        volume\n        volumeUsd\n        paymentsCount\n      }\n    }\n  `, { projectId, chainId, address });\n\n  return data.participant;\n}\n\n\u002F**\n * Get trending projects\n *\u002F\nasync function getTrendingProjects(chainId = null, limit = 10) {\n  const where = chainId ? { chainId } : {};\n\n  const data = await query(`\n    query($where: ProjectWhereInput, $limit: Int!) {\n      projects(\n        where: $where\n        orderBy: \"trendingScore\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          projectId\n          chainId\n          name\n          handle\n          trendingScore\n          trendingVolume\n          trendingPaymentsCount\n        }\n      }\n    }\n  `, { where, limit });\n\n  return data.projects.items;\n}\n\n\u002F**\n * Get historical snapshot for governance\n *\u002F\nasync function getSnapshot(suckerGroupId, timestamp) {\n  const response = await fetch(`${BENDYSTRAW_URL}\u002F${API_KEY}\u002Fparticipants`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ suckerGroupId, timestamp })\n  });\n\n  return await response.json();\n}\n\n\u002F**\n * Get unified activity feed (all event types)\n *\u002F\nasync function getActivityFeed(projectId, chainId, limit = 50) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      activityEvents(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          id\n          timestamp\n          txHash\n          from\n          type\n          payEvent { amount, amountUsd, beneficiary, memo }\n          cashOutTokensEvent { cashOutCount, reclaimAmount, holder }\n          mintNftEvent { tierId, tokenId }\n          sendPayoutsEvent { amount, amountPaidOut, fee }\n          borrowLoanEvent { borrowAmount, collateral }\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.activityEvents.items;\n}\n\n\u002F**\n * Get active loans for a project\n *\u002F\nasync function getLoans(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      loans(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"createdAt\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          id\n          borrowAmount\n          collateral\n          prepaidDuration\n          prepaidFeePercent\n          owner\n          beneficiary\n          createdAt\n        }\n        totalCount\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return {\n    loans: data.loans.items,\n    total: data.loans.totalCount\n  };\n}\n\n\u002F**\n * Get wallet portfolio across all projects\n *\u002F\nasync function getWalletPortfolio(address) {\n  const data = await query(`\n    query($address: String!) {\n      wallet(address: $address) {\n        address\n        volume\n        volumeUsd\n        lastPaidTimestamp\n        participants(limit: 100) {\n          items {\n            projectId\n            chainId\n            balance\n            volume\n            project { name, handle }\n          }\n        }\n      }\n    }\n  `, { address });\n\n  return data.wallet;\n}\n\n\u002F**\n * Get NFT tier details with metadata\n *\u002F\nasync function getNFTTiers(projectId, chainId) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!) {\n      nftTiers(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"tierId\"\n        orderDirection: \"asc\"\n        limit: 100\n      ) {\n        items {\n          tierId\n          price\n          initialSupply\n          remainingSupply\n          category\n          votingUnits\n          resolvedUri\n          metadata\n          svg\n        }\n      }\n    }\n  `, { projectId, chainId });\n\n  return data.nftTiers.items;\n}\n\n\u002F**\n * Get historical project snapshots for charting\n *\u002F\nasync function getProjectHistory(projectId, chainId, limit = 100) {\n  const data = await query(`\n    query($projectId: Int!, $chainId: Int!, $limit: Int!) {\n      projectMoments(\n        where: { projectId: $projectId, chainId: $chainId }\n        orderBy: \"timestamp\"\n        orderDirection: \"desc\"\n        limit: $limit\n      ) {\n        items {\n          block\n          timestamp\n          volume\n          volumeUsd\n          balance\n          trendingScore\n        }\n      }\n    }\n  `, { projectId, chainId, limit });\n\n  return data.projectMoments.items;\n}\n\n\u002F\u002F Example usage\nasync function main() {\n  \u002F\u002F Get project on Ethereum mainnet\n  const project = await getProject(1, 1);\n  console.log(`${project.name}: ${project.balance} wei balance`);\n\n  \u002F\u002F If omnichain, get aggregated stats\n  if (project.suckerGroupId) {\n    const omni = await getOmnichainStats(project.suckerGroupId);\n    console.log(`Omnichain total: ${omni.volume} wei volume across ${omni.projects_rel.length} chains`);\n  }\n\n  \u002F\u002F Get recent activity\n  const payments = await getRecentPayments(1, 1, 5);\n  console.log(`Last ${payments.length} payments:`);\n  payments.forEach(p => {\n    console.log(`  ${p.from.slice(0,8)}... paid ${p.amount} wei`);\n  });\n\n  \u002F\u002F Get top holders\n  const { holders, total } = await getTopHolders(1, 1, 10);\n  console.log(`Top 10 of ${total} holders:`);\n  holders.forEach((h, i) => {\n    console.log(`  ${i + 1}. ${h.address.slice(0,8)}... - ${h.balance} tokens`);\n  });\n}\n\nmain().catch(console.error);\n```\n\n---\n\n## BendystrawClient Class\n\n```javascript\nclass BendystrawClient {\n  constructor(apiKey, isTestnet = false) {\n    this.baseUrl = isTestnet\n      ? 'https:\u002F\u002Ftestnet.bendystraw.xyz'\n      : 'https:\u002F\u002Fbendystraw.xyz';\n    this.apiKey = apiKey;\n  }\n\n  async query(graphql, variables = {}) {\n    const response = await fetch(`${this.baseUrl}\u002F${this.apiKey}\u002Fgraphql`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify({ query: graphql, variables })\n    });\n\n    if (!response.ok) {\n      throw new Error(`Bendystraw request failed: ${response.statusText}`);\n    }\n\n    const result = await response.json();\n    if (result.errors) {\n      throw new Error(result.errors[0].message);\n    }\n\n    return result.data;\n  }\n\n  \u002F\u002F Convenience methods\n  async getProject(projectId, chainId) { ... }\n  async getSuckerGroup(id) { ... }\n  async getPayments(projectId, chainId, limit) { ... }\n  async getParticipants(projectId, chainId, limit) { ... }\n  async getSnapshot(suckerGroupId, timestamp) { ... }\n}\n```\n\n---\n\n## Server-Side Proxy\n\nSince the API key must be kept secret, use a server-side proxy:\n\n### Next.js API Route\n\n```typescript\n\u002F\u002F pages\u002Fapi\u002Fbendystraw.ts\nexport default async function handler(req, res) {\n  if (req.method !== 'POST') {\n    return res.status(405).json({ error: 'Method not allowed' });\n  }\n\n  const response = await fetch(\n    `https:\u002F\u002Fbendystraw.xyz\u002F${process.env.BENDYSTRAW_API_KEY}\u002Fgraphql`,\n    {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(req.body)\n    }\n  );\n\n  const data = await response.json();\n  res.json(data);\n}\n```\n\n### Express Middleware\n\n```javascript\napp.post('\u002Fapi\u002Fbendystraw', async (req, res) => {\n  const response = await fetch(\n    `https:\u002F\u002Fbendystraw.xyz\u002F${process.env.BENDYSTRAW_API_KEY}\u002Fgraphql`,\n    {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(req.body)\n    }\n  );\n\n  res.json(await response.json());\n});\n```\n\n---\n\n## Error Handling\n\n| Error | Cause | Solution |\n|-------|-------|----------|\n| `Unauthorized` | Invalid API key | Verify API key is correct |\n| `Rate limited` | Too many requests | Add backoff\u002Fretry logic |\n| `Invalid query` | GraphQL syntax error | Check query structure |\n| `Not found` | Entity doesn't exist | Verify projectId\u002FchainId |\n| `Timeout` | Query too complex | Reduce limit, add filters |\n\n---\n\n## Gotchas & Common Pitfalls\n\n### V4 vs V5 Protocol Confusion\n\n**CRITICAL:** V4 and V5 are completely different protocols with different contract addresses.\nProject #1 on V4 is NOT the same as Project #1 on V5. Never mix V4 and V5 addresses.\n\n### V5.0 vs V5.1 Contract Confusion\n\n**CRITICAL:** Within V5, there are two contract versions: V5.0 and V5.1. Contracts that have\nboth versions MUST NOT be mixed. A project using JBController5_1 MUST use JBMultiTerminal5_1.\n\n**Shared contracts (work with both V5.0 and V5.1):**\n\n| Contract | Address |\n|----------|---------|\n| JBProjects | `0x885f707efa18d2cb12f05a3a8eba6b4b26c8c1d4` |\n| JBTokens | `0x4d0edd347fb1fa21589c1e109b3474924be87636` |\n| JBDirectory | `0x0061e516886a0540f63157f112c0588ee0651dcf` |\n| JBSplits | `0x7160a322fea44945a6ef9adfd65c322258df3c5e` |\n\n**V5.0 contracts (for revnets and older projects):**\n\n| Contract | Address |\n|----------|---------|\n| JBController | `0x27da30646502e2f642be5281322ae8c394f7668a` |\n| JBMultiTerminal | `0x2db6d704058e552defe415753465df8df0361846` |\n| JBRulesets | `0x6292281d69c3593fcf6ea074e5797341476ab428` |\n| REVDeployer | `0x2ca27bde7e7d33e353b44c27acfcf6c78dde251d` |\n| JB721TiersHookDeployer | `0x7e4f7bfeab74bbae3eb12a62f2298bf2be16fc93` |\n\n**V5.1 contracts (for new projects):**\n\n| Contract | Address |\n|----------|---------|\n| JBController5_1 | `0xf3cc99b11bd73a2e3b8815fb85fe0381b29987e1` |\n| JBMultiTerminal5_1 | `0x52869db3d61dde1e391967f2ce5039ad0ecd371c` |\n| JBRulesets5_1 | `0xd4257005ca8d27bbe11f356453b0e4692414b056` |\n| JBOmnichainDeployer5_1 | `0x587bf86677ec0d1b766d9ba0d7ac2a51c6c2fc71` |\n| JB721TiersHookDeployer5_1 | `0x7e6e7db5081c59f2df3c83b54eb0c4d029e9898e` |\n\n**Determining project version:** Query `JBDirectory.controllerOf(projectId)` and compare:\n- `0x27da30646502e2f642be5281322ae8c394f7668a` = V5.0 (use JBMultiTerminal, JBRulesets)\n- `0xf3cc99b11bd73a2e3b8815fb85fe0381b29987e1` = V5.1 (use JBMultiTerminal5_1, JBRulesets5_1)\n\nSee `\u002Fjb-v5-v51-contracts` for complete reference and code patterns.\n\n### Token Symbol Confusion\n\n**CRITICAL:** The `tokenSymbol` field in Bendystraw returns the **base\u002Faccounting token** (e.g., \"ETH\" or \"USDC\"), NOT the project's issued ERC20 token symbol (e.g., \"NANA\" for Bananapus).\n\nTo get the project's issued token symbol, you must query the blockchain directly:\n\n```javascript\nimport { createPublicClient, http } from 'viem'\nimport { mainnet } from 'viem\u002Fchains'\n\n\u002F\u002F JBTokens address (same on all V5 chains)\nconst JB_TOKENS = '0x4d0edd347fb1fa21589c1e109b3474924be87636'\n\nconst TOKEN_ABI = [\n  {\n    name: 'tokenOf',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [{ name: 'projectId', type: 'uint256' }],\n    outputs: [{ name: '', type: 'address' }],\n  },\n  {\n    name: 'symbol',\n    type: 'function',\n    stateMutability: 'view',\n    inputs: [],\n    outputs: [{ name: '', type: 'string' }],\n  },\n]\n\nasync function getProjectTokenSymbol(projectId: number, chainId: number) {\n  const client = createPublicClient({ chain: mainnet, transport: http() })\n\n  \u002F\u002F Get the token address from JBTokens\n  const tokenAddress = await client.readContract({\n    address: JB_TOKENS,\n    abi: TOKEN_ABI,\n    functionName: 'tokenOf',\n    args: [BigInt(projectId)],\n  })\n\n  \u002F\u002F Zero address means no ERC20 deployed yet (using credits only)\n  if (tokenAddress === '0x0000000000000000000000000000000000000000') {\n    return null\n  }\n\n  \u002F\u002F Get symbol from the token contract\n  return await client.readContract({\n    address: tokenAddress,\n    abi: TOKEN_ABI,\n    functionName: 'symbol',\n  })\n}\n```\n\n### GraphQL Type Inconsistencies\n\nDifferent queries expect different GraphQL types for the same conceptual values. This causes silent failures if you use the wrong type:\n\n| Query | projectId type | chainId type | version type |\n|-------|---------------|--------------|--------------|\n| `project()` | `Float!` | `Float!` | `Float!` |\n| `payEvents()` | `Int!` | `Int!` | `Int!` |\n| `participants()` | `Int!` | `Int` | - |\n| `projects()` | - | - | - |\n\n**Example of the issue:**\n\n```graphql\n# This works (project query uses Float)\nquery GetProject($projectId: Float!, $chainId: Float!, $version: Float!) {\n  project(projectId: $projectId, chainId: $chainId, version: $version) { ... }\n}\n\n# This fails silently if you pass Float instead of Int\nquery GetPayEvents($projectId: Int!, $chainId: Int!, $version: Int!) {\n  payEvents(where: { projectId: $projectId, chainId: $chainId, version: $version }) { ... }\n}\n```\n\n**Best practice:** Check the schema or use the GraphQL playground to verify expected types for each query.\n\n### SuckerGroup Cross-Chain Aggregation\n\nFor omnichain projects, use `suckerGroupId` to get aggregated data across all chains instead of querying each chain separately:\n\n```javascript\n\u002F\u002F INEFFICIENT: Query each chain separately\nconst chains = [1, 10, 8453, 42161]\nconst balances = await Promise.all(\n  chains.map(chainId => getProjectBalance(projectId, chainId))\n)\nconst totalBalance = balances.reduce((sum, b) => sum + b, 0n)\n\n\u002F\u002F EFFICIENT: Use suckerGroup for cross-chain totals\nconst project = await getProject(projectId, chainId)\nif (project.suckerGroupId) {\n  const group = await getSuckerGroup(project.suckerGroupId)\n  \u002F\u002F group.balance is already the cross-chain total\n  \u002F\u002F group.projects_rel has per-chain breakdown\n}\n```\n\nThe `suckerGroup` query returns:\n- Pre-aggregated totals (`balance`, `volume`, `tokenSupply`, etc.)\n- Per-chain breakdown via `projects_rel`\n- Consistent data without race conditions from parallel queries\n\n### ETH vs USDC Project Currency\n\n**CRITICAL:** Projects can use different base currencies (ETH or USDC). The `amount`, `balance`, and `volume` fields use different decimal precision depending on the currency:\n\n| Currency | Decimals | Code |\n|----------|----------|------|\n| ETH | 18 | 1 |\n| USDC | 6 | 2 |\n\nThe currency info is available from the `suckerGroup` or `participants` query:\n\n```graphql\nquery GetSuckerGroup($id: String!) {\n  suckerGroup(id: $id) {\n    projects_rel {\n      projectId\n      chainId\n      decimals       # 18 for ETH, 6 for USDC\n      currency       # 1 for ETH, 2 for USDC\n      balance\n    }\n  }\n}\n```\n\n**Formatting amounts correctly:**\n\n```javascript\nimport { formatUnits } from 'viem'\n\nfunction formatAmount(wei, decimals, currency) {\n  const num = parseFloat(formatUnits(BigInt(wei), decimals))\n  const symbol = currency === 2 ? 'USDC' : 'ETH'\n  \u002F\u002F USDC uses fewer decimal places for display\n  const precision = currency === 2 ? 2 : 4\n  return `${num.toFixed(precision)} ${symbol}`\n}\n\n\u002F\u002F Example: USDC project (6 decimals)\nformatAmount('1000000', 6, 2)    \u002F\u002F \"1.00 USDC\"\n\n\u002F\u002F Example: ETH project (18 decimals)\nformatAmount('1000000000000000000', 18, 1)  \u002F\u002F \"1.0000 ETH\"\n```\n\n**Common mistake:** Using `formatEther()` (assumes 18 decimals) for USDC projects will show wildly incorrect values. Always detect the currency first and use `formatUnits()` with the correct decimals.\n\n---\n\n## Best Practices\n\n1. **Use server-side proxy** - Never expose API key in frontend code\n2. **Cache responses** - Data updates every ~1 minute, cache accordingly\n3. **Query only needed fields** - Reduces payload size and latency\n4. **Use pagination** - Don't fetch thousands of records at once\n5. **Handle nulls** - Fields like `volumeUsd`, `handle` may be null\n6. **Consider freshness** - Indexer may lag 1-2 blocks behind chain\n7. **Use filters** - Narrow queries by chainId, projectId when possible\n8. **Always include version** - V4 and V5 projects with the same projectId are completely different\n9. **Use BigInt for USD values** - volumeUsd is 18 decimals; parseFloat loses precision on large values\n10. **Group by projectId + version** - Same project across chains should be grouped, but different versions should not\n11. **Check GraphQL types** - Different queries expect Float vs Int for the same fields\n12. **Use suckerGroup for cross-chain data** - More efficient than querying each chain separately\n13. **Fetch token symbols from chain** - Bendystraw's tokenSymbol is the accounting token, not the project's issued token\n14. **Detect currency before formatting** - Use `formatUnits(wei, decimals)` not `formatEther(wei)` since USDC projects use 6 decimals\n\n---\n\n## Use Cases\n\n- **Project dashboards** - Display stats, activity, holders\n- **Unified activity feeds** - Use `activityEvents` for all-in-one activity streams\n- **Governance snapshots** - Get token balances at specific timestamps\n- **Analytics** - Track trends, volumes, contributor growth via `projectMoments`\n- **Portfolio tracking** - Use `wallet` query for user positions across all projects\n- **Omnichain aggregation** - Unified view via `suckerGroup` and `suckerGroupMoments`\n- **Airdrops** - Generate recipient lists from holder data\n- **Loan dashboards** - Track RevLoans borrowing, repayments, liquidations\n- **NFT galleries** - Full tier metadata via `nftTiers` including SVGs and pricing\n- **Cross-chain tracking** - Monitor token bridging via `suckerTransactions`\n- **Treasury management** - Track payouts and allowance usage\n\n---\n\n## Related Skills\n\n- `\u002Fjb-relayr` - Execute multi-chain transactions\n- `\u002Fjb-omnichain-ui` - Build UIs with Bendystraw data\n- `\u002Fjb-query` - Direct on-chain queries via cast\u002Fethers\n- `\u002Fjb-revloans` - RevLoans protocol integration\n- `\u002Fjb-loan-queries` - Loan-specific query patterns\n- `\u002Fjb-nft-gallery-ui` - Build NFT galleries with tier data\n",{"name":5,"description":7},"main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fmejango\u002Fjuicy\u002Fjb-bendystraw",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add kbarbel640-del\u002Fskills --skill jb-bendystraw",{"tool":54,"command":55},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill jb-bendystraw",{"tool":57,"command":58},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill jb-bendystraw","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fmejango\u002Fjuicy\u002Fjb-bendystraw\u002FSKILL.md",true,[],[63,93,122,143,149,168],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":74,"publishedAt":36},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[69],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[75,76,30,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92],"crowd prompting","prompting","with","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":94,"name":95,"nameZh":6,"description":96,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":104,"publishedAt":36},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[99],{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[105,30,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,81,121],"dilemm ai","arena","analyze","browser","openclaw agent","create","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":123,"name":124,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":131,"publishedAt":36},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[128],{"categoryNo":129,"categoryName":130,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000021","资料检索与研究",[132,133,134,135,136,137,138,77,139,119,121,140,30,141,142],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":144,"name":124,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":148,"publishedAt":36},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[147],{"categoryNo":129,"categoryName":130,"taxonomyType":102,"taxonomyTypeDesc":103},[135,134,133,132,142,137,138,77,136,119,121,139,140,30,141],{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":155,"publishedAt":36},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[156,157,158,159,160,161,162,163,164,165,166,23,167],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","branches",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":172,"categories":173,"keywords":178,"publishedAt":36},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[174,175],{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},{"categoryNo":176,"categoryName":177,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000037","律师与法务",[179,180,181,182,77,183,184,41,185,140,186,187,188,189,190,90,191,192,193],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]