jb-terminal-wrapper
Terminal wrapper pattern for extending JBMultiTerminal functionality. Use when: (1) need dynamic splits at pay time, (2) revnet can't modify ruleset data hooks, (3) want atomic pay + distribute operations, (4) need to intercept/redirect tokens before delivery, (5) implementing pay-time configuration, (6) cash out + bridge/swap in one tx, (7) cash out + stake redeemed funds. Covers IJBTerminal implementation, _acceptFunds pattern from JBSwapTerminalRegistry, beneficiary manipulation for both pay and cash out flows, and the critical mental model that wrappers are additive (not restrictive).
Install
mkdir -p .claude/skills/jb-terminal-wrapper && curl -L -o skill.zip "https://mcp.directory/api/skills/download/8228" && unzip -o skill.zip -d .claude/skills/jb-terminal-wrapper && rm skill.zipInstalls to .claude/skills/jb-terminal-wrapper
About this skill
Terminal Wrapper Pattern
Problem
Revnets and other projects often need extended payment functionality that can't be achieved through ruleset data hooks alone. Common needs include:
- Dynamic splits specified at payment time
- Token interception and redirection
- Atomic multi-step operations (pay + distribute)
- Client-specific features without breaking permissionless access
Context / Trigger Conditions
Apply this pattern when:
- Building payment flows that need dynamic configuration
- Working with revnets where ruleset hooks can't be edited
- Need to bundle multiple operations atomically
- Want to intercept tokens for further processing
- Implementing "pay and do X" flows
Solution
Core Architecture
Create a custom IJBTerminal that wraps JBMultiTerminal. Use a shared _acceptFunds helper
(pattern from JBSwapTerminalRegistry) to handle ETH/ERC20 consistently:
contract PayWithSplitsTerminal is IJBTerminal {
using SafeERC20 for IERC20;
IJBMultiTerminal public immutable MULTI_TERMINAL;
IJBController public immutable CONTROLLER;
constructor(IJBMultiTerminal _multiTerminal, IJBController _controller) {
MULTI_TERMINAL = _multiTerminal;
CONTROLLER = _controller;
}
function pay(
uint256 projectId,
address token,
uint256 amount,
address beneficiary,
uint256 minReturnedTokens,
string calldata memo,
bytes calldata metadata
) external payable returns (uint256 beneficiaryTokenCount) {
// 1. Parse custom metadata
(JBSplit[] memory splits, bytes memory innerMetadata) = _parseMetadata(metadata);
// 2. Configure splits if provided
if (splits.length > 0) {
_configureSplits(projectId, splits);
}
// 3. Accept funds (handles ETH/ERC20 uniformly)
uint256 valueToSend = _acceptFunds(token, amount, address(MULTI_TERMINAL));
// 4. Forward to underlying terminal
beneficiaryTokenCount = MULTI_TERMINAL.pay{value: valueToSend}(
projectId,
token,
amount,
beneficiary,
minReturnedTokens,
memo,
innerMetadata
);
// 5. Distribute reserved tokens
CONTROLLER.sendReservedTokensToSplitsOf(projectId);
return beneficiaryTokenCount;
}
/// @notice Accept funds from caller and prepare for forwarding.
/// @dev Pattern from JBSwapTerminalRegistry - consolidates token handling.
function _acceptFunds(
address token,
uint256 amount,
address spender
) internal returns (uint256 valueToSend) {
if (token == JBConstants.NATIVE_TOKEN) {
return msg.value; // Forward ETH
}
// ERC20: pull from sender, approve spender
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
IERC20(token).forceApprove(spender, amount);
return 0; // No ETH to forward
}
}
Beneficiary Manipulation Pattern
Intercept tokens by setting beneficiary to the wrapper itself:
function payAndStake(
uint256 projectId,
address token,
uint256 amount,
uint256 minReturnedTokens,
bytes calldata metadata
) external payable returns (uint256 tokenCount) {
// Parse user's desired destination from metadata
(address finalDestination, bytes memory stakingParams) = abi.decode(
metadata,
(address, bytes)
);
// Receive tokens to this contract
tokenCount = MULTI_TERMINAL.pay{value: msg.value}(
projectId,
token,
amount,
address(this), // <-- Wrapper receives tokens
minReturnedTokens,
"",
""
);
// Do something with the tokens
IERC20 projectToken = IERC20(CONTROLLER.TOKENS().tokenOf(projectId));
// Example: stake them somewhere on behalf of user
_stakeTokens(projectToken, tokenCount, finalDestination, stakingParams);
return tokenCount;
}
Metadata Encoding (Client Side)
import { encodeAbiParameters, parseAbiParameters } from 'viem';
// For dynamic splits
const metadata = encodeAbiParameters(
parseAbiParameters('(address preferredBeneficiary, uint256 percent, uint256 lockedUntil)[], bytes'),
[
[
{ preferredBeneficiary: '0x...', percent: 500000000n, lockedUntil: 0n }, // 50%
{ preferredBeneficiary: '0x...', percent: 500000000n, lockedUntil: 0n }, // 50%
],
'0x' // Inner metadata for MultiTerminal
]
);
// For beneficiary redirection
const metadata = encodeAbiParameters(
parseAbiParameters('address finalDestination, bytes stakingParams'),
[userAddress, stakingCalldata]
);
Critical Mental Model
┌─────────────────────────────────────────────────────────────────┐
│ WRAPPER IS ADDITIVE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client A ──► PayWithSplitsTerminal ──► JBMultiTerminal │
│ (gets special features) │
│ │
│ Client B ────────────────────────────► JBMultiTerminal │
│ (still works!) │
│ │
│ BOTH ARE VALID. The wrapper cannot block direct access. │
│ This is a FEATURE, not a bug. Permissionless = good. │
│ │
└─────────────────────────────────────────────────────────────────┘
Bad thinking: "I'll use a wrapper to block payments that don't meet criteria X"
Reality: Users can always call JBMultiTerminal.pay() directly
Good thinking: "I'll use a wrapper to provide enhanced functionality for clients that opt in"
Use Cases
| Use Case | How Wrapper Helps |
|---|---|
| Pay Wrappers | |
| Dynamic splits at pay time | Parse splits from metadata, configure before pay |
| Pay + distribute reserved | Atomic operation, no separate tx needed |
| Token interception | Receive to self, then stake/lock/forward |
| Referral tracking | Parse referrer from metadata, record on-chain |
| Conditional logic | Check conditions before forwarding to MultiTerminal |
| Multi-hop payments | Receive tokens, swap, pay another project |
| Cash Out Wrappers | |
| Cash out + bridge | Intercept redeemed funds, bridge to another chain |
| Cash out + swap | Swap redeemed ETH to stablecoin before delivery |
| Cash out + stake | Stake redeemed funds in another protocol |
| Cash out + LP | Add redeemed funds to liquidity pool |
Cash Out Wrapper Pattern
Same beneficiary-to-self trick works for cash outs:
/// @notice Cash out with automatic swap to different token.
function cashOutAndSwap(
address holder,
uint256 projectId,
uint256 tokenCount,
address tokenToReclaim,
uint256 minTokensReclaimed,
address tokenOut, // Custom param: swap to this
uint256 minAmountOut, // Custom param: slippage
address beneficiary,
bytes calldata metadata
) external returns (uint256 amountOut) {
// 1. Cash out to THIS contract (intercept funds)
uint256 reclaimAmount = MULTI_TERMINAL.cashOutTokensOf(
holder,
projectId,
tokenCount,
tokenToReclaim,
minTokensReclaimed,
address(this), // <-- Wrapper receives redeemed funds
metadata
);
// 2. Swap redeemed tokens to desired output
amountOut = _swap(tokenToReclaim, tokenOut, reclaimAmount, minAmountOut);
// 3. Send swapped tokens to beneficiary
_sendFunds(tokenOut, amountOut, beneficiary);
return amountOut;
}
/// @notice Cash out with automatic bridging.
function cashOutAndBridge(
address holder,
uint256 projectId,
uint256 tokenCount,
address tokenToReclaim,
uint256 minTokensReclaimed,
address beneficiary,
uint256 destChainId,
bytes calldata metadata
) external returns (uint256 reclaimAmount) {
// 1. Cash out to this contract
reclaimAmount = MULTI_TERMINAL.cashOutTokensOf(
holder,
projectId,
tokenCount,
tokenToReclaim,
minTokensReclaimed,
address(this),
metadata
);
// 2. Bridge funds to destination chain
_bridgeFunds(tokenToReclaim, reclaimAmount, beneficiary, destChainId);
return reclaimAmount;
}
Comparison with Swap Terminal
Swap Terminal is a canonical example of this pattern:
User pays with USDC ──► SwapTerminal ──► Swaps to ETH ──► JBMultiTerminal
(wraps + transforms)
Your wrapper follows the same architecture but with different transformation logic.
Verification
- Deploy wrapper pointing to existing JBMultiTerminal
- Test that direct MultiTerminal payments still work (permissionless)
- Test that wrapper payments get enhanced behavior
- Verify atomic operations complete or revert together
- Test metadata parsing edge cases (empty, malformed)
Example
Complete implementation for pay-time splits:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IJBTerminal} from "@bananapus/core/src/interfaces/IJBTerminal.sol";
import {IJBMultiTerminal} from "@bananapus/core/src/interfaces/IJBMultiTerminal.sol";
import {IJBController} from "@bananapus/core/src/interfaces/IJBController.sol";
import {IJBSplits} from "@bananapus/core/src/interfaces/IJBSplits.sol";
import {JBSplit} from "@bananapus/core/src/structs/JBSplit.sol";
import {JBSplitGroup} from "@bananapus/core/src/structs/JBSplitGroup.sol";
contract DynamicSplitsTerminal is IJBTerminal {
IJBMultiTerminal public immutable MULTI_TERMINAL;
IJBController public immutable CONTROLLER;
// Split group ID for reserved tokens
uint256 constant RES
---
*Content truncated.*
More by openclaw
View all skills by openclaw →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversTerminal control, file system search, and diff-based file editing for Claude and other AI assistants. Execute shell comm
Desktop Commander MCP unifies code management with advanced source control, git, and svn support—streamlining developmen
Cipher empowers agents with persistent memory using vector databases and embeddings for seamless context retention and t
Securely join MySQL databases with Read MySQL for read-only query access and in-depth data analysis.
ToolFront is a knowledge database software and db management system offering unified access to databases with advanced t
Context Portal: Manage project memory with a database-backed system for decisions, tracking, and semantic search via a k
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.