Build

Implement Revnet V6 operations

Connect your product to revnet deployment, payments, cash outs, loans, shops, operator controls, and multichain settlement with the same code paths users can inspect and verify.

Building with an agent? .

1

Wire the V6 surface

Start from the current SDK, generated ABIs, and deployed-address registry; do not hand-maintain selectors or addresses in product code.

A revnet is a Juicebox V6 project on each chain plus Revnet contracts for deployment, immutable stage ownership, loans, and operator controls. Model its local identity as chain ID plus project ID. A sucker group can connect those local projects, but it does not make their addresses, balances, stage IDs, or writes interchangeable.

Use the V6 SDK builders as pure transaction constructors. Keep reads on a chain-specific public client, writes on a wallet client connected to that same chain, and amounts as bigint atomic units until the display boundary.

Code point

Import builders and generated contract surfaces

SDK
@bananapus/nana-sdk-core/v6
ABIs + addresses
@bananapus/nana-sdk-core
Local identity
{ chainId: JBChainId, projectId: bigint }
Amounts
bigint in the token or protocol field's declared decimals
import {
  build721RulesetMetadata,
  buildAutoIssueTx,
  buildBorrowTx,
  buildBridgeClaimTx,
  buildBridgePrepareTx,
  buildBurnTokensTx,
  buildCashOutTx,
  buildClaimTokensTx,
  buildDeployRevnetTx,
  buildPayTx,
  buildRepayLoanTx,
  buildRevnetStageConfig,
  buildSyncAccountingDataTx,
  buildToRemoteTx,
  getBorrowableAmount,
  prepareHookAwareCashOut,
  previewPay,
  REV_METADATA_ALLOW_SUCKER_DEPLOYMENT,
  slippageFloor,
} from "@bananapus/nana-sdk-core/v6";

import {
  jbControllerAbi,
  jbMultiTerminalAbi,
  revLoansAbi,
  type JBChainId,
} from "@bananapus/nana-sdk-core";

V6 SDK packageJuicebox V6 sourceRevnet V6 source

2

Read the revnet before operating it

Discovery data can come from an indexer; anything used to construct a signature must be refreshed from the target chain.

Resolve the controller, terminals, accounting contexts, token, current stage, full committed stage schedule, splits, sucker peers, operator, and relevant permission IDs. Cache successful reads by chain and contract identity, then invalidate the affected keys after a confirmed write.

Render previously known names, logos, and project facts while RPC reads refresh. Treat missing onchain state as unknown—not zero, empty, or permissionless.

Code point

Signing-critical read map

Directory
JBDirectory.controllerOf / terminalsOf / primaryTerminalOf
Stage
JBController.currentRulesetOf / JBRulesets.getRulesetOf
Accepted tokens
JBMultiTerminal.accountingContextsOf
Supply
JBTokens.totalSupplyOf / totalBalanceOf / creditBalanceOf
Splits
JBSplits.splitsOf(projectId, rulesetId, groupId)
Cash out
JBMultiTerminal.previewCashOutFrom
Loans
REVLoans.borrowableAmountFrom / loanOf
Peers
JBSuckerRegistry.suckerPairsOf
const [controller, terminals, contexts, stage] = await publicClient.multicall({
  allowFailure: false,
  contracts: [
    controllerOf(projectId),
    terminalsOf(projectId),
    accountingContextsOf(projectId),
    currentRulesetOf(projectId),
  ],
});

// Re-run the reads used by a quote immediately before simulateContract.

Reference project readsStage reads

3

Deploy the immutable schedule

Encode every stage and every chain deliberately, then simulate the exact REVDeployer.deployFor overload shown in review.

Build accounting contexts with token-keyed currency IDs. Build each stage with its absolute start, issuance, issuance cut, cash-out tax, split bucket, recipients, auto-issuance, and metadata flags. The full auto-issuance list participates in the cross-chain encoded configuration even though each chain only mints its local rows.

Use the explicit tiered-721 overload when a custom reserve token needs non-18 shop-price decimals. Filter overloaded tuple-heavy ABIs to the selected argument count before encoding, simulation, review, and submission so each boundary uses the same selector.

Code point

REVDeployer.deployFor

Builder
buildRevnetStageConfig → buildDeployRevnetTx
Contract
REVDeployer
Function
deployFor
Value
project creation fee returned on the request
No operator
0xdead000000000000000000000000000000000000
const stage = buildRevnetStageConfig({
  startsAtOrAfter,
  initialIssuance,              // 18-decimal project-token weight
  issuanceCutFrequency,         // seconds
  issuanceCutPercent,           // protocol units
  cashOutTaxRate,               // protocol units
  splitPercent,                 // basis points
  splits: encodedSplits,          // row percents sum to SPLITS_TOTAL_PERCENT
  autoIssuances,
  extraMetadata:
    build721RulesetMetadata({ pauseTransfers: true }) |
    REV_METADATA_ALLOW_SUCKER_DEPLOYMENT,
});

const tx = buildDeployRevnetTx({
  chainId,
  config: {
    description: { name, ticker, uri, salt },
    baseCurrency,
    operator,
    scopeCashOutsToLocalBalances: false,
    stageConfigurations: [stage],
  },
  accountingContexts,
  suckerConfig: { deployerConfigurations, salt },
  creationFee,
});

Complete deploy builderDeploy execution boundary

Discard and rebuild a deployment quote whose first-stage start has passed. A stale first stage can activate REVDeployer's seven-day cash-out and loan lock.
4

Quote and execute payments

Preview the live terminal path, compare any executable market route, then bind the chosen route with a minimum project-token output.

A terminal payment may issue new tokens or route through the configured buyback hook. A direct market swap is a different transaction and bypasses the stage split. Compare executable, slippage-protected minimums—not optimistic chart prices—and explain which route the wallet will sign.

For ERC-20 payments, approve only the request's actual spender and only the required amount. Native-token requests carry the amount in value. Shop purchases are still payments; build tier metadata and include every NFT plus the fungible-token result in the confirmation.

Code point

JBMultiTerminal.pay

Quote
previewPay(publicClient, …) → previewPayFor
Builder
buildPayTx
Function
JBMultiTerminal.pay
Bound
minReturnedTokens
Shop metadata
build721PayMetadata
const quote = await previewPay(publicClient, {
  chainId, terminal, projectId, token, amount, beneficiary, metadata,
});

const tx = buildPayTx({
  chainId, terminal, projectId, token, amount, beneficiary, metadata,
  minReturnedTokens: slippageFloor(quote.beneficiaryTokenCount, 100n),
  memo,
});

// ERC-20: approve tx.address. Native: tx.value === amount.

Payment route and approval flowRoute preview helpers

Code point

Add funds without issuing tokens

Contract
JBMultiTerminal
Function
addToBalanceOf
Constraint
only a token accepted directly by that terminal
5

Cash out through the live route

Use the hook-aware terminal preview; a surplus-only calculation can disagree with the transaction that will actually execute.

JBMultiTerminal.previewCashOutFrom runs the real data-hook and buyback decision. Its route determines where the minimum belongs: minTokensReclaimed on the treasury path, or buyback metadata on the AMM path. Re-quote after any stage, supply, balance, pool, hook, or fee change.

Internal credits and claimed ERC-20 tokens have different direct-market capabilities. Only compare a direct swap for the claimed balance that the router can actually spend.

Code point

JBMultiTerminal.cashOutTokensOf

Prepare
prepareHookAwareCashOut → previewCashOutFrom + buildCashOutTx
Treasury bound
route.terminalMinimum
AMM bound
route.metadata
Token count
18-decimal revnet-token bigint
const prepared = await prepareHookAwareCashOut(publicClient, {
  chainId, terminal, holder, projectId, cashOutCount, tokenToReclaim,
  beneficiary,
});

const { route, transaction: tx } = prepared;
// AMM routes are re-previewed with their slippage metadata before return.

Cash-out implementationHook-aware quote

Code point

Token-account operations

Claim credits
buildClaimTokensTx → JBController.claimTokensFor
Burn
buildBurnTokensTx → JBController.burnTokensOf
Auto-issue
buildAutoIssueTx → REVOwner.autoIssueFor
6

Open, repay, and reallocate loans

Loan UI must derive its bounds from current collateral capacity, fees, source token, and permission state—not from a cached cash-out estimate.

Before borrowing, read borrowableAmountFrom in the selected accounting context and apply a non-zero minimum. Grant REVLoans only BURN_TOKENS permission ID 11; never grant ROOT. The collateral and source token are chain-local.

Before repayment, re-read loanOf and the source fee, compute a conservative maximum, approve or permit the source token if necessary, and simulate the exact collateral amount being returned. Native-token repayment sends the ceiling as value; excess is refunded.

Code point

REVLoans.borrowFrom

Quote
getBorrowableAmount / borrowableAmountFrom
Builder
buildBorrowTx
Permission
JBPermissions BURN_TOKENS = 11
Bound
minBorrowAmount
const { borrowableNow } = await getBorrowableAmount(publicClient, {
  chainId, revnetId, collateralCount, decimals, currency,
});

const tx = buildBorrowTx({
  chainId, revnetId, token, collateralCount, beneficiary, holder,
  prepaidFeePercent,
  minBorrowAmount: slippageFloor(borrowableNow, 100n),
});

Protected loan buildersBorrow operation

Code point

REVLoans.repayLoan

Builder
buildRepayLoanTx
Bound
maxRepayBorrowAmount; excess is refunded
Partial repay
collateralCountToReturn; may mint a replacement loan NFT
ERC-20
Permit2 allowance or prior approval

Repayment implementation

7

Implement the limited operator surface

Resolve permission per chain and expose only the operations the immutable deployment granted; an operator is never a revnet owner.

Build each operator write from freshly resolved contracts, operator address, permission IDs, and project state. Simulate each chain independently. A multisig proposal is pending until its Safe transaction executes onchain; do not invalidate state or show success at proposal creation.

Shop tiers are separate from stage economics. Tier transferability is fixed at creation, while operator ability to add tiers, update metadata, change discounts, or mint depends on the deployed hook flags and permissions.

Code point

Operator write map

Metadata
JBController.setUriOf
Split redirect
JBController.setSplitGroupsOf
Transfer role
REVOwner.setOperatorOf
Add shop tiers
JB721TiersHook.adjustTiers
Operator mint
JB721TiersHook.mintFor
Buyback hook
JBBuybackHookRegistry.setHookFor
Router terminal
JBRouterTerminalRegistry.setTerminalFor
TWAP
JBBuybackHook.setTwapWindowOf
Initialize pool
JBBuybackHookRegistry.initializePoolFor
Add chains
REVDeployer.deploySuckersFor
const { request } = await publicClient.simulateContract({
  account: operator,
  address: hook,
  abi: jb721TiersHookAbi,
  functionName: "adjustTiers",
  args: [tierConfigurations, tiersToRemove],
});

const hash = await walletClient.writeContract(request);

Operator transfer implementationShop tier writes

For a permanently authority-free revnet, deploy the operator as 0xdead000000000000000000000000000000000000. The zero address and an empty UI field are not equivalent.
8

Move tokens and settle accounting across chains

Treat sucker movement as a multi-transaction state machine: prepare, send, prove, claim, and separately sync accounting snapshots.

A prepared movement is not delivered value. Track its source sucker, peer sucker, token mapping, leaf index, beneficiary bytes32, proof, transport, fees, and status. CCIP and native bridge families have different value requirements and delivery times; discover payable value by simulating the exact call rather than guessing.

Accounting gossip can change displayed group backing without moving the local terminal balance. Keep queued, in-transit, claimable, claimed, failed, and retriable states distinct.

Code point

Sucker transaction sequence

1. Prepare
buildBridgePrepareTx → sucker.prepare
2. Send
buildToRemoteTx → sucker.toRemote
3. Claim
buildBridgeClaimTx → peerSucker.claim
Accounting
buildSyncAccountingDataTx → sucker.syncAccountingData
Peer discovery
getV6SuckerPairs
const prepare = buildBridgePrepareTx({
  chainId, sucker, projectTokenCount, beneficiary,
  minTokensReclaimed, token, metadata,
});

const send = buildToRemoteTx({ chainId, sucker, token, value: bridgeFee });
const claim = buildBridgeClaimTx({ chainId: peerChainId, sucker: peer, claim: proof });
const sync = buildSyncAccountingDataTx({ chainId, sucker, value: syncFee });

Protected prepare builderProof and claim flowSettlement state machine

9

Use one reviewed transaction boundary

The request you quote, simulate, decode, review, and submit must be the same request—not five similar reconstructions.

Make every operation builder pure: validated input in; chain ID, address, ABI, function name, arguments, and value out. Immediately before signing, refresh the reads that establish bounds and permissions, rebuild, simulate with the actual account, ABI-encode and decode the calldata, then present it for review.

After submission, distinguish wallet rejection, Safe proposal, onchain inclusion, reverted execution, and confirmed success. Only confirmed execution should invalidate reads and move the UI to its final state.

Code point

Build → simulate → decode → write → confirm

const tx = buildOperation(freshState, userInput);

const { request } = await publicClient.simulateContract({
  ...tx,
  account,
});

const calldata = encodeFunctionData(tx);
const decoded = decodeFunctionData({ abi: tx.abi, data: calldata });
await review({ ...tx, calldata, decoded });

const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") throw new Error("Transaction reverted");

Review decoderWrite-site inventory check

10

Test the contract-facing invariants

Test the same builders your product ships, at the boundaries where immutable economics, route changes, permissions, and asynchronous settlement can surprise it.

Round-trip every builder through its ABI and fork-test it against current deployments. Cover stale stage starts, exact stage boundaries, split rounding, custom decimals, changed allowance, changed route, empty pool, changed cash-out hook, zero fee-free surplus, partial loan repayment, Safe proposal without execution, partial multichain deployment, and delayed claims.

Publish the contract addresses, source repositories, transaction map, and human-readable stage schedule users need to independently verify your implementation.

  • Deployment: the encoded configuration is byte-consistent where required and every overload round-trips
  • Payments: the chosen route's executable minimum is no worse than the alternatives shown
  • Cash outs: the terminal or hook enforces the same minimum the confirmation displays
  • Loans: only permission ID 11 is granted and repayment ceilings cannot underpay the live obligation
  • Operator: no exposed call can rewrite committed stage issuance or cash-out economics
  • Multichain: one chain's project, token, decimals, operator, or proof is never reused on another

Revnet contractsJuicebox contractsReference web clientUser-facing verification model

Start from the smallest operation your product needs, copy its reference pattern, and keep the live read, pure builder, simulation, review, and confirmation boundaries intact. The complete working implementation is in the Revnet Money repository.