💻Интеграция с Alpaca Finance

Integrating with Alpaca Finance

Alpaca Finance allows DeFi applications and end users to integrate with its core protocol. As a simple example, a developer could build an auto-compounding protocol which deposits users' funds into Alpaca Finance's deposit vaults and compounds the yields for users for gas efficiency and convenience. The purpose of this document is to assist developers in integrating with our protocol to ensure overall security for the ecosystem.

The integration with Alpaca API requires authentication, so please reach out to us at requests@alpacafinance.org if you would like to use this service.

Smart Contract Integration

This section describes how to integrate with Alpaca Finance directly from a smart contract.

Lending

Alpaca Finance allows users to earn interest income on crypto assets by depositing them into our deposit vaults. These assets are then offered to yield farmers for leveraging up their positions. Any smart contract which is EVM compatible and deployed on Binance Smart Chain can integrate with Alpaca Finance's lending protocol.

To deposit the base token into Alpaca Finance's vaults, the list of available vaults and their corresponding base tokens should first be acquired.

  • Please see here for the detailed list of all vault contract addresses.

Before a deposit can be made to a vault, the smart contract which acts as a depositor must first approve the spending allowance of the base token to be deposited. This will provide Alpaca's Smart Contract the ability to obtain tokens from the sender.

// JavaScript
import { ERC20 } from '../typechain/ERC20'
import { MaxUint256 } from '@ethersproject/constants'

...

const yourSmartContractAddress = 'xxx'
tokenERC20.approve(yourSmartContractAddress, MaxUint256);

The above JavaScript code snippet is a simplified workflow on how to approve token spending allowance with maximum possible amount. Please note that using MaxUint256 could introduce security risk, using the exact token amount input by user is recommended.

Performing a deposit is done by calling the deposit method and specifying the deposit amount in the amountToken parameter. The amountToken data type is uint256 and this parameter must be formatted to the decimals of the deposit token (for example, BNB uses 18 decimals, 1 BNB = 10000000000000000000 or 1 * 1e18). For the case where the deposit token is BNB, the vault will handle the wrapping of BNB into wBNB, but the transaction must include enough BNB sent as the native token.

// Solidity
address vaultContractAddress = '0xd7D069493685A581d27824Fc46EdA46B7EfC0063'; // BNB Vault
if (msg.value == 0) { // if no native token is sent, then it is a ERC20/BEP20 token deposit
	IERC20(tokenAddess).safeTransferFrom(address(msg.sender), address(this), amountToken);
}
// Allow transfer to vault
SafeToken.safeApprove(tokenAddess, vaultContractAddress, amountToken);
// Deposit to vault
IVault(vaultContractAddress).deposit(amountToken);

If the deposit is successful, the vault will mint interest-bearing tokens (ibTokens) and return those minted tokens back to the caller. Please note that the amount of the returned interest-bearing token will not be equal to the supplied base token amount due to the design of interest-bearing tokens. The caller of the deposit method must correctly keep track of the users' shares for deposits in vaults, please see the Interest Bearing Token Calculation section for a detailed explanation.

The interest from lending will accrue to the ibTokens. To realize the gains from lending, the ibTokens must be withdrawn, redeeming back the base token + collected interest. You will notice that the exchange price of the ibToken would be higher at withdrawal, which indicates that interest has already accrued. Therefore, you will receive additional base tokens upon withdrawing.

To redeem the base tokens from the vault, the amount of the ibTokens to be withdrawn must be supplied to the withdraw method in the Vault contract.

// Solidity
address vaultContractAddress = '0xd7D069493685A581d27824Fc46EdA46B7EfC0063'; // BNB Vault
IVault(vaultContractAddress).withdraw(ibTokenAmount);

The base tokens will then be returned back to the caller. If the returned token is BNB, the Vault contract will unwrap WBNB back into BNB.

Interest Bearing Token Calculation

An Interest Bearing Token (ibToken) is the token which represents the vault share of the depositor. ibTokens will accrue interest received from lending over time. Any DeFi protocol wishing to utilize Alpaca Finance's vaults should understand and implement the calculation of ibTokens correctly to reflect the actual users' shares of the vault.

Hence, the ratio between an ibToken and the actual base token will only be 1:1 when the vault first opens, after which the ibToken's value will continue to increase as lending interest accrues (The value of an ibToken relative to the base token will only move one-way upwards. The value cannot decrease). For example, let's assume the price: 1 ibBNB = 1.0292 BNB which means a deposit of 1.0292 BNB will get 1 ibBNB in return. On the other hand, redeeming 1 ibBNB will get 1.0292 BNB in return. The additional BNB from redeeming ibBNB are from the accrued interest.

Understanding this one fundamental mechanic of ibTokens' values is crucial, since a DeFi protocol integrating with Alpaca Finance's lending vaults might need to accurately calculate the users' shares of the vault deposit. Failure to do so could impose security risk and financial loss. This was the case for the attacks on bEarn.fi and ValueDeFi in which these protocols always treated ibTokens as having a 1:1 ratio with the underlying token (read the analysis of the attacks on bEarn.fi and ValueDefi).

In addition, although Alpaca does not work with flash loans, in an external non-Alpaca vault, there could be a risk of ibToken price manipulation from an attack such as a flash loan attack. If unprepared, the price of an ibToken could be drastically changed inside the scope of an attacker's transaction. That's why, relying on the ibToken price from smart contract calculation alone is not enough. We recommend the project to have a price oracle feeding the current ratio of ibTokens vs its base token to prevent such an attack mentioned above.

Therefore, we will show you the safest and correct way to calculate the prices of ibTokens.

Direct calculation from smart contract

Calculating ibToken token price from the smart contract is the simplest way, but for robustness, it must not be the only method you rely on.

// Solidity
address vaultContractAddress = '0xd7D069493685A581d27824Fc46EdA46B7EfC0063'; // BNB Vault
IVault vault = IVault(vaultContractAddress);
uint256 ibTokenAmount = ...;
uint256 ibTokenPrice = vault.totalToken()).div(vault.totalSupply();
uint256 underlyingTokenAmount = ibTokenAmount.mul(ibTokenPrice);

The above code snippet demonstrates an ibToken price calculation from retrieving the value of totalToken and totalSupply from the corresponding vault.

Retrieving ibToken price from Alpaca API

Alpaca Finance has provided an API to retrieve the current ibToken prices by sending a GET REST request to the endpoint /ibTokens to retrieve any ibToken price. The integration with Alpaca API requires authentication, so please reach out to us at requests@alpacafinance.org if you would like to use this service.

The sample result from the API is as follow:

{
    "status": {
        "code": 1000,
        "messages": [
            "OK"
        ]
    },
    "data": [
        {
            "symbol": "ibALPACA",
            "baseTokenPerShare": "1.051108636596531492",
            "lendingApr": "0.0003638488758058",
            "stakingApr": "0.0"
        },
        {
            "symbol": "ibBNB",
            "baseTokenPerShare": "1.025808940627339553",
            "lendingApr": "9.7785294844178225",
            "stakingApr": "316693.265280905443641"
        },
        {
            "symbol": "ibBUSD",
            "baseTokenPerShare": "0.972337256352625836",
            "lendingApr": "0.368330890561667",
            "stakingApr": "2269.786601379154162"
        },
        {
            "symbol": "ibUSDT",
            "baseTokenPerShare": "0.913094694500682622",
            "lendingApr": "0.0012775198722327",
            "stakingApr": "0.0"
        },
        {
            "symbol": "ibBTCB",
            "baseTokenPerShare": "1.000006160536069502",
            "lendingApr": "6.5534019786376696",
            "stakingApr": "0.0"
        },
        {
            "symbol": "ibETH",
            "baseTokenPerShare": "0.999463386510494271",
            "lendingApr": "0.0000004209960912",
            "stakingApr": "0.0"
        }
    ]
}

The price retrieved from Alpaca's API should be supplied to the smart contract using a price oracle. Supplying the prices via an input to a smart contract call will be vulnerable to an injection attack from the client. We strongly advised against doing that. An example of a simple price oracle could be seen here. By using a trusted price oracle, you can ensure the integrity and reliability of the ibToken price data.

We strongly recommend retrieving the ibToken price from both smart contract calculation and Alpaca API. These two sources of price data should be compared to prevent any possible data anomaly. If the comparison between the two sources differs significantly, the price data should be rejected.

In summary, the most secure process to calculate ibToken prices is the following:

  1. Calculate ibToken prices in your own contracts.

  2. Use an off-chain oracle for ibToken prices or pull those prices from Alpaca's API.

  3. Compare 1 and 2. If the difference is more than n% then revert the transaction.

Alpaca's API is currently whitelist-only for protocols and institutions. To request access, please email us at requests@alpacafinance.org

Staking

Alpaca Finance provides staking opportunities to users. Interest-bearing tokens and selected LP tokens are available for staking in our staking pools to receive additional yields in the form of ALPACA tokens. If you have deposited funds and received ibTokens, we recommend you to stake those tokens into these pools to earn the maximum available rewards.

To stake the tokens into Alpaca Finance's staking pools, the list of pools and their corresponding staking tokens should first be acquired. Please see here for an in-depth list of all pool contract addresses. The contract of the pools is generally called Fairlaunch in our codebase.

Performing staking is done by calling the deposit method. The parameters are as followed:

  • _for is the address of the depositor.

  • _pid is the id of the staking pool.

  • _amount is the amount of the token to be deposited in uint256 expressed in its decimals (for example, ibBNB uses 18 decimals, 1 ibBNB = 10000000000000000000 or 1 * 1e18)

// Solidity
address fairlaunchContractAddress = '0xA625AB01B08ce023B2a342Dbb12a16f2C8489A8F'; // ibBNB pool
IFairLaunch fairlaunch = IFairLaunch(fairlaunchContractAddress);
fairlaunch.deposit(msg.sender, poolId, amount);

The information of the staking share will be stored inside the smart contract state. There will be no token issued from staking. The share of a user can be retrieved by calling the userInfo method from the Fairlaunch contract.

The rewards from staking are not automatically credited and must be manually harvested. However, performing a deposit or withdraw on the staked pools will automatically harvest any pending rewards to the caller. To harvest rewards from a staking pool, the caller must call the harvest method from the Fairlaunch contract and supply the pool id.

// Solidity
address fairlaunchContractAddress = '0xA625AB01B08ce023B2a342Dbb12a16f2C8489A8F'; // ibBNB pool
IFairLaunch fairlaunch = IFairLaunch(fairlaunchContractAddress);
fairlaunch.harvest(poolId);

To withdraw the staking tokens from the pool, the amount of the user's share must be supplied to the withdraw method in the Fairlaunch contract.

// Solidity
address fairlaunchContractAddress = '0xA625AB01B08ce023B2a342Dbb12a16f2C8489A8F'; // ibBNB pool
IFairLaunch fairlaunch = IFairLaunch(fairlaunchContractAddress);
fairlaunch.withdraw(msg.sender, poolId, amount);

The staking tokens will be returned to the caller and any pending rewards will also be automatically harvested.

Last updated