# Integrating with DeFi Protocols

### Introduction

The integration of Veilnyx with any DeFi protocol aims to supercharge the users of that protocol with Veilnyx's privacy and compliance features. In this integration example, we integrate **Veilnyx with Uniswap**, one of the leading decentralized exchange protocols offering liquidity and trading capabilities. This integration will enable users to execute trades on Uniswap while maintaining complete privacy over their transactions.

**Benefits of Integrating with Uniswap**

* **Enhanced Privacy:** Users can trade on Uniswap without exposing their wallet address.
* **Selective Deanonymisation:** Transactions can be decoded when necessary using a revoker and a guardian network, ensuring compliance and accountability.
* **Seamless Experience:** The integration ensures that users can leverage the best of both protocols without compromising on usability.

### Getting Started

#### Prerequisites

Before integrating Veilnyx with an external DeFi protocol, ensure you have the following prerequisites in place:

* **Veilnyx Protocol as Submodule:** Include the Veilnyx protocol it as a submodule in your project. This will provide the necessary functionalities required for the integration.
* **Interface/Periphery Contracts of the Target DeFi Protocol:** Obtain the interface of the periphery contracts of the DeFi protocol you are integrating with. These contracts will be essential for interacting with the protocol’s core functionalities.
* **Address of the Periphery Contracts:** Ensure you have the correct addresses of the periphery contracts of the DeFi protocol you're integrating with, on the deployment network (e.g., Ethereum mainnet, testnet, etc.). These addresses are crucial for interfacing with the target protocol.
* **Involved Token Addresses:** Identify and obtain the addresses of the tokens involved in the transactions on the deployment network. This includes both the tokens you plan to trade and any intermediary tokens required by the protocol.

With these prerequisites in place, you are ready to proceed with the installation and setup of the Veilnyx protocol. Let's go!

#### Installation

To integrate Veilnyx with your chosen DeFi protocol, follow these steps to install and set up the necessary tools and libraries. This guide assumes you are using **Foundry and Solidity** for smart contract development.

**Install Foundry:**\
Foundry is a fast, portable, and modular toolkit for Ethereum application development. Installing Foundry will also handle the installation of the Solidity compiler (solc).

```bash
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

**Install Veilnyx Protocol**

Install the Veilnyx protocol using Foundry:

```bash
forge install zkfi-tech/v1-protocol
```

**Create the interface of the periphery contract**

Include just the interface of the periphery contract of the DeFi protocol you are integrating with, in your project directory, under the `interfaces` folder.

{% hint style="warning" %}
We advise against including the complete DeFi protocol as a submodule as you can run into version conflicts with the Solidity compiler and other common dependencies like OpenZeppelin.
{% endhint %}

**Install OpenZeppelin Contracts**

OpenZeppelin provides a library of modular, reusable, and secure smart contracts for Ethereum. Install OpenZeppelin contracts for secure ERC20 related operations.

```bash
forge install OpenZeppelin/openzeppelin-contracts
```

With these steps completed, your development environment is set up, and you are ready to proceed with integrating Veilnyx with your target DeFi protocol.

### Transaction Flow

You will use the Veilnyx[ SDK to create and submit a `ZTransaction`](https://labyrinth.gitbook.io/veilnyx/labyrinth-sdk/sending-transaction) to Veilnyx Pool proxy contract for execution of your transaction. Here’s how your `ZTransaction` will flow:

<figure><img src="https://1310074946-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfuRJzJ8cEqekSVBqpKIH%2Fuploads%2FpfFiRYxmhDLtQ1EMRinK%2Fimage.png?alt=media&#x26;token=6d883081-95ea-4fa8-9a93-0dcf775cb0ea" alt=""><figcaption><p>Transaction Flow</p></figcaption></figure>

### Veilnyx \`IAdaptor\` implementation

Veilnyx offers the `IAdaptor` interface for protocol devs to implement in their DeFi adaptors. This interface offers a `adaptorConnect(...)` function:

```solidity
function adaptorConnect(
        uint24[] calldata inAssetIds,
        uint256[] calldata inValues,
        bytes calldata payload
    )
        external
        payable
        virtual
        returns (uint24[] memory outAssetIds, uint256[] memory outValues);
```

`inAssetIds` : The array of incoming asset ids involved in the transaction.

{% hint style="info" %}
Veilnyx offers a struct `Asset` to whitelist the assets you’re going to use, during Veilnyx protocol initialization itself. The asset IDs used as `inAssetIds` and `outAssetIds` are returned once an asset is whitelisted during Veilnyx initialization. We will cover this in the deployment section.
{% endhint %}

`inValues` : The quantity of each incoming asset.

`payload` : Miscellaneous parameters (encoded as bytes) that your DeFi adaptor might need for the transaction to the target DeFi protocol. These are generally parameters expected by the target protocol to execute. For eg: Uniswap expects information of `outToken`, `beneficiary`, `slippage protection`, etc to execute a swap, which can all be encoded in the `payload` params and made available to your adaptor contract by decoding it.

Here’s an implementation of the `adaptorConnect(...)` function of the UniswapV3 adaptor which executes a swap between WETH<>USDC tokens:

{% hint style="info" %}
The `adaptorConnect(...)` is called via `delegatecall` by the `zkFi::AdaptorHandler.sol`, with all the required input tokens/assets already in balance. The converted assets or outputs are then returned in the same function.
{% endhint %}

```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.24;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { AdaptorBase } from "src/base/AdaptorBase.sol";
import {Asset, AssetType} from "src/libraries/Asset.sol";
import {ISwapRouter02} from "./ISwapRouter02.sol";

contract UniswapV3Adapter is AdaptorBase {
    // Errors //
    error UnsupportedAsset(uint24 assetId);
    error MultiAssetSwap();
    error ZeroValues();
    error ZeroAddress();

    ISwapRouter02 public immutable swapRouter02; // Uniswap's Periphery contract interface
    uint24 public constant feeTier = 3000; // Uniswap variable

    constructor(address swapRouter02_, address pool_) AdaptorBase(pool_) {
        swapRouter02 = ISwapRouter02(swapRouter02_); // Uniswap V3 Swap router
    }

/// @dev Will be called by the zkFi AdaptorHandler.sol to execute the swap.
    function adaptorConnect(
        uint24[] calldata inAssetIds,
        uint256[] calldata inValues,
        bytes calldata payload
    )
        external
        payable
        override
        returns (uint24[] memory outAssetIds, uint256[] memory outValues)
    {
        uint256 inValue;
        // Checks
        if (inAssetIds.length != 1 || inValues.length != 1) {
            revert MultiAssetSwap();
        }

        if (inValues[0] == 0) {
            revert ZeroValues();
        } else {
            inValue = inValues[0];
            console.log("UniswapV3Adp:: inValue to swap:", inValue);
        }

        Asset memory inAsset = getAsset(inAssetIds[0]); // offered by zkFi::adaptorBase.sol
        if (!inAsset.isSupported) {
            revert UnsupportedAsset(inAssetIds[0]);
        }

        if (inAsset.assetAddress == address(0)) {
            revert ZeroAddress();
        }

        // decoding payload
        (uint24 outAssetId, address beneficiary, uint256 minOut) = abi.decode(
            payload,
            (uint24, address, uint256)
        );

        Asset memory outAsset = getAsset(outAssetId); // offered by zkFi::adaptorBase.sol
        if (!outAsset.isSupported) {
            revert UnsupportedAsset(outAssetId);
        }

        if (beneficiary == address(0)) {
            // means the out tokens will go to the ZKFI AdaptorHandler and have to be processed to the pool
            beneficiary = address(this); // AdaptorHandler.sol
        }

        // Executing swap using UniswapV3
        uint256 tokenOutAmount = swapExactInputSingle({
            tokenIn: inAsset.assetAddress,
            tokenInAmt: inValue,
            tokenOut: outAsset.assetAddress,
            minOut: minOut,
            beneficiary: beneficiary
        });

        // initialising out arrays
        if (beneficiary == address(this)) { // address(this) > zkFi::AdaptorHandler.sol
            outAssetIds = new uint24[](1);
            outValues = new uint256[](1);

            outAssetIds[0] = outAssetId;
            outValues[0] = tokenOutAmount;
        } else {
            outAssetIds = new uint24[](0);
            outValues = new uint256[](0);
        }

        return (outAssetIds, outValues);
    }

    /// @param tokenIn The address of the token to be swapped
    /// @param tokenInAmt The amount of `tokenIn` tokens to swap
    /// @param tokenOut The address of the output token
    /// @param minOut The minimum amount of output token the user expects to get back. This is used for slippage protection.
    /// @param beneficiary The address which will receive the output tokens. This will be the zkFi Convertor.sol contract.
    function swapExactInputSingle(
        address tokenIn,
        uint256 tokenInAmt,
        address tokenOut,
        uint256 minOut,
        address beneficiary
    ) public returns (uint256 amountOut) {
        // ZkFi convertor to approve the Uniswap adaptor the inTokens received.
        SafeERC20.forceApprove(
            IERC20(tokenIn),
            address(swapRouter02), 
            tokenInAmt
        );

        // Create the params that will be used to execute the swap
        /// @param sqrtPriceLimitX96 This is the sqrt of potential value decrease of outAsset relative to inAsset (uint160), that the trader is willing to ignore for the swap. We will be deactivating this protective measure for the MVP. We will only be deploying the slippage protection using `amountOutMinimum`
        ISwapRouter02.ExactInputSingleParams memory params = ISwapRouter02
            .ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: feeTier,
                recipient: beneficiary,
                amountIn: tokenInAmt,
                amountOutMinimum: minOut,
                sqrtPriceLimitX96: uint160(0)
            });

        // The call to `exactInputSingle` executes the swap.
        amountOut = swapRouter02.exactInputSingle(params);
    }
```

{% hint style="warning" %}
We highly recommend making the external calls through interfaces, rather than low-level calls like `contractAddress.call(...)`to reduce chances of reverts caused due to parameter encoding.
{% endhint %}

### **Testing Adaptor contract guide:**

In order to test your adaptor, we provide you with a ready to use test setup and scripts to abstract complexity and offer a smooth dev experience.

#### **Main Imports:**

1. `BaseTest.t.sol` : Test setup which offers `BaseTest::_loadZTx()` function to load the ZTransaction to execute the swap. Before you can use this, you will have to create these ZTransaction using the js-ffi provided by Veilnyx. We will cover this in detail in the test setup section.
2. `ZkFiDeploy.s.sol`: Deploy script for deploying the complete Veilnyx protocol. It will return the instance of the pool proxy on which you will call the `Pool::transact()` function to execute your ZTransaction created in the previous step. [Refer the transaction flow](https://app.gitbook.com/o/zJnmCeo1QbAjeMfjO1Qu/s/fuRJzJ8cEqekSVBqpKIH/~/changes/72/zkfi-sdk/integrating-with-defi-protocols/~/comments#transaction-flow) above for more clarity.
3. `zkFi Pool` : Offers the `setConvertProxy(address adaptor, bool enable)` function which whitelist your adaptor with the zkFi protocol. If not whitelisted, the zkFi protocol will throw a `UnsupportedAdaptor()` error and will not delegateCall your adaptor.
4. Adaptor: We will deploy our DeFi adaptor in the test setup to be used in our tests.

#### **Creating zTransaction using js-ffi**

zkFi has included some TS scripts as part of the protocol using the foreign function interface offered by Solidity. These scripts can be used by you to generate the `zTransactions` for submitting to the `Pool::transact()`.

To view the pre-existing scripts, open the `genFixtures.ts` file at `lib/v1-protocol/test/js-ffi/genFixtures.ts`.

The following object define the properties required to generate the zTransaction:

```solidity
deposit_1_weth: {
    type: TransactionType.DEPOSIT,
    assetIds: [wethAssetId],
    values: [parseEther("1")],
    feeAssetId: 0,
    to: senderAccount.shieldedAddress.pack(),
    viaBundler: false,
    paymaster: zeroAddress,
  }
```

In order to modify the transaction to suit your testing requirements, you can just modify the values of these properties. For eg, suppose you want to deposit a 100 WETH instead of 1, here’s how you can modify/add the transaction object.

```solidity
deposit_100_weth: { // object key changed
    type: TransactionType.DEPOSIT,
    assetIds: [wethAssetId],
    values: [parseEther("100")], // changed from 1 => 100
    feeAssetId: 0,
    to: senderAccount.shieldedAddress.pack(),
    viaBundler: false,
    paymaster: zeroAddress,
  }
```

{% hint style="info" %}
Do note the assetIds property. These are deterministic following the pattern: \
BytesPrefix - AssetTypeId (1 byte) - UID (2 bytes)

Eg: const wethAssetId = 0x010001 \
0x = bytes prefix \
01 = ID for ERC20 tokens \
0001 = UID 1 alloted to the first token added

These can also be derived by converting the asset id returned by the zkFi Asset::addAsset() into it’s hexadecimal representation. 65537 ⇒ 0x010001
{% endhint %}

Once the request objects are defined, include them in the reqs object of the genFixtures::main() function and run the script:

```bash
cd test/js-ffi
pnpm install
pnpm run genFixtures
```

You will see the ZTransactions outputs as .txt files in the `test/fixtures/ztx` folder . Now you will be able to call these zTransactions inside your test suite using the `BaseTest::_loadZTx()` function.

### Writing your tests

Now with the imports and zTransactions in place, you are all set to test your DeFi adaptor!

* **Lets setup our test suite:**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
pragma abicoder v2;

import {BaseTest} from "test/fixtures/BaseTest.sol";
import {Pool} from "src/core/Pool.sol";
import {ZkFiDeploy} from "script/deploy/ZkFi.s.sol";
import {ZTransaction} from "src/libraries/ZTransaction.sol";
import {UniswapV3Adapter} from "src/adaptors/uniswap-v3/UniswapV3Adapter.sol";
import {IWToken} from "src/interfaces/IWToken.sol"; // interface for wrapped tokens
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Asset} from "src/libraries/Asset.sol";
import {console} from "forge-std/console.sol";

contract UniswapV3AdaptorTest is BaseTest {    
    Pool pool;
    UniswapV3Adapter uniswapV3Adapter;
    address uniswapSwapRouter02;
    address public WETH = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; // Sepolia
    address public USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; // Sepolia
    IWToken public iWETH = IWToken(WETH);
    uint256 public constant INITIAL_SUPPLY = 1 ether;
    uint256 public constant SWAP_AMT = 0.01 ether;
    address public user = 0x689EcF264657302052c3dfBD631e4c20d3ED0baB; // Sepolia address

    function setUp() external {
        ZkFiDeploy zkFiDeployer = new ZkFiDeploy();
        (pool, uniswapSwapRouter02) = zkFiDeployer.run(); // deploying zkFi contracts. Avoid on already deployed testnets.
        
        uniswapV3Adapter = new UniswapV3Adapter( // deploying the UniswapV3 adaptor
            uniswapSwapRouter02,
            address(pool)
        );

        console.log("Uniswap adaptor:", address(uniswapV3Adapter));

        address poolOwner = pool.owner();
        vm.prank(poolOwner);
        pool.setConvertProxy(address(uniswapV3Adapter), true); // whitelisting the UniswapV3 adaptor in the zkFi protocol

        vm.deal(user, INITIAL_SUPPLY * 2);

        vm.startPrank(user);
        iWETH.deposit{value: INITIAL_SUPPLY}(); // getting iWETH tokens
        iWETH.approve(address(pool), INITIAL_SUPPLY); // approving the iWETH tokens to the zkFi pool before submitting the deposit zTx.
        ZTransaction memory ztxWethDeposit = _loadZTx("deposit_1_weth"); // loading the zTx we created using the js-ffi
        pool.transact(ztxWethDeposit); // submitting the deposit zTx to the zkFi Pool::transact()
        vm.stopPrank();
    }
```

{% hint style="info" %}
Make sure to provide zkFi pool access to the input tokens before executing any transaction like transfer or convert through the zkFi protocol. Therefore we have included the `deposit_1_weth` zTx in our test setup() itself.
{% endhint %}

* **Test if the adaptor got deployed**

```solidity
function testUniswapZkFiAdaptorDeploy() external view {
        assert(address(uniswapV3Adapter) != address(0));
    }
```

* **Test the core feature of your adaptor**

```solidity
	function testWethToUSDCSwapToPool() external {
        console.log("Initiating WETH<>USDC swap");
        uint256 poolUSDCBalBeforeConvert = IERC20(USDC).balanceOf(
            address(pool)
        );

        ZTransaction memory ztxDeposit = _loadZTx("swap_1e16_weth_to_usdc"); // swapping 0.01 WETH to USDC
        pool.transact(ztxDeposit); // submitting the transaction.

        // Asserts
        uint256 poolUSDCBalPostConvert = IERC20(USDC).balanceOf(address(pool));
        console.log("Pool USDC bal before swap:", poolUSDCBalBeforeConvert);
        console.log("Pool USDC bal after swap:", poolUSDCBalPostConvert);
        assert(poolUSDCBalPostConvert > poolUSDCBalBeforeConvert);
    }
```

{% hint style="info" %}
If you’re curious to know how a ZTransaction submitted to the Pool::transact() flows through the zkFi protocol > adaptor > target DeFi protocol and back, refer the [Transaction Flow section.](https://app.gitbook.com/o/zJnmCeo1QbAjeMfjO1Qu/s/fuRJzJ8cEqekSVBqpKIH/~/changes/72/zkfi-sdk/integrating-with-defi-protocols/~/comments#transaction-flow)
{% endhint %}

* **Run your tests**

  Fork the mainnet/testnet on Anvil, in a new terminal tab:

```bash
anvil --fork-url {archival_node_url} --fork-block-number {any_recent_block_number} --fork-chain-id 1 --chain-id 1 
```

{% hint style="warning" %}
We higly recommend testing against mainnet forks as most DeFi protocols are already deployed on mainnets. Also you will not run into liquidity deficit issues.
{% endhint %}

```bash
forge test --mt {testFunction} --rpc-url http://127.0.0.8:8545 -vvvv
```

### Deploy

Once you’re done testing your DeFi adaptor, it’s finally time to ship!

If you’re deploying on a network which has the Veilnyx and the target DeFi protocols, then you’re in luck. Just grab the `zkFi PoolProxy` contract address, the `periphery contract` addresses of your target protocol and just deploy your adaptor with the required addresses:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {UniswapV3Adapter} from "src/adaptors/uniswap-v3/UniswapV3Adapter.sol";

contract UniswapV3AdaptorDeploy {
    function run() external broadcast returns(UniswapV3Adapter) {
        address poolProxy = `${zkFi_pool_proxy_address_on_target_network}`;
        address uniswapRouter02 = `${uniswapSwapRouter02_address_on_target_network};

        UniswapV3Adapter uniswapV3Adapter = new UniswapV3Adapter(uniswapRouter02, poolProxy);
        return uniswapV3Adapter;
    }
}
```

If either protocols are not deployed on your target network, please refer their respective documentation, to deploy the protocols on your target network, before proceeding with your adaptor deployment.
