9.3k★by mejango
jb-bendystraw – OpenClaw Skill
jb-bendystraw is an OpenClaw Skills integration for coding workflows. Bendystraw GraphQL API reference for querying Juicebox project data across all chains. Get project stats, payments, token holders, loans, NFT tiers, unified activity feeds, historical snapshots, and cross-chain aggregations.
Skill Snapshot
| name | jb-bendystraw |
| description | 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. OpenClaw Skills integration. |
| owner | mejango |
| repository | mejango/juicypath: jb-bendystraw |
| language | Markdown |
| license | MIT |
| topics | |
| security | L1 |
| install | openclaw add @mejango/juicy:jb-bendystraw |
| last updated | Feb 7, 2026 |
Maintainer

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