# Commands: import, deposit, provide liquidity

These use Foundry's `cast` and the planner in `../scripts/liquidity.mjs`. Any equivalent client works if it sends the same calls. Run every `cast call` read as shown. Before each `cast send`, run the same call as `cast call --from "$ME"` to simulate it.

## 0. Environment

Pick the network's row from the deployment table in [SKILL.md](../SKILL.md#deployments). Base is shown here:

```sh
export ETH_RPC_URL=https://…               # the user's Base RPC; cast reads it automatically
export REGISTRY=0x3dCd35945Dc86a9FaA80846B06CB4676961d0AEa
export ROUTER=0x91aad599EfD70E633d091FC060cc6f9D3e5298BE
export WETH=0x4200000000000000000000000000000000000006
export POOL_MANAGER=0x498581fF718922c3f8e6A244956aF099B2652b2b
export CHAIN=8453
export NFT=0x…                             # the ERC-721 contract
export ME=0x…                              # the authorized signing wallet
export SIGN="--account my-keystore"        # or --ledger; never --private-key on the command line
export LP="node path/to/jpeg-markets-import/scripts/liquidity.mjs"
```

## 1. Verify the deployment

Stop if any of these differ from the deployment table.

```sh
cast chain-id                                                      # 8453
cast keccak "$(cast code $REGISTRY)"                               # registry runtime keccak
cast keccak "$(cast code $ROUTER)"                                 # router runtime keccak
cast call $REGISTRY "router()(address)"                            # $ROUTER
cast call $REGISTRY "quote()(address)"                             # $WETH
cast call $REGISTRY "poolManager()(address)"                       # $POOL_MANAGER
cast call $REGISTRY "poolGuard()(address)"                         # 0x0000000000000000000000000000000000000000
cast call $REGISTRY "poolFee()(uint24)"                            # 10000
cast call $REGISTRY "poolTickSpacing()(int24)"                     # 200
cast call $REGISTRY "maxBatch()(uint256)"                          # 50
cast call $ROUTER "registry()(address)"                            # $REGISTRY
```

## 2. Inspect the collection

```sh
curl -s "https://api.jpeg.markets/v1/discovery/$CHAIN/import/inspect?nft=$NFT"
cast call $REGISTRY "registrationOf(address)(bytes32)" $NFT
```

- `compatibility.status` must be `supported`. Otherwise report `compatibility.reasons` and stop. The registry accepts only contracts that declare ERC-721 through ERC-165 and are not ERC-1155.
- Show every entry of `compatibility.warnings` (`title` and `detail`) to the user before importing or depositing. They describe what the issuer can still do to the NFTs in the vault.
- `registrationOf` is zero: not imported yet, so go to step 3. Nonzero: it is the registration ID, so set `ID` to it and skip to the reads at the end of step 3.

## 3. Import

```sh
cast call --from $ME $REGISTRY "importCollection(address)(bytes32)" $NFT     # simulate; prints the ID
cast send $SIGN $REGISTRY "importCollection(address)" $NFT
export ID=$(cast call $REGISTRY "registrationOf(address)(bytes32)" $NFT)
cast call $REGISTRY "registrations(bytes32)((address,address,address,address,uint256,bytes32,bytes32,uint64))" $ID
# (actor, nft, vault, token, maxBatch, nftRuntimeHash, termsHash, registeredAt)
export VAULT=0x…   # third field
export FLOOR=0x…   # fourth field: the collection token
cast call $REGISTRY "predict(address)(bytes32,address,address)" $NFT      # must equal (ID, VAULT, FLOOR)
cast call $FLOOR "symbol()(string)"
```

The receipt carries `ImportRegistered(id, nft, actor, vault, floor, termsHash)`. A second `importCollection` for the same NFT reverts with `AlreadyRegistered()`.

## 4. Deposit NFTs

At most 50 IDs per call, each owned by `$ME` and not already in the vault.

```sh
cast call $NFT "ownerOf(uint256)(address)" 123                              # $ME, for each ID
cast send $SIGN $NFT "approve(address,uint256)" $VAULT 123                  # once per ID
# or, for many IDs: cast send $SIGN $NFT "setApprovalForAll(address,bool)" $VAULT true   (revoke afterwards)
cast call --from $ME $VAULT "deposit(uint256[],address)(uint256)" "[123,456]" $ME   # simulate: 2000000000000000000
cast send $SIGN $VAULT "deposit(uint256[],address)" "[123,456]" $ME
cast call $FLOOR "balanceOf(address)(uint256)" $ME                          # up by 1e18 per NFT
cast call $VAULT "inInventory(uint256)(bool)" 123                           # true
cast send $SIGN $NFT "setApprovalForAll(address,bool)" $VAULT false         # only if you set it
```

Never `transferFrom` an NFT to the vault directly. It strands the NFT with no tokens issued.

## 5. Read the pool

```sh
cast call $REGISTRY "poolKey(bytes32)((address,address,uint24,int24,address))" $ID
# (currency0, currency1, 10000, 200, 0x0…0): the token and WETH in address order
export POOL_ID=$(cast keccak $(cast abi-encode "f(address,address,uint24,int24,address)" <currency0> <currency1> 10000 200 0x0000000000000000000000000000000000000000))
export WORDS=$(cast call $POOL_MANAGER "extsload(bytes32,uint256)(bytes32[])" $(cast index bytes32 $POOL_ID 6) 4)
$LP state --floor $FLOOR --quote $WETH --pool-words "$WORDS"
```

`state` prints `initialized`, the current `price` (ETH per token), `tick`, active `liquidity`, and `tradable` (router trades work only when it's true). An uninitialized pool reads as zeros.

## 6. Plan

```sh
# Two-sided, full range, pool already priced:
$LP plan --floor $FLOOR --quote $WETH --pool-words "$WORDS" --mode both --tokens 2 --eth 0.05
# Two-sided between two prices:
$LP plan … --mode both --tokens 2 --eth 0.05 --low 0.01 --high 0.1
# Tokens only, from the current price up to 0.2 ETH per token:
$LP plan … --mode token --tokens 2 --high 0.2
# ETH only, from the current price down to 0.005:
$LP plan … --mode eth --eth 0.05 --low 0.005
# Opening an empty pool at 0.02 ETH per token with only tokens, up to 0.5:
$LP plan … --mode token --tokens 2 --start-price 0.02 --high 0.5
```

Options: `--low`/`--high` take a price, or `min`/`max` for no limit. Single-sided near edges default to the current price and far edges to 10× (tokens) or 1/10 (ETH) of it. `--tolerance` (percent, default 1, at most 50) bounds how far the pool price may move between review and inclusion. `--reprice` (with `--start-price`) moves an initialized pool's price through empty ranges before adding. `--deadline` sets a Unix time; the default is 20 minutes from now.

The output gives:

| Field | Use |
| --- | --- |
| `price`, `range.low`, `range.high` | What the user reviews, in ETH per token. `range.high` may be `unbounded`. |
| `deposit.tokens`, `deposit.eth` | What the position takes at the planned price. |
| `approve.tokenToRouter`, `approve.wethToRouter` | Raw approvals, and the most the router may pull. Anything unused is refunded in the same transaction. |
| `tickLower`, `tickUpper`, `liquidity` | Record them. Withdrawals and fee collection need the exact ticks. |
| `cast.params`, `cast.bounds` | Tuple arguments for `addLiquidityBounded`. |
| `notes` | Snapped opening prices, default edges, and any dust of the other asset. Tell the user. |

## 7. Fund and approve

```sh
cast call $WETH "balanceOf(address)(uint256)" $ME
cast send $SIGN $WETH "deposit()" --value <missing WETH, raw wei>          # wrap only what is missing
cast send $SIGN $FLOOR "approve(address,uint256)" $ROUTER <approve.tokenToRouter>
cast send $SIGN $WETH  "approve(address,uint256)" $ROUTER <approve.wethToRouter>
```

Skip an approval whose amount is 0.

## 8. Add liquidity

```sh
SIG="addLiquidityBounded(bytes32,(int24,int24,uint128,uint256,uint256,uint160,uint256),(bool,uint160,uint160,bool))"
cast call --from $ME $ROUTER "$SIG(uint256,uint256)" $ID "<cast.params>" "<cast.bounds>"   # simulate: (tokens used, WETH used)
cast send $SIGN $ROUTER "$SIG" $ID "<cast.params>" "<cast.bounds>"
cast call $ROUTER "positionLiquidity(bytes32,address,int24,int24)(uint128)" $ID $ME <tickLower> <tickUpper>
```

Confirm the receipt has `LiquidityChanged(id, owner = $ME, tickLower, tickUpper, liquidityDelta = liquidity)` from `$ROUTER`. When the pool was empty, it also has the PoolManager's `Initialize`; after `--reprice`, the router's `PoolRepriced`. Read step 5 again: `tradable` is true once a position covers the current price.

Adding again with the same ticks grows the same position. Different ticks make a separate position.

## 9. Manage

```sh
# Value and withdrawal minimums (1% slippage by default):
$LP value --floor $FLOOR --quote $WETH --pool-words "$WORDS" --tick-lower <lo> --tick-upper <hi> --liquidity <L>
# Collect fees only:
cast send $SIGN $ROUTER "collectFees(bytes32,int24,int24,address)" $ID <lo> <hi> $ME
# Withdraw (part or all of the liquidity; accrued fees come out too):
cast send $SIGN $ROUTER "removeLiquidity(bytes32,int24,int24,uint128,uint256,uint256,uint256,address)" $ID <lo> <hi> <L> <amountFloorMin> <amountQuoteMin> <deadline> $ME
# Take NFTs out of the vault for 1 token each:
cast send $SIGN $FLOOR "approve(address,uint256)" $VAULT <count × 1e18>
cast send $SIGN $VAULT "redeem(uint256[],address)" "[123]" $ME
```

Only the position's owner can withdraw it or collect its fees. Recipients can't be the zero address, the router, the vault or the PoolManager. Unwrap WETH with `cast send $SIGN $WETH "withdraw(uint256)" <amount>`.

## Reverts

| Error | Meaning | Do |
| --- | --- | --- |
| `UnsupportedCollection()` | Not ERC-721 by ERC-165, or ERC-1155 | Stop. |
| `AlreadyRegistered()` | Already imported | Use `registrationOf`. |
| `InvalidOperation()` | Bad batch (0 or > 50 IDs, duplicates, not owned, not in the vault), bad recipient, ticks not multiples of 200, or zero liquidity | Fix the input. |
| `Expired()` | Deadline passed | Re-plan with a fresh deadline. |
| `PoolStateChanged()` | The pool was initialized, or its price moved outside the reviewed bounds | Re-read the pool and re-plan with the user. |
| `Slippage()` | The position needs more than a maximum, or a withdrawal returns less than a minimum | Re-read and re-plan. |
| `RepriceRequiresTrade()` | Repricing would cross someone's liquidity | Don't reprice. Plan around the current price. |
| `MarketUnavailable()` | No liquidity at the current price (trades, withdrawals on an uninitialized pool) | Add a position covering the price, or reprice. |
