1. Choose a project or revnet
A Juicebox project collects money, shares tokens, and follows rules you set. Its owner can change the terms when those rules allow it. A revnet sets its core economic terms at launch and creates its transferable token then; its operator keeps a smaller set of controls.
Project builders can launch and manage funds without code. App builders can connect their product to Juicebox. Contract builders can add new behavior. Start with the path you need and use the reference as you go.
| You need to | Use |
|---|---|
| Pay a team from revenue or donations on a schedule | Project with a spending limit |
| Keep the option to change rules or contracts later | Project with those owner powers enabled |
| Sell collectibles or memberships | Either, with a shop |
| Set how new tokens and cash outs work at launch | Revnet; check the controls its operator keeps |
| Let holders exchange tokens for available project funds | Project or revnet, when the terms allow it |
| Let holders use tokens to borrow project funds | Revnet with funds available to lend |
2. Start with one working example
Choose one result below. Each path explains the tools you need as you use them. The Learn glossary is there when you need a definition.
- Launch a project: use the setup form on a test network. Review the terms, then try a small payment and payout before using real funds.
- Build an app: follow Your first test payment. Read a project, preview a payment, and check the result.
- Write a contract: choose one behavior to add. Read the V6 contract reference, build it, and test against a local copy of the chain.
3. Find the right contract
The deploy-all-v6 repository lists deployed contracts by chain. It includes each address and the format used to call it, called an ABI. The SDK includes the same address map. A project can use its own contracts, so use JBDirectory to find the contracts currently managing its rules and payments before building a transaction.
Who does what
- JBProjects
- Records each project’s owner using a unique token (NFT)
- JBController
- Manages rules, creates project tokens, and records project details
- JBMultiTerminal
- Receives payments, holds funds, sends payouts, and handles cash outs
- JBRulesets, JBSplits, JBFundAccessLimits, JBTokens, JBTerminalStore
- Store rules, recipients, spending limits, tokens, and balances
- JBDirectory
- Finds a project’s rules manager (controller) and payment contracts (terminals)
- JBPermissions
- Records permission to act for another account
- JBPrices
- Converts currencies when calculating new tokens and spending limits
- JB721TiersHook + deployer
- Adds a shop to project payments
- JBBuybackHookRegistry + JBBuybackHook
- Buys existing tokens on Uniswap V4 when that gives the payer more tokens
- JBSucker + JBSuckerRegistry, JBOmnichainDeployer
- Links projects and moves tokens between chains
- JBRouterTerminalRegistry
- Finds supported ways to convert a payment into an accepted token
- REVDeployer, REVOwner, REVLoans
- Revnets
- Chains: Ethereum, Optimism, Base, and Arbitrum, plus their Sepolia test networks. Find the list in the SDK’s SUPPORTED_CHAINS and JB_CHAINS.
- Source: the repos ending in -v6 are current. Older Juicebox versions are not interchangeable with them, and this site redirects unknown routes to the legacy app for those.
4. Find a contract call
Use this table once you know the action you want to build. The second column names its contract call and any matching helper in the Juicebox software library (SDK). Examples later in this guide contain placeholders; replace them before use. Some actions need more than one transaction, such as approving token spending before paying.
User action → contract call
- Launch a project
- JBController.launchProjectFor, or the 721 / omnichain deployers — buildLaunchProjectTx / buildOmnichainLaunchProjectTx
- Launch a revnet
- REVDeployer.deployFor — buildDeployRevnetTx
- Pay
- JBMultiTerminal.pay — buildPayTx
- Buy a shop item
- JBMultiTerminal.pay with 721 metadata — build721PayMetadata
- Add funds, no tokens
- JBMultiTerminal.addToBalanceOf
- Cash out
- JBMultiTerminal.cashOutTokensOf — prepareHookAwareCashOut
- Send payouts
- JBMultiTerminal.sendPayoutsOf
- Withdraw surplus allowance
- JBMultiTerminal.useAllowanceOf
- Send tokens set aside for other recipients
- JBController.sendReservedTokensToSplitsOf
- Schedule new terms
- JBController.queueRulesetsOf — buildQueueRulesetsTx
- Edit recipients and shares
- JBController.setSplitGroupsOf — buildSetSplitGroupsTx
- Create a transferable token contract (ERC-20)
- JBController.deployERC20For — buildDeployErc20Tx
- Move tokens tracked in Juicebox into the ERC-20
- JBController.claimTokensFor — buildClaimTokensTx
- Create more tokens
- JBController.mintTokensOf — buildMintTokensTx
- Give someone permission
- JBPermissions.setPermissionsFor — buildSetPermissionsTx
- Manage the shop
- JB721TiersHook.adjustTiers / mintFor
- Move tokens to another chain
- sucker.prepare → toRemote → claim
- Choose a market for buying existing tokens
- JBBuybackHookRegistry.initializePoolFor / setHookFor
- Create an address that forwards payments
- JBProjectPayerDeployer — buildDeployProjectPayerTx
Store amounts as whole numbers in the token’s smallest unit, using bigint. Convert only for display. Identify each project by both chain ID and project ID. Linked projects share a group ID, called a sucker group, but keep separate addresses, balances, and rule IDs on each chain.
5. Launch with the setup form
For: Project builders
The setup form covers Flavor, Look and feel, Rules or Stages when needed, Shop, and Launch. The simple flavor skips Rules and uses preset terms. Other projects can schedule new terms when their rules allow it. A revnet fixes its main financial terms at launch, while its operator keeps a limited set of controls.
The steps
- Flavor
- Choose a project type, real or test networks, accepted assets, and owner. Link chains if needed. A custom token must use the same address on each chain. “Accept any token” adds support for asset conversion. Turning “Allow changes” off sends ownership to an address nobody can use
- Look and feel
- Name (100 characters), token symbol, tagline, description (10,000 characters), logo and cover (25 MB each), links, tags, and payment notice. Saved when you launch to IPFS, a network that identifies files by their content
- Rules
- Set the first terms, what follows them, and how much notice changes require. The next section explains each setting
- Shop
- Add items with prices, supply, media, and categories. Choose who receives sales, discounts, reserved copies, and any voting rights. Launch creates a shop contract even with no items
- Launch
- One transaction per chain: preview, review, sign, and wait. A shared Safe wallet creates a proposal that still needs approval and execution
6. Choose the project’s rules
For: Project builders
A group of project terms is called a ruleset. It can repeat on a schedule, with each repeat called a cycle. These are the settings in the setup form.
Rules step
- Duration
- How long each cycle lasts: 1 day to a year, Flexible (0), or Forever. Timed cycles repeat until replaced. Flexible rules have no cycle boundary; notice and approval still apply
- Issuance
- New tokens per ETH or USD paid, before reserved tokens. Default 10,000. Leave blank on a later ruleset to continue the previous rate after any scheduled cuts
- Issuance cut
- How much the new-token rate falls each cycle. Revnets express this as “cut X% every N days”
- Reserved split
- The share of new tokens set aside for other recipients. Add recipients to set the total
- Cash outs
- Let holders exchange tokens for available project funds. The cash out tax controls how much stays for remaining holders: 0% gives a proportional share; 100% turns cash outs off. Revnets may also delay when cash outs begin
- Payouts
- None, payments to the owner within a limit, or payments to named recipients. Set amounts or percentage shares for each accepted asset. Limits apply per cycle and per chain
- Surplus allowance
- How much the owner can withdraw beyond the regular spending limit
- Accept payments
- Off pauses paying
- Owner can mint any time
- Whether the owner can create tokens without a payment (allowOwnerMinting)
- Superpowers
- Launch contracts and settings the owner can change: payment contracts, rules manager, project token, accepted assets, and currency feeds. Check both the current powers and whether later rules can enable more
- Afterwards, notice
- What follows these terms, and how much notice a change needs: 3 hours, 1 day, 3 days, 7 days, or none
7. What happens when you launch
For: Project buildersApp builders
- Launch contract: the app uses REVDeployer.deployFor for revnets, JBOmnichainDeployer.launchProjectFor for linked chains, and JB721TiersHookProjectDeployer.launchProjectFor for other projects. These launchers set up the required extensions.
- Transaction value: read JBProjects.creationFee() right before each launch and pass that exact amount as value. Show it in the review before signing.
- One transaction per chain: sign each launch in order with the same wallet. The app keeps a shared value, called a salt, so linked bridge contracts get matching addresses.
- Afterwards: each chain has its own project ID; the page lives at /<chain>:<id>. The Rulesets (or Terms) tab shows the terms as the contracts hold them.
8. Manage your project
For: Project builders
The project page groups available management actions into tabs. Available actions depend on your account, project rules, and extensions.
Project page
- Rulesets
- Read current and upcoming terms. Schedule a change under the project’s timing and approval rules
- Funds
- Send funds to chosen recipients, use the owner’s extra withdrawal allowance, or add funds
- Owners
- See holders and balances. Send tokens set aside for recipients, or create tokens scheduled by a revnet
- Shop
- Add items, create reserved copies, replace media, and redeem items
- Admin
- Manage permissions, ownership, names, links, and token details. Use the contract and market controls allowed by the current rules
- Extras
- Export current terms as a .jb file, create a payment-forwarding address, and manage funds supplied to markets
9. Use the Juicebox software library
For: App builders
The Juicebox software library (SDK), @bananapus/nana-sdk-core, helps you read projects and prepare transactions. It includes addresses and contract call formats (ABIs). Import shared addresses, formats, and project details from the package root; import V6 reads and transaction builders from /v6.
A builder prepares a request without sending it: { chainId, address, abi, functionName, args, value }. Read with a public client and sign with a wallet client on the same chain.
- Start Your first test payment
- Download the tested read-project.mjs example
- Next: preview, simulate, and inspect a payment
The tutorial uses an existing project on the Base Sepolia test network. It includes the full working example, package versions, expected output, and help when a step fails.
/v6 exports, grouped
- Reads
- getAccountingContexts, resolvePaymentTerminal, getCurrentRuleset, getUpcomingRuleset, getAllRulesets, previewPay, chooseBestPayRoute, getCashOutQuote, getHookAwareCashOutQuote, getV6SuckerPairs, getSuckerMovements, getTokenAddress, getCreditBalance, getProjectCreationFee, getProject721Shop, hasPermissions, getBorrowableAmount
- Builders
- buildLaunchProjectTx, buildOmnichainLaunchProjectTx, buildDeployRevnetTx, buildPayTx, buildCashOutTx, buildQueueRulesetsTx, buildOmnichainQueueRulesetsTx, buildSetSplitGroupsTx, buildSetPermissionsTx, buildDeployErc20Tx, buildClaimTokensTx, buildTransferCreditsTx, buildMintTokensTx, buildBurnTokensTx, buildDeployProjectPayerTx, buildBridgePrepareTx, buildToRemoteTx, buildBridgeClaimTx, buildSyncAccountingDataTx, buildDirectPaySwapTx, buildPermit2ApproveTx, buildBorrowTx, buildRepayLoanTx
- Config builders
- buildRulesetConfiguration, buildRulesetMetadata, buildAccountingContext, buildTerminalConfigurations, buildSplit, fillSplitPercents, buildTierMetadata, build721RulesetMetadata, build721PayMetadata, buildRevnetStageConfig
- Constants
- slippageFloor, RULESET_WEIGHT_INHERIT, STANDARD_FEE, MAX_FEE, MAX_RESERVED_PERCENT, MAX_CASH_OUT_TAX_RATE, SPLITS_TOTAL_PERCENT, RESERVED_TOKEN_SPLIT_GROUP_ID, payoutSplitGroupId, NATIVE_TOKEN, USDC_ADDRESSES, PERMIT2_ADDRESS, the uniswapV4* math family
- Sub-entries
- /v6/loans, /v6/cash-out, /v6/permit2, /v6/direct-pay, /v6/uniswap-v4, /chains, /jbcenter
import {
buildPayTx, buildQueueRulesetsTx, buildRulesetConfiguration, buildRulesetMetadata,
buildSetSplitGroupsTx, getCurrentRuleset, previewPay, slippageFloor,
} from "@bananapus/nana-sdk-core/v6";
import {
getJBContractAddress, JBCoreContracts, jbMultiTerminalAbi, type JBChainId,
} from "@bananapus/nana-sdk-core";
const terminal = getJBContractAddress(JBCoreContracts.JBMultiTerminal, 6, chainId);10. Read a project
For: App builders
Use the index (next section) to find and display projects. Use the chain for anything a signature depends on, and read it again right before signing.
What to read, and where
- Owner and project details
- JBProjects.ownerOf / JBController.uriOf
- Controller, terminals
- JBDirectory.controllerOf / terminalsOf / primaryTerminalOf
- Current, next, queued rulesets
- JBController.currentRulesetOf / upcomingRulesetOf / latestQueuedRulesetOf / allRulesetsOf
- Accepted tokens
- JBMultiTerminal.accountingContextsOf
- Balance and surplus
- JBTerminalStore.balanceOf / currentSurplusOf / usedPayoutLimitOf / usedSurplusAllowanceOf
- Limits
- JBFundAccessLimits.payoutLimitOf / surplusAllowanceOf
- Supply and balances
- JBTokens.totalSupplyOf / totalBalanceOf / creditBalanceOf / tokenOf
- Splits
- JBSplits.splitsOf(projectId, rulesetId, groupId)
- Pending reserved tokens
- JBController.pendingReservedTokenBalanceOf
- Cash out quote
- JBMultiTerminal.previewCashOutFrom
- Permissions
- JBPermissions.hasPermission / hasPermissions
- Chains
- JBSuckerRegistry.suckerPairsOf
11. Search project data with Bendystraw
For: App builders
Bendystraw gathers Juicebox chain records into a searchable database. This service, called an indexer, supplies project lists, activity, charts, and market positions through GraphQL queries.
Use it for fast browsing. Before signing, read balances, terms, spending approvals, and quotes directly from the chain again. The index can be a few blocks behind, so missing activity does not prove that a transaction failed.
Endpoints
- https://bendystraw.up.railway.app/graphql
- Mainnets: Ethereum, Optimism, Base, Arbitrum. No API key needed
- https://testnet.bendystraw.xyz/graphql
- Testnets: Sepolia and the L2 Sepolias
- …/schema
- A playground with the schema explorer; POST an introspection query to the graphql URL for codegen (same schema on both databases)
POST https://bendystraw.up.railway.app/graphql
{
projects(where: { chainId: 8453, version: 6 }, orderBy: "balance", orderDirection: "desc", limit: 10) {
items { projectId chainId name balance suckerGroupId }
totalCount
}
}What to ask it for
- projects / project(chainId, projectId, version: 6)
- Name, metadata URI, balance, token, owner, and the sucker group that links its chains
- payEvents, cashOutTokensEvents, activityEvents
- The feed behind any project page; filter by projectId or suckerGroupId
- participants
- Token holders and their balances, per project or per sucker group
- buybackPools, swapEvents, buybackPoolPositions
- Market pools, prices after trades, and the price ranges funded by liquidity providers
- loans, borrowLoanEvents
- Revnet loans and their collateral
- nftTiers, mintNftEvents
- 721 shop tiers and purchases
- suckerTransactions
- Cross-chain moves and where each one is in its lifecycle
- Every V6 row is versioned: filter with version: 6, and key a project by chainId + projectId, never projectId alone — the same number exists on every chain.
- Numeric arguments on singular queries are Float!, not Int! (Ponder’s choice). Declare variables as Float! or the request fails validation with no data.
- Lists page with limit and offset and return totalCount; loop until you have them all rather than trusting one page.
- suckerGroupId is as-of-event: when chains are linked later, old event rows keep the group id they were written with. Query by every project in the group when you need the full history.
- The SDK’s requestBendystraw(endpoint, query, variables) handles the POST, error surfacing, and endpoint normalisation; selectBendystrawEndpoint picks mainnet vs testnet from a chainId.
12. Connect wallets and request approval
For: App builders
A wallet can send a transaction directly. A shared Safe wallet needs a proposal and approvals before execution. Relayr can send a group of approved calls across chains. Permit2 grants limited token spending for swaps. This site chooses the flow based on the connected account.
Signing paths in this site
- Wallet write
- submitReviewedContractWrite: review the decoded call → switch chain → simulate as the connected account → sign → wait for the receipt
- Actions on behalf of an owner
- runAuthorityCalls selects the flow. A directly controlled wallet signs itself. An account that supports forwarded calls (ERC-2771) can approve a prepaid Relayr bundle. A Safe receives proposals in its transaction service
- Safe
- Propose, approve, then execute. A Safe can use the same address on another chain, but its deployment and signing authority must be checked on each chain
- Permit2
- Used for direct Uniswap V4 swaps. Other token payments use approve for the exact amount
Chain requests (RPC) go through Juicebox Center, which allows approved website origins. Connections include browser wallets, WalletConnect, Coinbase, and Safe. Para provides an embedded wallet and card purchases.
13. Save names, descriptions, and media
For: App builders
- Shape: JBProjectMetadata: name, description, projectTagline, logoUri, coverImageUri, infoUri, payButton, payDisclosure, tags, twitter, telegram, discord, archived.
- Read: getProjectMetadata(publicClient, { jbControllerAddress, projectId }) resolves the project’s uri and fetches it; ipfsUri and cidFromIpfsUri handle the encoding.
- Pin: this site pins JSON, images (25 MB), and media (500 MB) straight from the browser to Juicebox Center, which is origin-allowlisted and guards against empty files. The gateway is juicebox.center/ipfs/.
- Write: the owner updates the pointer with JBController.setUriOf (SET_PROJECT_URI).
14. Review, send, and confirm a transaction
For: App builders
Use one request from preview through submission. Just before signing, refresh balances, limits, and permissions. Prepare the request, run a trial with the actual account, and show what it does and the minimum it will return. After sending, distinguish a rejected wallet request, a shared-wallet proposal, a pending transaction, and a confirmed success or failure.
The app checks that the encoded request still matches what the user reviewed. Its trial uses eth_call with a gas limit. A source check also verifies that every contract write uses one of the reviewed submission functions.
Refresh chain state, build one request, simulate it for the actual account, decode and review it, then submit that same request. Report success only after a successful transaction receipt.
15. Run this site locally
For: App builders
Use the Node.js and npm versions declared in package.json. Copy the example public configuration before starting. NEXT_PUBLIC values are shipped to the browser, so they must never contain private keys or secrets.
git clone https://github.com/mejango/juicebox-money.git
cd juicebox-money
npm ci
cp .env.example .env.local
npm run devOpen http://localhost:3001. Confirm the configured indexer, RPC service, wallet providers, and allowed origin work in your environment. Start with project reads, then connect a testnet wallet for transaction flows.
Commands
- npm run dev
- Next.js on port 3001
- npm run check
- Full repository gate: dependencies, types, lint, source/deployment/schema checks, tests, production build, browser checks, and budgets
- npm test / npm run test:browser
- Vitest, then Playwright against the built app with a fixture server
- npm run schema:check
- Regenerates and verifies the Bendystraw operation registry
- Env: NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_BENDYSTRAW_URL, NEXT_PUBLIC_TESTNET_BENDYSTRAW_URL, NEXT_PUBLIC_PARA_API_KEY, NEXT_PUBLIC_PARA_ENV, NEXT_PUBLIC_VERSION; WalletConnect and the on-ramp provider are optional. No RPC or IPFS keys live in the client.
- Tests: the fixture-based browser suite isolates external services; use the documented fork and live-service checks separately when validating an integration.
16. Get funded
For: App buildersContract builders
A project receives payments through its payment contracts, called terminals. Use the current terms and a fresh quote to show the tokens the payer will receive.
pay(
projectId,
token, // which token to pay with
amount, // how much
beneficiary, // who receives the minted tokens
minReturnedTokens, // minimum amount the recipient must receive
memo, // message attached to the payment
metadata // extra data for hooks
)
// Returns: number of tokens minted for the beneficiaryChecking balances
- JBTerminalStore.balanceOf(terminal, projectId, token)
- Terminal balance for a specific token
- JBTerminalStore.currentSurplusOf(...)
- Surplus across specified terminals and tokens
- JBTerminalStore.currentTotalSurplusOf(...)
- Surplus aggregated across ALL terminals
17. Manage tokens
For: App buildersContract builders
Juicebox can track token balances itself; these balances are called credits. A project can also create a separate token contract using the ERC-20 standard. Holders can then move credits into that contract. Count both forms when showing a holder’s total balance.
Token operations
- JBController.deployERC20For(projectId, name, symbol, salt)
- Deploy the project’s ERC-20 token
- JBTokens.tokenOf(projectId)
- Get the ERC-20 address (zero if not yet deployed)
- JBTokens.totalBalanceOf(holder, projectId)
- Complete holdings (credits + ERC-20)
- JBTokens.creditBalanceOf(holder, projectId)
- Internal credits only
- JBController.claimTokensFor(holder, projectId, count, beneficiary)
- Convert credits into ERC-20 tokens
Minting & burning
- JBController.mintTokensOf(projectId, tokenCount, beneficiary, memo, useReservedPercent)
- Owner mints tokens on-demand (if ruleset allows)
- JBController.burnTokensOf(holder, projectId, tokenCount, memo)
- Holder burns their own tokens
18. Send funds and reserved tokens
For: App buildersContract builders
Payouts send funds to the project’s chosen recipients. Reserved tokens are new tokens set aside for other recipients. Anyone can normally trigger these distributions, but the ownerMustSendPayouts setting can restrict payouts to the owner or an operator with SEND_PAYOUTS permission.
sendPayoutsOf(
projectId,
token,
amount, // up to the payout limit
currency,
minTokensPaidOut // minimum amount the recipient must receive
)
// Distributes to splits, leftover to project owner
// Review the final amount each recipient will receivesendReservedTokensToSplitsOf(projectId)
// Anyone can call this at any time
// Mints accumulated reserved tokens and distributes to splitsTracking usage
- JBTerminalStore.usedPayoutLimitOf(...)
- How much of the payout limit has been used this cycle
- JBTerminalStore.usedSurplusAllowanceOf(...)
- How much surplus allowance has been used
- JBController.pendingReservedTokenBalanceOf(projectId)
- Undistributed reserved tokens
19. Cash out
For: App buildersContract builders
Holders can exchange tokens for available project funds, called surplus. With one asset in one terminal, surplus is the balance above the remaining payout limit. A cash out removes the exchanged tokens from supply.
The cash out tax controls how much stays for remaining holders. At 0%, the base formula returns a proportional share. Higher rates return less when only part of the supply is exchanged; 100% returns zero. Use a quote that includes the project’s extensions to find the final amount.
cashOutTokensOf(
holder,
projectId,
cashOutCount, // how many tokens to burn
tokenToReclaim, // which token to receive
minTokensReclaimed, // minimum amount the recipient must receive
beneficiary, // who receives the funds
metadata
)20. Deploy a revnet
For: App buildersContract builders
Create a revnet and its stage schedule with REVDeployer.deployFor().
Stages set how a revnet’s terms change over time. For example, an early stage can create more tokens per payment, followed by stages that gradually create fewer.
Each stage sets the new-token rate, scheduled cuts, reserved share, and cash out tax. Stages begin automatically at their scheduled times.
deployFor(
revnetId, // project ID (or 0 for auto)
configuration, // REVConfig with stages
accountingContextsToAccept[], // tokens the terminal should accept
suckerDeploymentConfiguration, // cross-chain setup
tiered721HookConfiguration, // optional NFT tiers
allowedPosts[] // optional croptop posts
)
// New revnet (revnetId == 0): include the current project creation fee.
// Existing project ID: send no native value.A revnet uses the same payment and cash out calls as other projects. REVOwner applies its terms through an extension. Include that extension when requesting a quote, and check the operator’s remaining permissions.
Reading stage state
- JBController.currentRulesetOf(projectId)
- Active stage parameters
- JBController.upcomingRulesetOf(projectId)
- Next ruleset, including an automatic cycle of the current stage; empty only when no ruleset follows.
- JBController.allRulesetsOf(projectId, startingId, size)
- Complete stage history
21. Build with revnets
The Revnet build guide covers launch settings, saved drafts, operator controls, loans, and custom extensions. Use its source references to check the terms you plan to launch.
22. Install the code and launch from Solidity
For: Contract builders
The V6 repos ship as npm packages and import by package path; remappings.txt in every repo only maps forge-std, and node_modules resolves the rest. Solidity 0.8.28.
Packages
- @bananapus/core-v6
- Terminals, controller, tokens, splits, permissions, prices, the hook interfaces
- @bananapus/721-hook-v6
- Shop tiers and the 721 project deployer
- @bananapus/omnichain-deployers-v6
- Launch plus suckers in one call
- @bananapus/buyback-hook-v6, @bananapus/suckers-v6, @bananapus/router-terminal-v6
- Pool routing, cross-chain, token routing
- @bananapus/project-payer-v6, @bananapus/project-handles-v6, @bananapus/distributor-v6
- Payer addresses, ENS handles, distributor
- @bananapus/permission-ids-v6
- The permission ID constants
- @rev-net/core-v6
- Revnets
launchProjectFor, field by field
- JBController.launchProjectFor
- (address owner, string projectUri, JBRulesetConfig[] rulesetConfigurations, JBTerminalConfig[] terminalConfigurations, string memo) payable → projectId. msg.value must equal JBProjects.creationFee() exactly
- JBController.queueRulesetsOf
- (uint256 projectId, JBRulesetConfig[] rulesetConfigurations, string memo). Schedules new terms. The current timing and approval requirements decide when they can begin
- JBRulesetConfig
- mustStartAtOrAfter (uint48; 0 = now), duration (uint32 seconds; 0 = until replaced), weight (uint112, 18-dec tokens per base-currency unit; 1 = inherit the decayed weight, 0 = no issuance), weightCutPercent (uint32, of 1e9), approvalHook, metadata, splitGroups[], fundAccessLimitGroups[]
- JBRulesetMetadata
- reservedPercent (uint16, of 10,000), cashOutTaxRate (uint16, of 10,000; 10,000 = cash outs off), baseCurrency (uint32: 1 = ETH, 2 = USD, or uint32(uint160(token))), pausePay, pauseCreditTransfers, allowOwnerMinting, allowSetCustomToken, allowTerminalMigration, allowSetTerminals, allowSetController, allowAddAccountingContext, allowAddPriceFeed, ownerMustSendPayouts, holdFees, scopeCashOutsToLocalBalances, useDataHookForPay, useDataHookForCashOut, dataHook, metadata (uint16, 14 usable bits)
- JBFundAccessLimitGroup
- terminal, token, payoutLimits[] and surplusAllowances[] as { amount (uint224, in the token’s decimals), currency (uint32) }, sorted by strictly increasing currency. Empty = no payouts; type(uint224).max = unlimited. Per chain, never aggregate
- JBTerminalConfig
- terminal, accountingContextsToAccept[] of { token, decimals (uint8), currency (uint32) }. NATIVE_TOKEN is 0x…EEEe with currency 61166
- JBSplitGroup / JBSplit
- groupId (1 = reserved tokens; uint256(uint160(token)) = payouts in that token), splits[] of { percent (uint32, of 1e9), projectId (uint64), beneficiary, preferAddToBalance, lockedUntil (uint48), hook }. Routing: hook, else projectId, else beneficiary
- JBOmnichainDeployer.launchProjectFor
- (owner, projectUri, JBOmnichain721Config, rulesetConfigurations, terminalConfigurations, memo, JBSuckerDeploymentConfig) payable → (projectId, hook, suckers[]). Also deploys this chain’s suckers; the sucker salt is keccak256(sender, salt), so the same sender must launch on every chain
- JB721TiersHookProjectDeployer.launchProjectFor
- (owner, JBDeploy721TiersHookConfig, JBLaunchProjectConfig, controller, salt) payable → (projectId, hook). Sets the hook as the data hook
23. Add custom behavior with hooks
For: Contract builders
An extension can change a payment’s token output, send rewards, or check a rule change. These extensions are called hooks. Choose the interface for the moment when your code needs to run.
Hook interfaces
- IJBRulesetDataHook
- Runs before a payment or cash out is recorded. Can change the new-token rate, cash out inputs, and which later hooks run.
- IJBPayHook
- Runs after a payment is recorded and tokens are created. Can deliver rewards or trigger other actions.
- IJBCashOutHook
- Runs after a cash out is recorded. Can receive funds and run additional actions.
- IJBSplitHook
- Runs when a recipient’s share is sent to an extension. Can forward or use the funds.
- IJBRulesetApprovalHook
- Approves new project terms before they can take effect. Must return APPROVED.
function afterPayRecordedWith(
JBAfterPayRecordedContext calldata context
) external payable;
// context includes:
// payer, projectId, rulesetId, amount,
// forwardedAmount, weight, newlyIssuedTokenCount,
// beneficiary, hookMetadata, payerMetadata24. Implement an extension
For: Contract builders
- Installing a data hook: set metadata.dataHook and useDataHookForPay / useDataHookForCashOut on the ruleset. beforePayRecordedWith returns the weight to use and pay hook specifications; beforeCashOutRecordedWith returns the tax rate, count, supply, surplus, and cash out hook specifications.
- Funds: native value arrives at a pay or cash out hook as msg.value; ERC-20 arrives as an allowance you must transferFrom during the call, revoked afterwards. Quote the amount the hook will actually receive. A specification with noop = true is informational and never called.
- Two metadatas: hookMetadata is authored by the data hook, while payerMetadata / cashOutMetadata comes from the caller. Trust hookMetadata only after authenticating the calling terminal and the expected hook path; validate caller-supplied fields.
- Metadata format: JBMetadataResolver: a reserved first word, then a table of 4-byte ids with word offsets, then 32-byte-aligned blobs. createMetadata(ids, datas), addToMetadata, getDataFor(id, metadata). Ids are getId(purpose, target) = bytes4(bytes20(target) ^ bytes20(keccak256(purpose))).
- Minting from a hook: JBController.mintTokensOf lets the terminal, the ruleset’s data hook, or any address the data hook’s hasMintPermissionFor approves mint without a grant; everyone else needs MINT_TOKENS (10) and allowOwnerMinting.
- Split hooks: a split whose hook field is set receives its share through processSplitWith. On the reserved-token path the controller approves an ERC-20 for the amount and burns whatever the hook leaves unspent; on payouts the terminal checks ERC-165 first.
- Reentrancy: a hook can call back into your contract before its first call finishes, called reentrancy. Protect your state updates and test each callback. A guard that blocks every nested call can break intended flows. Use override(ERC165, IERC165) for supportsInterface.
25. Permissions
For: Contract buildersApp builders
Give another address permission for a specific action with JBPermissions. Choose only the permissions it needs and the project they apply to. The contract stores each permission as one bit in a 256-bit field.
setPermissionsFor(
account, // the address granting permission
permissionsData // { operator, projectId, permissionIds[] }
)
// projectId = 0 is the wildcard: every project `account` controls on this chainChecking permissions
- JBPermissions.hasPermission(operator, account, projectId, permissionId, includeRoot, includeWildcard)
- Check a single permission
- JBPermissions.hasPermissions(operator, account, projectId, permissionIds[], includeRoot, includeWildcard)
- Check multiple permissions at once
- JBPermissions.WILDCARD_PROJECT_ID()
- Returns 0 — the wildcard project ID
Permission ids
- 1 - ROOT
- Grants every permission. Prefer the specific actions needed.
- 2 - QUEUE_RULESETS
- Queue new rulesets for the project.
- 3 - LAUNCH_RULESETS
- Launch the project’s first rulesets.
- 4 - CASH_OUT_TOKENS
- Cash out (redeem) project tokens on a holder’s behalf.
- 5 - SEND_PAYOUTS
- Trigger payout distributions.
- 6 - MIGRATE_TERMINAL
- Migrate funds to a new terminal.
- 7 - SET_PROJECT_URI
- Update project metadata.
- 8 - DEPLOY_ERC20
- Deploy the project’s ERC-20 token.
- 9 - SET_TOKEN
- Set a custom token for the project.
- 10 - MINT_TOKENS
- Create tokens without a payment.
- 11 - BURN_TOKENS
- Burn tokens from another holder.
- 12 - CLAIM_TOKENS
- Claim credits into ERC-20 tokens for a holder.
- 13 - TRANSFER_CREDITS
- Transfer a holder’s unclaimed credits.
- 14 - SET_CONTROLLER
- Change the project controller.
- 15 - SET_TERMINALS
- Set the project’s terminals.
- 16 - ADD_TERMINALS
- Add terminals to the project.
- 17 - SET_PRIMARY_TERMINAL
- Set the primary terminal for a token.
- 18 - USE_ALLOWANCE
- Withdraw surplus via the surplus allowance.
- 19 - SET_SPLIT_GROUPS
- Modify payout and reserved token splits.
- 20 - ADD_PRICE_FEED
- Add a price feed for a currency pair.
- 21 - ADD_ACCOUNTING_CONTEXTS
- Add accounting contexts (accepted tokens) to a terminal.
- 22 - SET_TOKEN_METADATA
- Set the project token’s name and symbol.
- 23 - SIGN_FOR_ERC20
- Sign ERC-20 permit approvals on the project’s behalf.
- 24 - ADJUST_721_TIERS
- Add or remove tiers on a 721 hook.
- 25 - SET_721_METADATA
- Update a 721 hook’s metadata (base URI, resolver, contract URI).
- 26 - MINT_721
- Mint NFTs directly, without a payment.
- 27 - SET_721_DISCOUNT_PERCENT
- Set the 721 hook’s discount percent.
- 28 - SET_BUYBACK_TWAP
- Set the period used to calculate the buyback market’s average price (TWAP).
- 29 - SET_BUYBACK_POOL
- Set the buyback hook’s Uniswap pool.
- 30 - SET_BUYBACK_HOOK
- Set which buyback hook the registry routes the project to.
- 31 - SET_ROUTER_TERMINAL
- Configure the project’s router terminal.
- 32 - MAP_SUCKER_TOKEN
- Link matching assets across a pair of bridge contracts (suckers).
- 33 - DEPLOY_SUCKERS
- Create bridge contracts between the project’s chains.
- 34 - SET_SUCKER_PEER
- Set a sucker’s cross-chain peer.
- 35 - SUCKER_SAFETY
- Emergency token recovery on a sucker.
- 36 - SET_SUCKER_DEPRECATION
- Deprecate a sucker.
- 37 - OPEN_LOAN
- Open a REVLoans loan against project tokens.
- 38 - REALLOCATE_LOAN
- Move collateral into a new loan and borrow its current capacity. The operator chooses where new proceeds go.
- 39 - REPAY_LOAN
- Repay or release collateral on a holder’s behalf. The operator chooses where returned collateral goes.
26. Test against deployed contracts
For: Contract builders
Foundry
- TestBaseWorkflow
- @bananapus/core-v6/test/helpers/TestBaseWorkflow.sol: deploys the full protocol locally with a mock USDC and two terminals, plus Permit2. Extend it for unit tests of hooks and integrations
- Fork tests
- Create a local copy of the chain at one fixed block, called a fork. Configure foundry.toml rpc_endpoints and test against the deploy-all-v6 addresses
- Sizes
- forge build --sizes early; several V6 contracts sit near EIP-170 and were split or trimmed to fit. Plan for library extraction if you are close
- Bytecode parity
- compare the deployed machine code with deploy-all-v6 artifacts. Matching source alone is insufficient because linked libraries change the resulting code
27. Behavior to check carefully
For: Contract buildersApp builders
- Who is the payer: context.payer is the terminal’s msg.sender. Through a router, project payer, or wrapper it is that contract, unless the contract exposes originalPayer() (IJBPayerTracker), which the router registry probes. Expose the original payer where the integration expects it, and set beneficiary/refund addresses explicitly so rewards or refunds are not accidentally credited to the intermediary.
- Router terminal cold start: the router registry reverts accountingContextForTokenOf for projects below its threshold; it is not universally accepting. Probe with previewPayFor before assuming a route.
- Buyback metadata is three words: the pay metadata under getId("pay", hook) decodes as (amountToSwapWith, minimumSwapAmountOut, skipSplits). Always encode all three; two-word quotes revert. Buyback 1.4.0 falls back to minting when the TWAP floor cannot be filled.
- Router gateway custody: resolve registry.terminalOf(projectId), then gateway.ROUTER() when the selected terminal is a gateway. Eligible failed fee routes remain held for retry, not paid or forgiven. Track QueuePendingCall, ProcessPendingCall, RecordTerminalCallFailure and RefundPendingCall; pendingCallCount is the lifetime count of issued IDs, not the number currently pending.
- Per-chain rollout and prices: use canonical executed deployment records, preserving previous and v1 addresses for history. Mainnet proposals do not activate the new stack. JBRatioPriceFeed supplies project-zero defaults for USDC→NATIVE and USDC→ETH; addresses vary by chain and OP Sepolia has the feed without hook/router/gateway.
- Payouts to a project without a terminal: if the recipient project has no payment contract for the asset, its payout fails and the balance is restored. The payout limit is still consumed.
- Ownership changes reset permissions: permissions must come from the current owner. A previous owner’s grants stop working after ownership transfers.
- Sucker salt: the omnichain deployer derives sucker addresses from the sender and the salt, so a different wallet on another chain produces a project that cannot be linked.
28. Sell collectibles and memberships
For: Project buildersApp buildersContract builders
A project shop can sell uniquely identified tokens, called NFTs. Items are grouped into tiers with a price and supply. The JB721TiersHook extension delivers the selected items when a payment covers their price.
launchProjectFor(
owner,
deployTiersHookConfig, // NFT name, symbol, tiers[]
launchProjectConfig, // standard project config
controller,
salt // CREATE2 salt for a deterministic hook address (0 for none)
)
// Always use this deployer, even with empty tiersTier configuration
- price
- What one NFT of this tier costs.
- initialSupply
- Max NFTs available. Must be at least 1; capped at 999,999,999. 0 is rejected.
- category
- Grouping ID. Tiers MUST be sorted by category (ascending).
- reserveFrequency
- Mint 1 reserved NFT every N minted.
- reserveBeneficiary
- Who receives reserved NFTs.
- votingUnits
- Voting weight through JB721Checkpoints. Used only when flags.useVotingUnits is set; otherwise the tier’s price sets its voting weight.
- encodedIpfsUri
- IPFS content hash for metadata.
- flags.cantBeRemoved
- If true, tier is permanent (one of the nested flags: allowOwnerMint, useVotingUnits, transfersPausable, cantBeRemoved, …).
Reading nft state
- JB721TiersHookStore.tiersOf(hook, categories[], includeResolvedUri, startId, size)
- List tiers with optional filters
- JB721TiersHookStore.tierOf(hook, tierId, includeResolvedUri)
- Single tier details
- JB721TiersHook.balanceOf(owner)
- NFTs held by an address
- JB721TiersHook.cashOutWeightOf(tokenIds[])
- Cash out weight of specific NFTs (divide by totalCashOutWeight() for the surplus fraction)
29. Buy existing tokens when they offer more
For: App buildersContract builders
A payment can create new tokens or buy existing ones from a market. JBBuybackHook compares both and uses the configured Uniswap V4 pool when it gives the payer more tokens. It can also route cash outs.
To limit price changes during a swap, the hook uses an average price over time, called TWAP, to calculate its default minimum. Buyback 1.4.0 falls back to minting when the swap cannot meet that floor. Pay metadata under getId("pay", hook) must encode (amountToSwapWith, minimumSwapAmountOut, skipSplits); the third word is a bool, and a two-word quote reverts.
// Set up the buyback hook with a Uniswap V4 pool
JBBuybackHook.setPoolFor(
projectId,
fee, // Uniswap pool fee tier
tickSpacing, // pool tick spacing
twapWindow, // TWAP observation window (seconds)
terminalToken // the terminal token to route
)
// The pool key is once-only; the TWAP window stays mutable via setTwapWindowOf
// Requires SET_BUYBACK_POOL permission payment arrives
│
▼
query TWAP oracle for market price
│
├─ pool gives more tokens than minting
│ └─▶ swap on Uniswap V4, mint any unswapped remainder
│
└─ minting gives equal or more tokens
└─▶ normal mint flow (weight × amount)30. Accept payments in other assets
For: App buildersContract builders
A router can convert a payment into an asset the project accepts. JBRouterTerminal finds a supported route, makes the conversion, and forwards the result to the project’s payment contract. It does not keep a project balance.
JBPayRouteResolver compares available routes by the tokens the payer would receive. A route may forward an accepted asset, swap through Uniswap V3 or V4, cash out other Juicebox tokens, or combine these steps. Availability depends on the project, asset, and market; check previewPayFor before offering the route.
Router terminal functions
- pay(...)
- Same IJBTerminal interface as JBMultiTerminal — resolves the best route, converts the input, then calls pay() on the destination terminal.
- addToBalanceOf(...)
- Same as pay() but forwards via addToBalanceOf() on the destination terminal (no token minting).
- previewPayFor(...)
- Preview the chosen route and expected output for a payment without executing it.
- bestPoolLiquidityOf(tokenA, tokenB)
- Report the deepest-liquidity Uniswap pool the router would use for a pair.
Projects attach JBRouterTerminalRegistry to JBDirectory alongside JBMultiTerminal. Read terminalOf(projectId) to find the selected route: current deployments use JBRouterTerminalGateway → ROUTER(), while unmigrated projects can still select the previous router. The gateway takes custody before routing. Eligible failed fee routes remain pending for retry instead of being settled or forgiven; finalization can refund the source project after qualified failures. Use canonical deployment records per chain: a proposed mainnet deployment is not a live gateway.
31. Create an address that forwards payments
For: Project buildersContract builders
A payer address forwards incoming ETH to a project. Deploy JBProjectPayer as a small copy of a shared implementation, called a clone. Its constructor takes JBDirectory; set defaults through initialize() or setDefaultValues().
When defaultAddToBalance is false, incoming ETH calls pay() and delivers project tokens to the beneficiary. When true, it adds funds without creating tokens. With no chosen beneficiary, the contract finds the original payer, including through supported forwarding contracts. ERC-20 payments need an explicit pay() or addToBalanceOf() call after approval; sending those tokens directly will not trigger a payment.
// Set via initialize() after clone deployment:
defaultProjectId // which project to forward to
defaultBeneficiary // who gets the tokens (0 = msg.sender)
defaultMemo // attached to each payment
defaultMetadata // extra data for hooks
defaultAddToBalance // false = pay(), true = addToBalance()
// Anyone sends ETH to the payer address:
// → receive() fires
// → looks up DIRECTORY.primaryTerminalOf(projectId, token)
// → calls pay() or addToBalanceOf() with defaults32. Give a project a name
For: Project buildersApp builders
A project can use a readable Ethereum Name Service (ENS) name. JBProjectHandles checks that the name’s record also points back to the project before returning it from handleOf().
Anyone can propose a name. The registry stores each proposal by its setter, chain ID, and project ID. Apps should also check that the project’s current owner or operator claimed the name.
Handle functions
- setEnsNamePartsFor(chainId, projectId, parts[])
- Associate ENS name parts with a project. Anyone can call this — no access control.
- ensNamePartsOf(chainId, projectId, setter)
- Get the stored name parts as set by a specific setter address.
- handleOf(chainId, projectId, setter)
- Returns the verified handle string, or empty if ENS text record doesn’t match.
- TEXT_KEY
- The ENS text record key: "juicebox". Expected value: "{chainId}:{projectId}".
Name parts are in reverse order. handleOf returns the dot-joined labels without a .eth suffix; the suffix is only added when computing the namehash for verification. `_formatHandle` (JBProjectHandles.sol:220-232) walks the array from the LAST element to the first, so the innermost label goes last. For "myproject.eth" → ["myproject"]. For "sub.myproject.eth" → ["myproject", "sub"]. Parts cannot contain dots, ASCII control characters, DEL, "eth", or be empty. Unicode normalization (ENSIP-15) is the caller/client’s responsibility, not the contract’s.
33. Share rewards over time
For: Contract builders
A distributor shares reward tokens with eligible holders over time. Rewards unlock gradually, called vesting. Deploy this optional extension for your project; it is separate from the shared core contracts.
JBTokenDistributor supports tokens with IJBActiveVotes, such as Juicebox JBERC20. JB721Distributor supports NFT holders. Fund either through project distributions or direct deposits. Each round records the available rewards and holders’ eligible balances, then unlocks their shares evenly over the configured rounds.
Core functions
- fund(hook, token, amount)
- Directly deposit reward tokens for a specific hook’s staker pool
- beginVesting(hook, tokenIds[], tokens[])
- Snapshot and begin vesting for the specified token IDs
- collectVestedRewards(hook, tokenIds[], tokens[], beneficiary)
- Collect unlocked vested tokens (auto-vests current round too)
- releaseForfeitedRewards(hook, tokenIds[], tokens[], beneficiary)
- Return unvested rewards from burned tokens to the pool
- poke()
- Record the snapshot block for the current round early
Read state
- balanceOf(hook, token)
- Balance held for a hook’s staker pool
- collectableFor(hook, tokenId, token)
- How much is unlocked and ready to collect right now
- claimedFor(hook, tokenId, token)
- Total uncollected amount (vesting + vested-but-uncollected)
- currentRound()
- The current round number
- roundSnapshotBlock(round)
- The block number used for stake weight lookups
34. Test what can surprise you
- Launch: the encoded configuration round-trips through the ABI; the creation fee is read at send time; the same sender and salt yield the same sucker addresses on a second chain.
- Payments: the chosen route’s executable minimum is no worse than the alternatives shown; an empty pool falls back to issuance; a router route is probed, not assumed.
- Cash outs: the terminal or hook enforces the same minimum the confirmation shows, including zero-tax cash outs.
- Payouts: limits apply per chain and cycle; a recipient project without a payment contract can consume the limit without receiving funds.
- Rulesets: a queue with too little notice fails its approval hook; an inherited weight is decayed, not copied.
- Permissions: the narrowest ID is granted, scoped to the project; a Safe proposal is not success.
- Hooks: reentrancy from a hostile hook or token, an under-pulling split hook, hostile payer metadata.
Test against a local copy of the current deployments. Publish the addresses, source, user actions, and schedule of terms so people can check how your product works. The audit page has prompts for reviewing a whole product or one transaction.
35. Use this app as a reference
Use Juicebox Money’s source to build your own product. Its Next.js app shows how to find projects, load details and media, and prepare transactions against the current V6 contracts.
Start with a flow close to yours, such as Pay, Cash Out, Create, or Shop. Read its source and tests to see how it checks current data, prepares a request, and shows what the user will sign.
- Start from the product flow closest to yours — such as Pay, Cash Out, project creation, ruleset editing, or the Shop — and identify its component, supporting reads, and transaction builder.
- Give your assistant the relevant source, tests, guide link, V6 contracts, and Juicebox V6 skills library (github.com/mejango/juicebox-skills) to help it explain anything Juicebox.
- Use indexed data for browsing. Refresh the chain data the transaction depends on just before building and sending it.
- Prepare one request and check that encoding and decoding preserves its fields. Test the behavior your product depends on before asking a wallet to sign.
Use the app to study the experience and its source to see how it works. The server helps with search, media, and request preparation. The wallet signs only after the action is shown and checked against the V6 contract format.