Contract ID: SaucerSwapV2SwapRouter
Consider the tokenโs decimal places when determining the output amount.
The output values should be in the tokenโs smallest unit. For the SAUCE token, which has 6 decimal places, an input of 123.45 SAUCE should be entered as 123450000 (123.45 multiplied by 10^6).
Ensure that the โtoโ account has the output token id associated prior to executing the swap. Failure to do so will result in a TOKEN_NOT_ASSOCIATED_TO_ACCOUNT error.
When providing HBAR in the path array, use the Wrapped HBAR token id (WHBAR).
Swap Exact HBAR for Tokens
Swap an exact amount of HBAR for a minimum token amount Solidity function name: exactInput| Struct Parameter Name | Description |
|---|---|
| bytes path | A bytes array representing a route path including fees data |
| recipient | EVM address of the token recipient |
| deadline | Deadline in Unix seconds |
| amountIn | The exact input token amount in its smallest unit |
| amountOutMinimum | The minimum token amount to receive in its smallest unit |
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
/// @inheritdoc ISwapRouter
function exactInput(ExactInputParams memory params)
external
payable
override
checkDeadline(params.deadline)
returns (uint256 amountOut)
{
address payer = msg.sender; // msg.sender pays for the first hop
while (true) {
bool hasMultiplePools = params.path.hasMultiplePools();
// the outputs of prior swaps become the inputs to subsequent ones
params.amountIn = exactInputInternal(
params.amountIn,
hasMultiplePools ? address(this) : params.recipient, // for intermediate swaps, this contract custodies
0,
SwapCallbackData({
path: params.path.getFirstPool(), // only the first pool in the path is necessary
payer: payer
})
);
// decide whether to continue or terminate
if (hasMultiplePools) {
payer = address(this); // at this point, the caller has paid
params.path = params.path.skipToken();
} else {
amountOut = params.amountIn;
break;
}
}
require(amountOut >= params.amountOutMinimum, 'Too little received');
}
Set the minimum output token amount (amountOutMinimum) with caution.A high minimum might lead to a swap failure due to insufficient liquidity or rapid price movements. Conversely, setting the minimum too low can expose you to significant slippage, potentially resulting in a financial loss as you might receive far fewer tokens than expected.
It is recommended to include refundETH in the multicall in case excess HBAR was sent to the contract.
The data passed to the โpathโ parameter follows this format: [token, fee, token, fee, token, โฆ], with each โtokenโ in the route being 20 bytes long and each โfeeโ being 3 bytes long. Example, 0x0001F4 (500) for a 0.05% fee.
Code Overview
- JavaScript SDK
Resources:
- SaucerSwap deployed contract IDs
- Hedera JavaScript SDK
- Ethers.js docs (v6)
- Associate tokens to an account
Typescript
import * as ethers from 'ethers'; //V6
import {
ContractExecuteTransaction,
TokenAssociateTransaction,
..
} from '@hashgraph/sdk';
//Client pre-checks:
// - Output token is associated
//load ABI data containing SwapRouter, PeripheryPayments and Multicall functions
const abiInterfaces = new ethers.Interface(abi);
//ExactInputParams
const params = {
path: routeDataWithFee, //'0x...'
recipient: recipientAddress, //'0x...' - user's recipient address
deadline: deadline, //Unix seconds
amountIn: inputTinybar, //in Tinybar
amountOutMinimum: outputAmountMin//in token's smallest unit
};
//encode each function individually
const swapEncoded = abiInterfaces.encodeFunctionData('exactInput', [params]);
const refundHBAREncoded = abiInterfaces.encodeFunctionData('refundETH');
//multi-call parameter: bytes[]
const multiCallParam = [swapEncoded, refundHBAREncoded];
//get encoded data for the multicall involving both functions
const encodedData = abiInterfaces.encodeFunctionData('multicall', [multiCallParam]);
//get encoded data as Uint8Array
const encodedDataAsUint8Array = hexToUint8Array(encodedData);
const response = await new ContractExecuteTransaction()
.setPayableAmount(Hbar.from(inputTinybar, HbarUnit.Tinybar))
.setContractId(swapRouterContractId)
.setGas(gasLim)
.setFunctionParameters(encodedDataAsUint8Array)
.execute(client);
const record = await response.getRecord(client);
const result = record.contractFunctionResult!;
const values = result.getResult(['uint256']);
const amountOut = values[0]; //uint256 amountOut
Swap HBAR for Exact Tokens
Swap a maximum amount of HBAR to receive an exact tokens amount. Solidity function name: exactOutput| Struct Parameter Name | Description |
|---|---|
| bytes path | A bytes array representing a route path including fees data |
| recipient | EVM address of the token recipient |
| deadline | Deadline in Unix seconds |
| amountOut | The exact input token amount in its smallest unit |
| amountInMaximum | The minimum token amount to receive in its smallest unit |
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
/// @inheritdoc ISwapRouter
function exactOutput(ExactOutputParams calldata params)
external
payable
override
checkDeadline(params.deadline)
returns (uint256 amountIn)
{
// it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output
// swap, which happens first, and subsequent swaps are paid for within nested callback frames
exactOutputInternal(
params.amountOut,
params.recipient,
0,
SwapCallbackData({path: params.path, payer: msg.sender})
);
amountIn = amountInCached;
require(amountIn <= params.amountInMaximum, 'Too much requested');
amountInCached = DEFAULT_AMOUNT_IN_CACHED;
}
Ensure that the refundETH is included in the multicall so that any excess payable HBAR amount, up to the maximum amount, is refunded to the sender.
Set the maximum input token amount (amountInMaximum) with caution.A low maximum might lead to a swap failure if the required liquidity surpasses this limit or due to rapid price movements. Conversely, setting it too high can expose you to significant slippage, potentially leading to a financial loss as you might spend far more tokens than expected.
The data passed to the โpathโ parameter follows this format: [token, fee, token, fee, token, โฆ], but reversed (i.e. the first token in the array should be output token), with each โtokenโ in the route being 20 bytes long and each โfeeโ being 3 bytes long. Example, 0x000BB8 (3000) for a 0.30% fee.
Code Overview
- JavaScript SDK
Resources:
- SaucerSwap deployed contract IDs
- Ethers.js docs (v6)
- Hedera JavaScript SDK
- Associate tokens to an account
Typescript
import * as ethers from 'ethers'; //V6
import {
ContractExecuteTransaction,
TokenAssociateTransaction,
..
} from '@hashgraph/sdk';
//Client pre-checks:
// - Output token is associated
//load ABI data containing SwapRouter, PeripheryPayments and Multicall functions
const abiInterfaces = new ethers.Interface(abi);
//ExactOutputParams
const params = {
path: routeDataWithFee, //'0x...' (reversed route path)
recipient: recipientAddress, //'0x...' - user's recipient address
deadline: deadline, //Unix seconds
amountOut: outputAmount, //in token's smallest unit
amountInMaximum: inputTinybarMax //in Tinybar
};
//encode each function individually
const swapEncoded = abiInterfaces.encodeFunctionData('exactOutput', [params]);
const refundHBAREncoded = abiInterfaces.encodeFunctionData('refundETH');
//multi-call parameter: bytes[]
const multiCallParam = [swapEncoded, refundHBAREncoded];
//get encoded data for the multicall involving both functions
const encodedData = abiInterfaces.encodeFunctionData('multicall', [multiCallParam]);
//get encoded data as Uint8Array
const encodedDataAsUint8Array = hexToUint8Array(encodedData);
const response = await new ContractExecuteTransaction()
.setPayableAmount(Hbar.from(inputTinybarMax, HbarUnit.Tinybar))
.setContractId(swapRouterContractId)
.setGas(gasLim)
.setFunctionParameters(encodedDataAsUint8Array)
.execute(client);
const record = await response.getRecord(client);
const result = record.contractFunctionResult!;
const values = result.getResult(['uint256']);
const amountIn = values[0]; //uint256 amountIn