@bananapus/swap-terminal
v0.0.21
Published
A Juicebox terminal that accepts any token and swaps it for ETH (or the appropriate native token, like matic).
Downloads
337
Readme
Swap Terminal
A Juicebox terminal that accepts any token and swaps it for ETH (or the appropriate native token, like matic).
If you're having trouble understanding this contract, take a look at the core protocol contracts and the documentation first. If you have questions, reach out on Discord.
Usage
Install
How to install nana-swap-terminal
in another project.
For projects using npm
to manage dependencies (recommended):
npm install @bananapus/swap-terminal
For projects using forge
to manage dependencies (not recommended):
forge install Bananapus/nana-swap-terminal
If you're using forge
to manage dependencies, add @bananapus/swap-terminal/=lib/nana-swap-terminal/
to remappings.txt
. You'll also need to install nana-swap-terminal
's dependencies and add similar remappings for them.
Develop
nana-swap-terminal
uses npm (version >=20.0.0) for package management and the Foundry development toolchain for builds, tests, and deployments. To get set up, install Node.js and install Foundry:
curl -L https://foundry.paradigm.xyz | sh
You can download and install dependencies with:
npm ci && forge install
If you run into trouble with forge install
, try using git submodule update --init --recursive
to ensure that nested submodules have been properly initialized.
Some useful commands:
| Command | Description |
| --------------------- | --------------------------------------------------- |
| forge build
| Compile the contracts and write artifacts to out
. |
| forge fmt
| Lint. |
| forge test
| Run the tests. |
| forge build --sizes
| Get contract sizes. |
| forge coverage
| Generate a test coverage report. |
| foundryup
| Update foundry. Run this periodically. |
| forge clean
| Remove the build artifacts and cache directories. |
To learn more, visit the Foundry Book docs.
Scripts
For convenience, several utility commands are available in package.json
.
| Command | Description |
| --------------------------------- | -------------------------------------- |
| npm test
| Run local tests. |
| npm run test:fork
| Run fork tests (for use in CI). |
| npm run coverage
| Generate an LCOV test coverage report. |
| npm run artifacts
| Fetch Sphinx artifacts and write them to deployments/
|
Deployments
With Sphinx
nana-buyback-hook
manages deployments with Sphinx. To run the deployment scripts, install the npm devDependencies
with:
`npm ci --also=dev`
You'll also need to set up a .env
file based on .example.env
. Then run one of the following commands:
| Command | Description |
| ------------------------- | ---------------------------- |
| npm run deploy:mainnets
| Propose mainnet deployments. |
| npm run deploy:testnets
| Propose testnet deployments. |
Your teammates can review and approve the proposed deployments in the Sphinx UI. Once approved, the deployments will be executed.
Without Sphinx
You can use the Sphinx CLI to run the deployment scripts without paying for Sphinx. First, install the npm devDependencies
with:
`npm ci --also=dev`
You can deploy the contracts like so:
PRIVATE_KEY="0x123…" RPC_ETHEREUM_SEPOLIA="https://rpc.ankr.com/eth_sepolia" npx sphinx deploy script/Deploy.s.sol --network ethereum_sepolia
This example deploys nana-buyback-hook
to the Sepolia testnet using the specified private key. You can configure new networks in foundry.toml
.
Tips
To view test coverage, run npm run coverage
to generate an LCOV test report. You can use an extension like Coverage Gutters to view coverage in your editor.
If you're using Nomic Foundation's Solidity extension in VSCode, you may run into LSP errors because the extension cannot find dependencies outside of lib
. You can often fix this by running:
forge remappings >> remappings.txt
This makes the extension aware of default remappings.
Repository Layout
The root directory contains this README, an MIT license, and config files. The important source directories are:
nana-swap-terminal/
├── src/
│ ├── JBSwapTerminal.sol - The main swap terminal contract.
│ └── interfaces/ - Contains wETH interface (do not modify).
├── script/
│ ├── Deploy.s.sol - Deployment script.
│ └── helpers/ - Internal helpers for the deployment script.
└── test/
├── Fork/ - Fork tests.
├── Invariant/ - Invariant tests (empty for now).
├── Units/ - Unit tests (empty for now).
└── helper/ - Contains a mock ERC-20 token for tests.
Other directories:
nana-swap-terminal/
├── .github/
│ └── workflows/ - CI/CD workflows.
└── deployments/ - Sphinx deployment logs.
Description
The JBSwapTerminal
accepts payments in any token. When the JBSwapTerminal
is paid, it uses a Uniswap pool to exchange the tokens it received for ETH (or the appropriate native token, like MATIC). Then, the swap terminal pays the project's primary ETH terminal with the ETH it received from the swap, forwarding the specified beneficiary to receive any tokens or NFTs minted by that payment.
EXAMPLE: One of the "Clungle" project's terminals accepts ETH and mints $CLNG tokens. The Clungle project also has a swap terminal. If Jimmy pays the Clungle project with USDC and sets his address as the payment's beneficiary, the swap terminal will swap the USDC for ETH. Then it pays that ETH into the ETH terminal, minting $CLNG tokens for Jimmy.
Flow
At a high level:
sequenceDiagram
participant Frontend client
participant JBDirectory
participant Uniswap pool
participant JBSwapTerminal
participant JBMultiTerminal
Note left of Frontend client: User attempts to pay project with USDC
Frontend client->>JBDirectory: Checks project's primary USDC terminal
JBDirectory->>Frontend client: Returns JBSwapTerminal if a USDC pool is available
Frontend client->>JBSwapTerminal: Calls pay(…) with USDC payment data (and optional Uniswap quote)
JBSwapTerminal->>JBDirectory: Checks project's primary ETH terminal
JBDirectory->>JBSwapTerminal: Returns JBMultiTerminal for ETH
JBSwapTerminal->>Uniswap pool: Swaps USDC for ETH
JBSwapTerminal->>JBMultiTerminal: Pays ETH to JBMultiTerminal
Note right of JBMultiTerminal: Mint tokens for original beneficiary
- The user attempts to pay a project with USDC through a frontend client. The client calls
JBDirectory.primaryTerminalOf(…)
to get the project's primary USDC terminal. This only returns the swap terminal if:- The project does not have another terminal as their primary USDC terminal, AND
- Either the project's owner has set up a Uniswap USDC/ETH pool for the swap terminal to use (by calling
JBSwapTerminal.addDefaultPool(…)
), or the swap terminal's owner has set up a default USDC/ETH pool for the swap terminal to use.
- Optionally, if no terminal was found, the frontend may call
JBDirectory.isTerminalOf(…)
to check if the project has a swap terminal but no pool set. If this is the case, the frontend can still use the swap terminal by passing in a Uniswap pool in the payment metadata. - The client calls the swap terminal's
pay(…)
function. In the payment metadata, the client may pass in a quote containing a Uniswap pool to use and the minimum number of project tokens to receive in exchange from payment. These should be encoded using theJBMetadataResolver
format. Frontend clients can usejuice-sdk-v4
to encode metadata in this format.- If no quote is provided, the swap terminal use the pool set by the project's owner.
- If the project's owner hasn't set a pool, the swap terminal will use a default pool set by the swap terminal's owner.
- If it still can't find a pool, the swap terminal will revert.
- The swap terminal calls
JBDirectory.primaryTerminalOf(…)
to get the project's primary ETH terminal. If the project doesn't have an ETH terminal, the swap terminal reverts. - The swap terminal swaps the USDC for ETH using the pool described in step 2.
- The swap terminal pays the project's primary ETH terminal with the ETH it received from the swap, forwarding the beneficiary to receive any tokens or NFTs minted by that payment.
Terminals
A Juicebox project's terminals are the entry point for operations involving inflows and outflows of funds – payments, redemptions, payouts, and surplus allowance usage. Each project can use multiple terminals, and a single terminal contract deployed to a specific address can be used by many projects.
A project's terminals can be read or updated through the JBDirectory
, which lists each project's current controller and terminals. It also stores their primary terminals. The primary terminal for a given token is where payments in that token are routed to by default.
Some useful functions in the JBDirectory
:
- To set a project's terminals, use
JBDirectory.setTerminalsOf(…)
. For example, to set up a project with aJBMultiTerminal
and aJBSwapTerminal
, you would call this function with the project's ID and an array containing the address of each terminal. - To check a project's primary terminal for a token, use
JBDirectory.primaryTerminalOf(…)
. Project owners can set their primary terminal for a token usingJBDirectory.setPrimaryTerminalOf(…)
. - To check whether a project has a given terminal, use
JBDirectory.isTerminalOf(…)
.
The most commonly used terminal, from nana-core
, is the JBMultiTerminal
. It's a generic terminal which manages payments, redemptions, payouts, and surplus allowance spending in native/ERC-20 tokens
Payment Metadata
The JBSwapTerminal
accepts encoded metadata
in its pay(…)
function. This metadata is decoded using the JBMetadataResolver
like so:
(bool exists, bytes memory quote) =
JBMetadataResolver.getDataFor(JBMetadataResolver.getId("quoteForSwap"), metadata);
// If there's a quote, use it.
if (exists) {
// If there is a quote, use it for the swap config.
(minAmountOut, pool) = abi.decode(quote, (uint256, IUniswapV3Pool));
What's happening here:
JBMetadataResolver.getId("quoteForSwap")
returns a unique 4-byte identifier where the quote is stored in the metadata.JBMetadataResolver.getDataFor(…, metadata)
attempts to retrieve the data associated with the ID from the metadata.getDataFor(…)
returns a tuple with two values:exists
: A boolean indicating whether the quote was found in the metadata.quote
: A bytes array containing theminAmountOut
and the Uniswappool
to use.
- If the data exists, the swap terminal decodes the quote and uses the
minAmountOut
andpool
for the payment.
Frontend clients can use juice-sdk-v4
to encode metadata in this format.