> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dzap.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate

> Move liquidity from one position straight into another in a single zap, same-chain or cross-chain.

A migrate exits one position and enters another in a single build: leave a Uniswap V3 LP and land in an Aerodrome pool, move a lending position between markets, or shift liquidity to a different chain. It is a zap where both ends are positions, `srcToken` is the position you are leaving and `destToken` is the position you are entering. It runs on EVM and Solana, same-chain or cross-chain (EVM to Solana and back). Pick a chain below by the position you are leaving; the tabs stay in sync across Setup, Steps, and End-to-end.

<Note>`positionDetails.nftId` identifies the source position you are leaving (NFT-based LP positions like Uniswap V3); `poolDetails` pins the destination range for a concentrated-liquidity target. On EVM the source position authorizes through Permit2; on Solana there is no approval step, you sign and send the built transactions directly.</Note>

## Setup

<Snippet file="install-sdk.mdx" />

<Tabs>
  <Tab title="EVM">
    Migrate from a position on Base into a USDC/WETH pool on Base.

    ```ts theme={null}
    import { DZapClient, Services, ApprovalModes, PermitTypes, TxnStatus } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { base } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: base, transport: http() });
    const dzap = DZapClient.getInstance();

    const TARGET_POOL = '0xd0b53D9277642d899DF5C87A3966A349A798F224';   // position to migrate into
    ```

    <Note>
      Protocol IDs (`provider`) come from [`getZapProviders`](/sdk/zap/request-quotes), scoped per chain by [`getZapChains`](/sdk/zap/request-quotes). Find target pool addresses with [`getZapPools`](/sdk/zap/request-quotes).
    </Note>
  </Tab>

  <Tab title="Solana">
    Migrate from a position on Solana into a Jupiter Lend position on Solana. Needs `@solana/web3.js` alongside the SDK.

    ```ts theme={null}
    import { DZapClient } from '@dzapio/sdk';
    import { Connection, VersionedTransaction } from '@solana/web3.js';

    const dzap = DZapClient.getInstance();
    const connection = new Connection('https://api.mainnet-beta.solana.com');

    // your Solana wallet adapter (wallet-standard, Keypair, etc.)
    const account = wallet.publicKey.toBase58();
    const TARGET_POSITION = '9BEcn9aPEmhSPbPQeFGjidRiEKki46fVQDyPpSQXPA2D';   // position to migrate into
    ```

    <Note>
      Protocol IDs (`provider`) come from [`getZapProviders`](/sdk/zap/request-quotes), scoped per chain by [`getZapChains`](/sdk/zap/request-quotes). Find target position addresses with [`getZapPools`](/sdk/zap/request-quotes).
    </Note>
  </Tab>
</Tabs>

## Steps

<Tabs>
  <Tab title="EVM">
    <Steps>
      <Step title="Find the source position">
        List the account's open positions for a protocol, then pick the one to migrate out of. Each position carries its `address`, `chainId`, `amount`, and optional `nftDetails`.

        ```ts theme={null}
        const { positions } = await dzap.getZapPositions({
          account: account.address,
          chainId: 8453,
          provider: 'uniswap',
        });

        const from = positions[0];   // assumes at least one open position
        ```
      </Step>

      <Step title="Quote">
        Route from the source position into the target position. `srcToken` is what you leave, `destToken` is what you enter. `positionDetails.nftId` identifies the source NFT to burn. Read-only.

        ```ts theme={null}
        const request = {
          srcChainId: from.chainId,
          srcToken: from.address,       // position you are leaving
          amount: from.amount,
          destChainId: from.chainId,    // same as srcChainId = same-chain migrate
          destToken: TARGET_POOL,       // position you are entering
          account: account.address,
          recipient: account.address,
          refundee: account.address,
          slippage: 1,                  // 1 = 1%
          ...(from.nftDetails && {
            positionDetails: { nftId: from.nftDetails.id },
          }),
        };

        const quote = await dzap.getZapQuote(request);
        console.log('Route:', quote.path.map((p) => p.protocol.name).join(' -> '));
        console.log('Expected:', quote.output[0]?.amount, quote.output[0]?.asset.symbol);
        ```

        <Note>
          Migrating into a concentrated-liquidity pool? Add `poolDetails: { lowerTick, upperTick }` to pin the new range, or omit it for the router default. To land on Solana instead, set `destChainId: 7565164`, `destToken` to the SPL position, and `recipient` to a base58 address; keep `refundee` as your EVM address.
        </Note>
      </Step>

      <Step title="Approve">
        Authorize the source position through Permit2, approving once on-chain only if the allowance is short.

        ```ts theme={null}
        const { data } = await dzap.getAllowance({
          chainId: from.chainId,
          sender: account.address,
          tokens: [{ address: from.address, amount: from.amount }],
          service: Services.zap,
          mode: ApprovalModes.PermitSingle,
        });

        if (data[from.address].allowance < BigInt(from.amount)) {
          await dzap.approve({
            chainId: from.chainId,
            signer: walletClient,
            sender: account.address,
            tokens: [{ address: from.address, amount: from.amount }],
            service: Services.zap,
            mode: ApprovalModes.PermitSingle,
          });
        }
        ```
      </Step>

      <Step title="Build & execute">
        Sign the Permit2 `PermitSingle`, attach the returned `permitData`, build the route, and execute its steps in one `zap()` call.

        ```ts theme={null}
        const permit = await dzap.sign({
          chainId: from.chainId,
          sender: account.address,
          service: Services.zap,
          signer: walletClient,
          tokens: [{ address: from.address, amount: from.amount }],
          permitType: PermitTypes.PermitSingle,
        });

        const fullRequest = { ...request, permitData: permit.tokens[0].permitData };
        const route = await dzap.buildZapTxn(fullRequest);
        const result = await dzap.zap({ request: fullRequest, steps: route.steps, signer: walletClient });
        console.log(`Migrated: ${result.txnHash}`);
        ```
      </Step>

      <Step title="Status">
        Poll to a terminal state. Zap status is uppercase: `PENDING`, `COMPLETED`, `FAILED`, or `REFUNDED`. `status.steps` shows the exit and entry legs.

        ```ts theme={null}
        const status = await dzap.getZapTxnStatus({
          chainId: from.chainId,
          txnHash: result.txnHash!,
        });

        console.log(status.status);   // PENDING -> COMPLETED
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Solana">
    <Steps>
      <Step title="Find the source position">
        List the account's open positions for a protocol on Solana (chain ID `7565164`), then pick the one to migrate out of. Each position carries its `address`, `chainId`, and `amount`.

        ```ts theme={null}
        const { positions } = await dzap.getZapPositions({
          account,
          chainId: 7565164,
          provider: 'jupiterlend',
        });

        const from = positions[0];   // assumes at least one open position
        ```
      </Step>

      <Step title="Quote">
        Route from the source position into the target position with base58 addresses. `srcToken` is what you leave, `destToken` is what you enter. Read-only.

        ```ts theme={null}
        const request = {
          srcChainId: from.chainId,
          srcToken: from.address,       // position you are leaving
          amount: from.amount,
          destChainId: 7565164,         // same as srcChainId = same-chain migrate
          destToken: TARGET_POSITION,   // position you are entering
          account,
          recipient: account,
          refundee: account,
          slippage: 1,                  // 1 = 1%
        };

        const quote = await dzap.getZapQuote(request);
        console.log('Route:', quote.path.map((p) => p.protocol.name).join(' -> '));
        ```

        <Note>
          To land on an EVM chain instead, set `destChainId` to the EVM chain, `destToken` to the EVM position, and `recipient` to a `0x` address; keep `refundee` as your Solana address.
        </Note>
      </Step>

      <Step title="Build">
        No approval on Solana, go straight to build. The step data carries an array of base64 transactions and a `txnId`.

        ```ts theme={null}
        const route = await dzap.buildZapTxn(request);
        const step = route.steps[0].data;   // SVM step: { data: string[], txnId, isJitoTx }
        ```
      </Step>

      <Step title="Sign & send">
        Deserialize each base64 transaction, sign with your wallet, and broadcast. Jito-routed zaps go through the Jito block engine.

        ```ts theme={null}
        const txns = step.data.map((b64) =>
          VersionedTransaction.deserialize(Buffer.from(b64, 'base64')),
        );

        if (step.isJitoTx) {
          const signed = await wallet.signTransaction(txns[0]);
          await fetch('https://mainnet.block-engine.jito.wtf/api/v1/transactions', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              jsonrpc: '2.0',
              id: 1,
              method: 'sendTransaction',
              params: [Buffer.from(signed.serialize()).toString('base64'), { encoding: 'base64' }],
            }),
          });
        } else {
          const signedAll = await wallet.signAllTransactions(txns);
          for (const tx of signedAll) {
            const sig = await connection.sendRawTransaction(tx.serialize());
            await connection.confirmTransaction(sig, 'confirmed');
          }
        }
        ```
      </Step>

      <Step title="Status">
        Track it with the `txnId` from the build step.

        ```ts theme={null}
        const status = await dzap.getZapTxnStatus({
          chainId: 7565164,
          txnHash: step.txnId,
        });

        console.log(status.status);   // PENDING -> COMPLETED
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## End-to-end

<Tabs>
  <Tab title="EVM">
    ```ts theme={null}
    import { DZapClient, Services, ApprovalModes, PermitTypes, TxnStatus } from '@dzapio/sdk';
    import { createWalletClient, http } from 'viem';
    import { base } from 'viem/chains';
    import { privateKeyToAccount } from 'viem/accounts';

    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({ account, chain: base, transport: http() });
    const dzap = DZapClient.getInstance();

    const TARGET_POOL = '0xd0b53D9277642d899DF5C87A3966A349A798F224';

    // 1. Pick the source position
    const { positions } = await dzap.getZapPositions({
      account: account.address,
      chainId: 8453,
      provider: 'uniswap',
    });
    const from = positions[0];

    // 2. Quote position -> position
    const request = {
      srcChainId: from.chainId,
      srcToken: from.address,
      amount: from.amount,
      destChainId: from.chainId,
      destToken: TARGET_POOL,
      account: account.address,
      recipient: account.address,
      refundee: account.address,
      slippage: 1,
      ...(from.nftDetails && { positionDetails: { nftId: from.nftDetails.id } }),
    };
    const quote = await dzap.getZapQuote(request);
    console.log('Route:', quote.path.map((p) => p.protocol.name).join(' -> '));

    // 3. Approve (Permit2, on-chain only if short)
    const { data } = await dzap.getAllowance({
      chainId: from.chainId,
      sender: account.address,
      tokens: [{ address: from.address, amount: from.amount }],
      service: Services.zap,
      mode: ApprovalModes.PermitSingle,
    });
    if (data[from.address].allowance < BigInt(from.amount)) {
      await dzap.approve({
        chainId: from.chainId,
        signer: walletClient,
        sender: account.address,
        tokens: [{ address: from.address, amount: from.amount }],
        service: Services.zap,
        mode: ApprovalModes.PermitSingle,
      });
    }

    // 4. Sign permit, build, execute
    const permit = await dzap.sign({
      chainId: from.chainId,
      sender: account.address,
      service: Services.zap,
      signer: walletClient,
      tokens: [{ address: from.address, amount: from.amount }],
      permitType: PermitTypes.PermitSingle,
    });
    const fullRequest = { ...request, permitData: permit.tokens[0].permitData };
    const route = await dzap.buildZapTxn(fullRequest);
    const result = await dzap.zap({ request: fullRequest, steps: route.steps, signer: walletClient });
    if (result.status !== TxnStatus.success) throw new Error(result.errorMsg ?? 'migrate failed');

    // 5. Status
    const status = await dzap.getZapTxnStatus({ chainId: from.chainId, txnHash: result.txnHash! });
    console.log('settled:', status.status);
    ```
  </Tab>

  <Tab title="Solana">
    ```ts theme={null}
    import { DZapClient } from '@dzapio/sdk';
    import { Connection, VersionedTransaction } from '@solana/web3.js';

    const dzap = DZapClient.getInstance();
    const connection = new Connection('https://api.mainnet-beta.solana.com');

    // your Solana wallet adapter (wallet-standard, Keypair, etc.)
    const account = wallet.publicKey.toBase58();
    const TARGET_POSITION = '9BEcn9aPEmhSPbPQeFGjidRiEKki46fVQDyPpSQXPA2D';

    // 1. Pick the source position
    const { positions } = await dzap.getZapPositions({
      account,
      chainId: 7565164,
      provider: 'jupiterlend',
    });
    const from = positions[0];

    // 2. Quote position -> position
    const request = {
      srcChainId: from.chainId,
      srcToken: from.address,
      amount: from.amount,
      destChainId: 7565164,
      destToken: TARGET_POSITION,
      account,
      recipient: account,
      refundee: account,
      slippage: 1,
    };
    const quote = await dzap.getZapQuote(request);
    console.log('Route:', quote.path.map((p) => p.protocol.name).join(' -> '));

    // 3. Build
    const route = await dzap.buildZapTxn(request);
    const step = route.steps[0].data;

    // 4. Sign and send
    const txns = step.data.map((b64) =>
      VersionedTransaction.deserialize(Buffer.from(b64, 'base64')),
    );
    if (step.isJitoTx) {
      const signed = await wallet.signTransaction(txns[0]);
      await fetch('https://mainnet.block-engine.jito.wtf/api/v1/transactions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          jsonrpc: '2.0',
          id: 1,
          method: 'sendTransaction',
          params: [Buffer.from(signed.serialize()).toString('base64'), { encoding: 'base64' }],
        }),
      });
    } else {
      const signedAll = await wallet.signAllTransactions(txns);
      for (const tx of signedAll) {
        const sig = await connection.sendRawTransaction(tx.serialize());
        await connection.confirmTransaction(sig, 'confirmed');
      }
    }

    // 5. Status
    const status = await dzap.getZapTxnStatus({ chainId: 7565164, txnHash: step.txnId });
    console.log('settled:', status.status);
    ```
  </Tab>
</Tabs>

## API usage

The zap API lives on `https://zap.dzap.io/v1`. List positions and check status with `GET`; quote and build are `POST`. `srcToken` is the old position, `destToken` is the new one; NFT sources add `positionDetails`.

<CodeGroup>
  ```bash Positions theme={null}
  curl "https://zap.dzap.io/v1/user/positions?account=0xUser&chainId=8453&provider=uniswap"
  ```

  ```bash Quote theme={null}
  curl -X POST https://zap.dzap.io/v1/quote \
    -H "Content-Type: application/json" \
    -d '{
      "srcChainId": 8453,
      "destChainId": 8453,
      "account": "0xUser",
      "srcToken": "0xOldPosition",
      "destToken": "0xd0b53D9277642d899DF5C87A3966A349A798F224",
      "amount": "<position amount>",
      "recipient": "0xUser",
      "refundee": "0xUser",
      "slippage": 1,
      "positionDetails": { "nftId": "<nft id, source LP positions only>" }
    }'
  ```

  ```bash Build theme={null}
  curl -X POST https://zap.dzap.io/v1/buildTx \
    -H "Content-Type: application/json" \
    -d '{
      "srcChainId": 8453,
      "destChainId": 8453,
      "account": "0xUser",
      "srcToken": "0xOldPosition",
      "destToken": "0xd0b53D9277642d899DF5C87A3966A349A798F224",
      "amount": "<position amount>",
      "recipient": "0xUser",
      "refundee": "0xUser",
      "slippage": 1,
      "permitData": "0x...",
      "positionDetails": { "nftId": "<nft id, source LP positions only>" }
    }'
  ```

  ```bash Status theme={null}
  curl "https://zap.dzap.io/v1/status?chainId=8453&txnHash=0xabc..."
  ```
</CodeGroup>

## Notes

* **Cross-chain migrate**: `srcChainId` and `destChainId` are independent, so any pair works (EVM to EVM, EVM to Solana, Solana to EVM). `recipient` is an address on the destination chain, `refundee` on the source chain. Expect bridge latency.
* **NFT source**: pass `positionDetails.nftId` for the position you are leaving. Fungible LP/vault sources omit it.
* **CL destination**: pass `poolDetails: { lowerTick, upperTick }` to pin the new range, or omit for the router default.
* **Slippage**: a two-sided move (exit + entry) has more surface for slippage; 1-2% is a safe default.

Read more in [Fuse execution flow](/products/dzap-fuse/execution-flow).
