Swap and Redeem FAssets
Overview
In this guide, you will learn how to swap a token (in this example, WCFLR) for FXRP and redeem FXRP for FAssets using the Uniswap V2 router (in this example, BlazeSwap) and the FAssets asset manager.
Redemption is the process of burning FAssets (e.g., FXRP) on Flare in exchange for their equivalent value on the original chain (e.g., XRP on XRPL).
See the Redemption overview for more details.
Prerequisites
Swap and Redeem Smart Contract
The following Solidity contract demonstrates how to swap WCFLR for FXRP and then redeem FXRP to receive XRP on the XRPL.
Contract Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
// 1. Required Imports
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IAssetManager} from "@flarenetwork/flare-periphery-contracts/coston/IAssetManager.sol";
import {AssetManagerSettings} from "@flarenetwork/flare-periphery-contracts/coston/userInterfaces/data/AssetManagerSettings.sol";
// 2. Interfaces
// Uniswap V2 Router interface needed for this example to communicate with BlazeSwap
interface ISwapRouter {
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
)
external
returns (uint256[] memory amountsSent, uint256[] memory amountsRecv);
function getAmountsIn(
uint256 amountOut,
address[] calldata path
) external view returns (uint256[] memory amounts);
}
// Contract to swap WCFLR for FXRP and redeem FAssets
contract SwapAndRedeem {
// 3. State Variables and Constructor
// Uniswap V2 Router interface to communicate with BlazeSwap
ISwapRouter public immutable router;
// FAssets assset manager interface
IAssetManager public immutable assetManager;
// FAssets token (FXRP)
IERC20 public immutable token;
// Path to swap WCFLR for FXRP
address[] public swapPath;
constructor(
address _router,
address _assetManager,
address[] memory _swapPath
) {
router = ISwapRouter(_router);
assetManager = IAssetManager(_assetManager);
swapPath = _swapPath;
token = IERC20(_swapPath[0]);
}
// 4. Main Function: `swapAndRedeem`
// Swap WCFLR for FXRP and redeem FAssets
// @param _lots: number of lots to redeem
// @param _redeemerUnderlyingAddressString: redeemer underlying address string (XRP address)
// @return amountOut: amount of FXRP received
// @return deadline: deadline of the swap
// @return amountsSent: amounts sent to the router
// @return amountsRecv: amounts received from the router
// @return _redeemedAmountUBA: amount of FAssets redeemed
function swapAndRedeem(
uint256 _lots,
string memory _redeemerUnderlyingAddressString
)
external
payable
returns (
uint256 amountOut,
uint256 deadline,
uint256[] memory amountsSent,
uint256[] memory amountsRecv,
uint256 _redeemedAmountUBA
)
{
// Calculate the amount needed to swap to FXRP and redeem
(uint256 _amountIn, uint256 _amountOut) = calculateRedemptionAmountIn(
_lots
);
require(
token.balanceOf(msg.sender) >= _amountIn,
"Insufficient token balance to swap"
);
// Check if the user has enough FXRP allowance
require(
token.allowance(msg.sender, address(this)) >= _amountIn,
"Insufficient FXRP allowance"
);
// Transfer tokens from msg.sender to this contract to swap to FXRP
require(
token.transferFrom(msg.sender, address(this), _amountIn),
"Transfer failed"
);
// Approve Uniswap router to spend the tokens
require(
token.approve(address(router), _amountIn),
"Router approval failed"
);
// Set the deadline for the swap (10 minutes)
uint256 _deadline = block.timestamp + 10 minutes;
address[] memory path = swapPath;
// Swap tokens to FXRP using BlazeSwap (Uniswap V2 router interface)
(uint256[] memory _amountsSent, uint256[] memory _amountsRecv) = _swap(
_amountIn,
_amountOut,
path,
_deadline
);
// Redeem FAssets from FXRP to the redeemer's underlying XRPL address
_redeemedAmountUBA = _redeem(_lots, _redeemerUnderlyingAddressString);
return (
_amountOut,
_deadline,
_amountsSent,
_amountsRecv,
_redeemedAmountUBA
);
}
// 5. Helper Function: `calculateRedemptionAmountIn`
// Calculate the amount needed to swap to FXRP and redeem
// @param _lots: number of lots to redeem
// @return amountOut: amount of FXRP received
// @return amountIn: amount of WCFLR needed to swap
function calculateRedemptionAmountIn(
uint256 _lots
) public view returns (uint256 amountOut, uint256 amountIn) {
AssetManagerSettings.Data memory settings = assetManager.getSettings();
uint256 lotSizeAMG = settings.lotSizeAMG;
// Calculate the amount of WCFLR needed to swap to FXRP
uint256[] memory amounts = router.getAmountsIn(
lotSizeAMG * _lots,
swapPath
);
return (amounts[0], amounts[1]);
}
// Redeem FAssets from FXRP to the redeemer's underlying XRPL address
// @param _lots: number of lots to redeem
// @param _redeemerUnderlyingAddressString: redeemer underlying address string (XRP address)
// @return amountRedeemed: amount of FAssets redeemed
function _redeem(
uint256 _lots,
string memory _redeemerUnderlyingAddressString
) internal returns (uint256) {
return
assetManager.redeem(
_lots,
_redeemerUnderlyingAddressString,
// The account that is allowed to execute redemption default (besides redeemer and agent).
// In this case it is not used
payable(address(0))
);
}
// Swap tokens to FXRP using BlazeSwap (Uniswap V2 router interface)
// @param amountIn: amount of tokens to swap
// @param amountOutMin: minimum amount of FXRP received
// @param path: path to swap tokens
// @param deadline: deadline of the swap
// @return amountsSent: amounts sent to the router
// @return amountsRecv: amounts received from the router
function _swap(
uint256 amountIn,
uint256 amountOutMin,
address[] memory path,
uint256 deadline
)
internal
returns (uint256[] memory amountsSent, uint256[] memory amountsRecv)
{
(amountsSent, amountsRecv) = router.swapExactTokensForTokens(
amountIn,
amountOutMin,
path,
address(this),
deadline
);
return (amountsSent, amountsRecv);
}
}
This contract has two main functions:
- Swaps WCFLR for FXRP using a BlazeSwap router (Uniswap V2 compatible).
- Redeems FAssets (FXRP) to an XRPL address using the FAssets asset manager.
1. Required Imports
The contract uses the following dependencies:
IERC20
OpenZeppelin standard ERC-20 interface.IAssetManager
interface to the FAssets Asset Manager.AssetManagerSettings
is used to retrieve lot size and configuration.
2. Interfaces
The ISwapRouter
interface allows interaction with a Uniswap V2-compatible router:
swapExactTokensForTokens
: Swaps an exact amount of input tokens.getAmountsIn
: Calculates how much input is needed for a given output.
3. State Variables and Constructor
Defined state variables:
router
: Uniswap V2 router contract; in this example, it is the BlazeSwap router.assetManager
: Asset Manager handling FXRP redemption.token
: Token to swap (WCFLR in the swap path).swapPath
: Array of addresses defining the swap route (e.g., WCFLR → FXRP).
To instantiate the contract, the constructor sets up the Uniswap router, FAssets asset manager, and swap path. It initializes the token as the first token in the swap path.
4. Main Function: swapAndRedeem
The swapAndRedeem
is the core function that executes the swap and redemption flow.
-
1. Validation
- Ensure the caller has sufficient WCFLR balance.
- Confirm the contract has enough FXRP allowance for redemption.
-
2. Transfer
- Move WCFLR from the caller to the contract.
- Approve the router to spend WCFLR on behalf of the contract.
-
3. Swap
- Perform the swap from WCFLR to FXRP using the specified swap path.
- Apply a 10-minute deadline for the swap operation.
-
4. Redemption
- Redeem the obtained FXRP through the FAssets system.
- Transfer the resulting XRP to the caller's XRPL address.
-
5. Helper Function:
calculateRedemptionAmountIn
- Accept the number of FXRP lots intended for redemption.
- Fetch the lot size from the FAssets asset manager and calculate the WCFLR needed for swap and redemption.
Execute the Swap and Redeem
To execute the swap and redeem process, you need to deploy the smart contract instance and call the swapAndRedeem
function.
// 1. Dependancies and constants
import { run } from "hardhat";
import { SwapAndRedeemInstance } from "../../typechain-types";
import { ERC20Instance } from "../../typechain-types/@openzeppelin/contracts/token/ERC20/ERC20";
// AssetManager address on Songbird Testnet Coston network
const ASSET_MANAGER_ADDRESS = "0x56728e46908fB6FcC5BCD2cc0c0F9BB91C3e4D34";
const LOTS_TO_REDEEM = 1;
const UNDERLYING_ADDRESS = "rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm";
// BlazeSwap router address on Songbird Testnet Coston network
const SWAP_ROUTER_ADDRESS = "0xf0D01450C037DB2903CF5Ff638Dd1e2e6B0EEDF4";
const SWAP_PATH = [
"0x767b25A658E8FC8ab6eBbd52043495dB61b4ea91", // WCFLR
"0x36be8f2e1CC3339Cf6702CEfA69626271C36E2fd", // FXRP
];
// 2. Deploy and verify the `SwapAndRedeem` smart contract
async function deployAndVerifyContract() {
const SwapAndRedeem = artifacts.require("SwapAndRedeem");
const args = [SWAP_ROUTER_ADDRESS, ASSET_MANAGER_ADDRESS, SWAP_PATH];
const swapAndRedeem: SwapAndRedeemInstance = await SwapAndRedeem.new(...args);
const fassetsSwapAndRedeemAddress = await swapAndRedeem.address;
try {
await run("verify:verify", {
address: fassetsSwapAndRedeemAddress,
constructorArguments: args,
});
} catch (e) {
console.log(e);
}
console.log("FAssetsSwapAndRedeem deployed to:", fassetsSwapAndRedeemAddress);
return swapAndRedeem;
}
async function main() {
// 2. Deploy and verify the `SwapAndRedeem` smart contract
const swapAndRedeem: SwapAndRedeemInstance = await deployAndVerifyContract();
// 3. Calculate Required Amounts
const swapAndRedeemAddress = await swapAndRedeem.address;
const amounts =
await swapAndRedeem.calculateRedemptionAmountIn(LOTS_TO_REDEEM);
const amountIn = amounts.amountIn;
const amountOut = amounts.amountOut;
console.log("Amount of tokens out (FXRP): ", amountOut.toString());
console.log("Amount of tokens in (WCFLR): ", amountIn.toString());
// 4. Approve spending WCFLR tokens
const ERC20 = artifacts.require("ERC20");
const wcflr: ERC20Instance = await ERC20.at(SWAP_PATH[0]);
const approveTx = await wcflr.approve(swapAndRedeemAddress, amountOut);
console.log("Approve transaction: ", approveTx);
// 5. Swap WCFLR for FXRP and redeem to underlying XRP token on XRP Ledger
const swapResult = await swapAndRedeemAddress.swapAndRedeem(
LOTS_TO_REDEEM,
UNDERLYING_ADDRESS,
);
console.log("Swap and redeem transaction: ", swapResult);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Code Breakdown
-
1. Dependencies and Constants
ASSET_MANAGER_ADDRESS
: The address of the FAssets Asset Manager contract.LOTS_TO_REDEEM
: The number of FAsset lots to redeem (typically set to 1).UNDERLYING_ADDRESS
: The XRPL address that will receive the redeemed assets.SWAP_ROUTER_ADDRESS
: The address of the Uniswap V2-compatible swap router.SWAP_PATH
: An array of token addresses defining the swap path from WCFLR to FXRP.
-
2. Deploy and Verify
- Deploys the
SwapAndRedeem
contract and verifies it using Flare Hardhat Starter Kit.
- Deploys the
-
3. Calculate Redemption Amounts
- Calls
calculateRedemptionAmountIn
to determine the required WCFLR amount.
- Calls
-
4. Approve Tokens
- Uses the ERC-20
approve
method to allow the contract to spend WCFLR.warningIn a production environment, you should use a secure method to approve spending the tokens.
- Uses the ERC-20
-
5. Execute the Swap and Redemption
- Calls
swapAndRedeem
to complete the FAssets redemption process.
- Calls
Run the Script
To run the script, use the Flare Hardhat Starter Kit with the following command:
npx hardhat run scripts/fassets/swapAndRedeem.ts --network coston
The script outputs transaction details, including swap amounts and redemption results.
For an in-depth explanation of the FAssets redemption process, refer to the FAssets Redeem Guide.
Conclusion
In this guide, you learned how to:
- swap WCFLR for FXRP using Uniswap V2 compatible router (in this example, BlazeSwap);
- redeem FXRP to XRP on the XRP Ledger using FAssets asset manager.
The complete code is in the Flare Hardhat Starter Kit.