Nhảy tới nội dung
This page uses machine translation from English, which may contain errors or unclear language. For the most accurate information, please see the original English version. Some content may be in the original English due to frequent updates. Help us improve this page's translation by joining our effort on Crowdin. (Crowdin translation page, Contributing guide)

Integrate Gas-Free USDT to KAIA Swaps

This guide provides an overview of the Gas-Free USDT to KAIA Swap feature, including its purpose, prerequisites, integration steps, and API references. It is designed to help developers integrate gas-free swap capabilities into their decentralized applications (DApps) on the Kaia network.

Introduction

A gasFreeSwapKaia API was introduced to enable users to perform gasless ERC20 token swaps (currently limited to USDT ) on the Kaia network without needing to hold KAIA tokens for gas fees, or even when covering transaction costs on behalf of users. The API specifically supports swapping USDT to KAIA using ERC20 permit signatures for a completely gasless user experience.

Benefits

  • 100% Gasless Experience: Users don't need any KAIA tokens to perform swaps
  • Enhanced User Onboarding: New users can start swapping tokens immediately without acquiring KAIA
  • ERC20 Permit Integration: Uses standard ERC20 permit signatures for secure, gasless token approvals

How It Works

  • User Initiates Swap: User selects USDT amount to swap for KAIA
  • Frontend Creates Permit: DApp constructs an ERC20 permit signature for the user to sign
  • User Signs Permit: User signs the permit message (no gas required)
  • DApp Calls API: Frontend sends swap parameters and permit signature to the API
  • Backend Executes: API validates the permit, executes the swap, and pays all gas fees
  • User Receives KAIA: Native KAIA tokens are sent directly to the user's wallet

Prerequisites and Supported Environments

Service Endpoints

Supported Token Pair

The API currently supports a single trading pair only:

Token In: USDT (0xd077a400968890eacc75cdc901f0356c943e4fdb)

Token Out: WKAIA (0x19aac5f612f524b754ca7e7c41cbfa2e981a4432)

Get Test Tokens

To get TEST tokens for Kairos Testnet:

  • Open ERC20 Faucet on Kaiascan
  • Go to the Contract tab, then select Write Contract
  • Locate the claim(token) function
  • Paste in the address of a supported GA token on Kairos (for this guide, use the address for TEST)
  • Click Query to submit the request. You should receive your TEST tokens shortly.

Smart Contract Requirements

The API interacts with the GaslessERC20PermitSwap smart contract which:

  • Supports ERC20 permit-based approvals
  • Integrates with Uniswap V2-compatible DEX
  • Converts WKAIA to native KAIA automatically
  • Enforces maximum swap limits for security

User Requirements

On mainnet, users must have zero KAIA balance to use this gasless swap service. This requirement ensures the service is used only by users who truly need gasless transactions for onboarding purposes. While on testnet, this restriction is relaxed for testing purposes.

The maximum swap amount is limited to 1 USDT on both mainnet and testnet. As this feature is designed for users to receive enough KAIA to begin their experience on the Kaia Chain.

Integration Steps

Complete Integration Example


const { JsonRpcProvider, Wallet } = require('@kaiachain/ethers-ext/v6');
const { Contract, parseUnits, formatUnits, Signature } = require('ethers');
async function fetchJson(url, init) {
if (typeof fetch !== 'undefined') {
return fetch(url, init);
}
const { default: nodeFetch } = await import('node-fetch');
return nodeFetch(url, init);
}
const GASLESS_SWAP_ABI = [
'function usdtToken() view returns (address)',
'function wkaiaToken() view returns (address)',
'function maxUsdtAmount() view returns (uint256)',
'function getExpectedOutput(address tokenIn, address tokenOut, uint256 amountIn) view returns (uint256)',
'function executeSwapWithPermit(address user, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',
];
const ERC20_METADATA_ABI = [
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
'function name() view returns (string)',
'function nonces(address owner) view returns (uint256)',
'function balanceOf(address owner) view returns (uint256)',
];
async function buildPermitSignature({ token, owner, spender, value, deadline, domainVersion = '1' }) {
const [name, version, network, verifyingContract, nonce] = await Promise.all([
token.name(),
Promise.resolve(domainVersion),
owner.provider.getNetwork(),
token.getAddress(),
token.nonces(owner.address),
]);
const domain = {
name,
version,
chainId: Number(network.chainId),
verifyingContract,
};
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
};
const message = {
owner: owner.address,
spender,
value,
nonce,
deadline,
};
return Signature.from(await owner.signTypedData(domain, types, message));
}
async function executeGaslessSwap({
rpcUrl,
serverUrl,
userWallet,
contractAddress,
amountIn = '0.01', // Amount in USDT
slippageBps = 50, // 0.5% slippage
permitDeadlineSeconds = 600 // 10 minutes
}) {
console.log('🚀 Starting gasless swap');
const provider = new JsonRpcProvider(rpcUrl);
const wallet = userWallet.connect(provider);
const swap = new Contract(contractAddress, GASLESS_SWAP_ABI, provider);
// Get token addresses from contract
const [tokenInAddress, tokenOutAddress, maxUsdtAmount] = await Promise.all([
swap.usdtToken(),
swap.wkaiaToken(),
swap.maxUsdtAmount(),
]);
const tokenIn = new Contract(tokenInAddress, ERC20_METADATA_ABI, provider);
const tokenOut = new Contract(tokenOutAddress, ERC20_METADATA_ABI, provider);
const [tokenInDecimals, tokenOutDecimals, tokenInSymbol, tokenOutSymbol] = await Promise.all([
tokenIn.decimals(),
tokenOut.decimals(),
tokenIn.symbol(),
tokenOut.symbol(),
]);
const amountInWei = parseUnits(amountIn, tokenInDecimals);
// Check if amount exceeds contract maximum
if (amountInWei > maxUsdtAmount) {
throw new Error(`Amount (${amountIn} ${tokenInSymbol}) exceeds contract cap (${formatUnits(maxUsdtAmount, tokenInDecimals)} ${tokenInSymbol})`);
}
// Get expected output and calculate minimum with slippage
const expectedOut = await swap.getExpectedOutput(tokenInAddress, tokenOutAddress, amountInWei);
const amountOutMin = (expectedOut * BigInt(10_000 - slippageBps)) / 10_000n;
// Create permit signature
const deadline = BigInt(Math.floor(Date.now() / 1000) + permitDeadlineSeconds);
const signature = await buildPermitSignature({
token: tokenIn,
owner: wallet,
spender: contractAddress,
value: amountInWei,
deadline,
});
// Prepare API payload
const payload = {
swap: {
user: wallet.address,
tokenIn: tokenInAddress,
tokenOut: tokenOutAddress,
amountIn: amountInWei.toString(),
amountOutMin: amountOutMin.toString(),
deadline: deadline.toString(),
},
permitSignature: signature.serialized,
};
console.log('From:', wallet.address);
console.log('Swap amount:', formatUnits(amountInWei, tokenInDecimals), tokenInSymbol);
console.log('Minimum out:', formatUnits(amountOutMin, tokenOutDecimals), tokenOutSymbol);
// Check balance before swap
const balanceBefore = await provider.getBalance(wallet.address);
// Call the API
const response = await fetchJson(`${serverUrl}/api/gasFreeSwapKaia`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const result = await response.json().catch(() => ({}));
console.log('HTTP status:', response.status);
console.log('Response:', JSON.stringify(result, null, 2));
if (response.ok && result.status) {
console.log('🎉 Gasless swap request succeeded');
// Check balance after swap
const balanceAfter = await provider.getBalance(wallet.address);
console.log('Balance before:', formatUnits(balanceBefore, 18), 'KAIA');
console.log('Balance after:', formatUnits(balanceAfter, 18), 'KAIA');
console.log('Balance difference:', formatUnits(balanceAfter - balanceBefore, 18), 'KAIA');
return result;
} else {
console.error('❌ Gasless swap request failed');
throw new Error(`Swap failed: ${result.data || result.message || 'Unknown error'}`);
}
}
// Usage example
async function main() {
try {
const userWallet = new Wallet('your_private_key');
const result = await executeGaslessSwap({
rpcUrl: 'https://public-en-kairos.node.kaia.io',
serverUrl: 'https://fee-delegation-kairos.kaia.io',
userWallet: userWallet,
contractAddress: '0xaaFe47636ACe87E2B8CAaFADb03E87090277Ff7B',
amountIn: '0.002',
slippageBps: 50,
});
console.log('Transaction hash:', result.data.hash);
} catch (error) {
console.error('💥 Swap failed:', error.message);
}
}
main();

API Reference Endpoint

  • URL: /api/gasFreeSwapKaia
  • Method: POST
  • Content-Type: application/json

Request Body


{
"swap": {
"user": "0x742d35Cc6635C0532925a3b8D400e6D2A4b8E0bb",
"tokenIn": "0xcb00ba2cab67a3771f9ca1fa48fda8881b457750",
"tokenOut": "0x043c471bEe060e00A56CcD02c0Ca286808a5A436",
"amountIn": "1000000",
"amountOutMin": "950000000000000000",
"deadline": "1699123456"
},
"permitSignature": "0x…65-byte signature string…"
}

Parameters

swap (object, required):

  • user (string): Address of the token owner who signed the permit
  • tokenIn (string): Address of input token (must match configured USDT address)
  • tokenOut (string): Address of output token (must match configured WKAIA address)
  • amountIn (string): Amount of input tokens as string (in wei/smallest unit)
  • amountOutMin (string): Minimum expected output tokens (slippage protection)
  • deadline (string): Unix timestamp (seconds) for permit and swap expiration

permitSignature (string, required):

  • Must be a valid hex string of 65 bytes
  • Contains the serialized ERC20 permit signature

Response Format

Success Response (200)


{
"message": "Request was successful",
"data": {
"_type": "TransactionReceipt",
"blockHash": "0x2a7ae196f6e7363fe3cfc79132c1d16292d159e231d73b4308f598a3222d1f57",
"blockNumber": 191523443,
"contractAddress": null,
"cumulativeGasUsed": "215000",
"from": "0x6C4ED74027ab609f506efCdd224041c9F5b5CDE1",
"gasPrice": "25000000000",
"gasUsed": "215000",
"hash": "0x0ca73736ceecf2dcf0ec2e1f65760d0b4f7348726cb9a0477710172b1dd44350",
"status": 1,
"to": "0x45bD04d5f14DD9AB908109cFEa816F758FaE6709",
"type": 49,
"feePayer": "0x1234567890abcdef1234567890abcdef12345678",
"feePayerSignatures": ["0x..."],
"logs": [
{
"address": "0x...",
"topics": ["0x..."],
"data": "0x..."
}
]
},
"status": true,
"requestId": "req_abc123def456"
}

Error Responses

400 Bad Request - Validation Errors:


{
"message": "Bad request",
"data": "Permit deadline has expired",
"error": "BAD_REQUEST",
"status": false,
"requestId": "req_error_123"
}

400 Bad Request - Transaction Revert:


{
"message": "Bad request",
"data": "execution reverted: Permit already used",
"error": "BAD_REQUEST",
"status": false,
"requestId": "req_revert_456"
}

500 Internal Server Error:


{
"message": "Internal server error",
"data": "Sending transaction was failed after 5 try, network is busy. Error message: Network timeout",
"error": "INTERNAL_ERROR",
"status": false,
"requestId": "req_error_789"
}

Error Handling

Common Error Scenarios

ErrorHTTP StatusDescriptionSolution
Missing required fields400swap or permitSignature missingInclude all required parameters
User has KAIA balance400User must have zero KAIA balanceOnly users with zero KAIA balance can use this service
Invalid signature format400Permit signature is not a valid hex stringProvide a valid 65-byte hex signature
Invalid address400Malformed Ethereum addressValidate if addresses are valid
Unsupported token400Token not in allowed listUse only configured token addresses
Expired deadline400Permit deadline in the pastUse future timestamp
Amount too large400Exceeds contract maximumCheck maxUsdtAmount() from contract
Insufficient quote400Slippage too strictIncrease slippage tolerance or reduce amount
Gas price too high400Network congestionWait for lower gas prices
Network timeout500RPC provider issuesRetry request after delay

Security Considerations

Gas Price Protection

The API rejects transactions when gas prices exceed 50 gwei to prevent excessive costs. Monitor gas prices and inform users during high congestion periods.

Signature Security

  • Never reuse permit signatures
  • Always use reasonable deadlines (5-30 minutes)
  • Validate all parameters before signing
  • Use HTTPS for all API communications

Smart Contract Details

GaslessERC20PermitSwap Contract Address


0x45bD04d5f14DD9AB908109cFEa816F758FaE6709

Key Functions

executeSwapWithPermit - Executes the gasless swap using a permit signature:

  • Validates permit and swap parameters
  • Transfers tokens using permit
  • Executes DEX swap
  • Converts WKAIA to native KAIA
  • Sends native KAIA to user

getExpectedOutput - View function to get expected output amount:


function getExpectedOutput(
address tokenIn,
address tokenOut,
uint256 amountIn
) external view returns (uint256)

Contract Limits

  • Maximum USDT per swap: 1,000,000 (1 USDT with 6 decimals)
  • Supported pair: USDT → WKAIA → Native KAIA
  • Anti-replay protection via signature tracking

Additional Resources

Cải thiện trang này