--- name: soulzap-giga description: Adds SoulZap support to GIGA - entering any GIGA concentrated liquidity pool on Robinhood Chain with a single token the user already holds, in one transaction. Use this whenever the task involves a GIGA pool, a GIGA position, or a one-click add-liquidity button for GIGA. Companion to soulzap-integration.txt, which must be read first. --- # Integrate SoulZap into GIGA > **Read `soulzap-integration.txt` first** - it carries the flow, the rules and every `lpData` > variant. This file only adds what is specific to GIGA. ## The goal One transaction that takes a single token the user already holds - including native ETH - and leaves them holding a GIGA position NFT: swap, split across both sides of the range, mint. It reverts as a whole if any leg fails. This is an extra path into positions GIGA already has; the manual add-liquidity flow stays exactly as it is. ## Start in the codebase Find these before writing anything, and reuse them: - **The add-liquidity path that exists today** - the position manager `mint` and `increaseLiquidity` calls. The zap swaps out the calldata, not the surrounding UX. - **Where pools come from** - factory reads, a subgraph or a static list - and how the app already reads a pool's `tickSpacing` and current tick. Reuse that; do not add a second source. - **The wallet helpers** - send, wait-for-receipt, and the ERC-20 approve/allowance helper. - **The slippage and deadline settings the user already controls.** Feed those into the request rather than introducing a second control. - **The token selector.** Paying with any held token is the point of the zap; if the UI only offers the two pool tokens, that list is what needs widening. Then make the smallest change that adds the path: build `lpData`, `POST /approve`, `POST /zap`, send `txData` through the helper that already exists. No new dependency - `fetch` is enough. ## What we know about GIGA - **Robinhood Chain** only - send `"chain": "robinhood"` (or `4663`). - A Uniswap V3 fork, concentrated liquidity only. There is no V2-style pair to zap into, so every request uses `lpType: "univ3"`. - Positions are the `Giga Positions` NFT (`GIGA-POS`), minted straight to the recipient. - No gauge, no farm, no lock. **Never send `protocolData`** - there is no protocol step here, and a `univ3` zap could not feed one anyway: the NFT goes to the user, not the router. ## Addresses | Used by | Field | Address | |---|---|---| | `univ3` | `positionManager` | `0xa79f5775b0b49e51202c48ddf03f380faa96f641` | Used for discovery, not in `lpData`: | Contract | Address | |---|---| | Factory | `0xece6ecd61177336ea6fb9b17937ac439d85ee20b` | | WETH | `0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73` | If the codebase already holds these, use its copy rather than pasting them in again. ``` factory.getPool(tokenA, tokenB, uint24 fee) -> pool, or the zero address ``` ## Fee tiers GIGA runs the standard Uniswap set plus two extra tiers, so a hardcoded `[100, 500, 3000, 10000]` misses live pools. Read the spacing from the pool rather than from this table where you can. | `fee` | Tick spacing | |---|---| | 100 (0.01%) | 1 | | 200 (0.02%) | 4 | | 500 (0.05%) | 10 | | 2000 (0.2%) | 40 | | 3000 (0.3%) | 60 | | 10000 (1%) | 200 | ## `univ3` ```json { "lpType": "univ3", "fromToken": "0x...", "fromAmount": "1000000", "positionManager": "0xa79f5775b0b49e51202c48ddf03f380faa96f641", "pool": "0x...", "tickLower": -206850, "tickUpper": -194850, "slippage": 1 } ``` Tick rules: - `tickLower` and `tickUpper` must be exact multiples of the pool's `tickSpacing`, and `tickLower` below `tickUpper`. - A range that does not contain the pool's current tick mints a single-sided position. To deposit both tokens, straddle `slot0().tick`. - GIGA's pools sit at large ticks - WETH/USDG near -200,400, WETH/PONS near 110,300 - so ranges have to be derived from the current tick, never hardcoded. If the app already has range presets, reuse their maths and only round to spacing: ```ts const [, tick] = await pool.slot0() const spacing = await pool.tickSpacing() const half = 6000 // 0.55x to 1.8x in price const tickLower = Math.floor((tick - half) / spacing) * spacing const tickUpper = Math.ceil((tick + half) / spacing) * spacing ``` Pass `tokenId` instead of ticks to add to an existing position; its own range is used, and it must already be owned by `recipient`. ## Worked example - native ETH into WETH/USDG 0.05% Only the request object; approve and zap exactly as the base guide does. ```ts const pool = '0x65ce976b6ab72f9533b4daf53c8de15dfecd408b' // WETH/USDG, fee 500, spacing 10 const [, tick] = await new Contract(pool, POOL_ABI, provider).slot0() const lpData = { lpType: 'univ3', fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // native ETH, nothing to approve fromAmount: '5000000000000000', // 0.005 ETH positionManager: '0xa79f5775b0b49e51202c48ddf03f380faa96f641', pool, tickLower: Math.floor((tick - 6000) / 10) * 10, tickUpper: Math.ceil((tick + 6000) / 10) * 10, slippage: 1, } // no protocolData - GIGA has no protocol step ``` ## Reading the response - `lpQuote.lpAddress` is the **position manager**, not the pool. - `lpQuote.token0` and `token1` carry the amount deposited per side. `toAmountEstimate` is not set. - The NFT is not an output, so it does not appear in `outputs` and no token id comes back in the quote. Read it from the `IncreaseLiquidity` / `Transfer` event in the receipt, or from the position manager after the transaction. - Whatever the range could not take is returned to `recipient` as token0 and token1. ## Errors | Message | Cause | |---|---| | `Ticks must be multiples of tickSpacing ...` | Ticks not rounded to the pool's `tickSpacing`. | | `fromAmount is too small to provide both sides of this range` | Deposit too small to split across the range. | | `Invalid input` on `protocolData.protocol` | There is no GIGA protocol - drop the key entirely. | | `Multicall: Something went wrong` | Usually a pool address that does not exist on this factory. |