Integration guide
This guide provides practical examples for integrating with the Kinetiq liquid staking protocol using both Solidity smart contracts and TypeScript with Viem.
Contract addresses (Hyperliquid Mainnet)
// Core contract addresses
address constant STAKING_MANAGER = 0x393D0B87Ed38fc779FD9611144aE649BA6082109;
address constant KHYPE_TOKEN = 0xfD739d4e423301CE9385c1fb8850539D657C296D;
address constant STAKING_ACCOUNTANT = 0x9209648Ec9D448EF57116B73A2f081835643dc7A;
address constant VALIDATOR_MANAGER = 0x4b797A93DfC3D18Cf98B7322a2b142FA8007508f;Interface imports
import "./interfaces/IStakingManager.sol";
import "./interfaces/IKHYPEToken.sol";
import "./interfaces/IStakingAccountant.sol";1. Depositing (staking) HYPE tokens
Basic deposit example
pragma solidity ^0.8.20;
import "./interfaces/IStakingManager.sol";
import "./interfaces/IKHYPEToken.sol";
contract KinetiqDepositor {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
constructor(address _stakingManager, address _kHYPE) {
stakingManager = IStakingManager(_stakingManager);
kHYPE = IKHYPEToken(_kHYPE);
}
/**
* @notice Stake HYPE tokens and receive kHYPE
* @dev Sends native HYPE tokens to the staking manager
*/
function stakeHYPE() external payable {
require(msg.value > 0, "Must send HYPE to stake");
// Get user's kHYPE balance before staking
uint256 kHYPEBefore = kHYPE.balanceOf(msg.sender);
// Stake HYPE tokens (msg.value is automatically sent)
stakingManager.stake{value: msg.value}();
// Get user's kHYPE balance after staking
uint256 kHYPEAfter = kHYPE.balanceOf(msg.sender);
emit StakeCompleted(msg.sender, msg.value, kHYPEAfter - kHYPEBefore);
}
event StakeCompleted(address indexed user, uint256 hypeAmount, uint256 kHYPEReceived);
}Advanced deposit with validation
contract AdvancedKinetiqDepositor {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
IStakingAccountant public immutable stakingAccountant;
constructor(address _stakingManager, address _kHYPE, address _stakingAccountant) {
stakingManager = IStakingManager(_stakingManager);
kHYPE = IKHYPEToken(_kHYPE);
stakingAccountant = IStakingAccountant(_stakingAccountant);
}
/**
* @notice Stake HYPE with validation and expected return calculation
* @param minKHYPEExpected Minimum kHYPE tokens expected to receive
*/
function stakeWithValidation(uint256 minKHYPEExpected) external payable {
require(msg.value > 0, "Must send HYPE to stake");
// Check protocol limits
require(msg.value >= stakingManager.minStakeAmount(), "Below minimum stake");
require(msg.value <= stakingManager.maxStakeAmount(), "Above maximum stake");
// Calculate expected kHYPE amount
uint256 expectedKHYPE = stakingAccountant.HYPEToKHYPE(msg.value);
require(expectedKHYPE >= minKHYPEExpected, "Expected kHYPE too low");
// Check if staking would exceed limit
uint256 currentTotal = stakingManager.totalStaked();
uint256 stakingLimit = stakingManager.stakingLimit();
require(currentTotal + msg.value <= stakingLimit, "Would exceed staking limit");
uint256 kHYPEBefore = kHYPE.balanceOf(msg.sender);
// Execute stake
stakingManager.stake{value: msg.value}();
uint256 kHYPEReceived = kHYPE.balanceOf(msg.sender) - kHYPEBefore;
require(kHYPEReceived >= minKHYPEExpected, "Insufficient kHYPE received");
emit ValidatedStakeCompleted(msg.sender, msg.value, kHYPEReceived, expectedKHYPE);
}
event ValidatedStakeCompleted(
address indexed user,
uint256 hypeAmount,
uint256 kHYPEReceived,
uint256 expectedKHYPE
);
}2. Withdrawals (unstaking)
Basic withdrawal flow
contract KinetiqWithdrawer {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
// Track user withdrawal requests
mapping(address => uint256[]) public userWithdrawalIds;
constructor(address _stakingManager, address _kHYPE) {
stakingManager = IStakingManager(_stakingManager);
kHYPE = IKHYPEToken(_kHYPE);
}
/**
* @notice Queue a withdrawal request
* @param kHYPEAmount Amount of kHYPE tokens to withdraw
*/
function queueWithdrawal(uint256 kHYPEAmount) external {
require(kHYPEAmount > 0, "Amount must be positive");
require(kHYPE.balanceOf(msg.sender) >= kHYPEAmount, "Insufficient kHYPE balance");
// Get next withdrawal ID for user
uint256 withdrawalId = stakingManager.nextWithdrawalId(msg.sender);
// Queue the withdrawal (this will burn kHYPE tokens)
stakingManager.queueWithdrawal(kHYPEAmount);
// Track withdrawal ID for user
userWithdrawalIds[msg.sender].push(withdrawalId);
emit WithdrawalQueued(msg.sender, withdrawalId, kHYPEAmount);
}
/**
* @notice Confirm a withdrawal request after delay period
* @param withdrawalId ID of the withdrawal request
*/
function confirmWithdrawal(uint256 withdrawalId) external {
// Get withdrawal request details
IStakingManager.WithdrawalRequest memory request =
stakingManager.withdrawalRequests(msg.sender, withdrawalId);
require(request.timestamp > 0, "Withdrawal request not found");
require(
block.timestamp >= request.timestamp + stakingManager.withdrawalDelay(),
"Withdrawal delay not met"
);
uint256 balanceBefore = address(msg.sender).balance;
// Confirm withdrawal (this will send HYPE to user)
stakingManager.confirmWithdrawal(withdrawalId);
uint256 hypeReceived = address(msg.sender).balance - balanceBefore;
emit WithdrawalConfirmed(msg.sender, withdrawalId, hypeReceived);
}
/**
* @notice Get all withdrawal IDs for a user
* @param user User address
* @return Array of withdrawal IDs
*/
function getUserWithdrawalIds(address user) external view returns (uint256[] memory) {
return userWithdrawalIds[user];
}
event WithdrawalQueued(address indexed user, uint256 indexed withdrawalId, uint256 kHYPEAmount);
event WithdrawalConfirmed(address indexed user, uint256 indexed withdrawalId, uint256 hypeReceived);
}Advanced withdrawal with batching
contract BatchKinetiqWithdrawer {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
IStakingAccountant public immutable stakingAccountant;
constructor(address _stakingManager, address _kHYPE, address _stakingAccountant) {
stakingManager = IStakingManager(_stakingManager);
kHYPE = IKHYPEToken(_kHYPE);
stakingAccountant = IStakingAccountant(_stakingAccountant);
}
/**
* @notice Queue multiple withdrawal requests in a single transaction
* @param kHYPEAmounts Array of kHYPE amounts to withdraw
*/
function batchQueueWithdrawals(uint256[] calldata kHYPEAmounts) external {
require(kHYPEAmounts.length > 0, "No amounts provided");
uint256 totalKHYPE = 0;
for (uint256 i = 0; i < kHYPEAmounts.length; i++) {
require(kHYPEAmounts[i] > 0, "Amount must be positive");
totalKHYPE += kHYPEAmounts[i];
}
require(kHYPE.balanceOf(msg.sender) >= totalKHYPE, "Insufficient kHYPE balance");
uint256[] memory withdrawalIds = new uint256[](kHYPEAmounts.length);
for (uint256 i = 0; i < kHYPEAmounts.length; i++) {
withdrawalIds[i] = stakingManager.nextWithdrawalId(msg.sender);
stakingManager.queueWithdrawal(kHYPEAmounts[i]);
}
emit BatchWithdrawalQueued(msg.sender, withdrawalIds, kHYPEAmounts);
}
/**
* @notice Confirm multiple withdrawal requests
* @param withdrawalIds Array of withdrawal IDs to confirm
*/
function batchConfirmWithdrawals(uint256[] calldata withdrawalIds) external {
require(withdrawalIds.length > 0, "No withdrawal IDs provided");
uint256 totalHypeReceived = 0;
uint256 balanceBefore = address(msg.sender).balance;
for (uint256 i = 0; i < withdrawalIds.length; i++) {
// Verify withdrawal is ready
IStakingManager.WithdrawalRequest memory request =
stakingManager.withdrawalRequests(msg.sender, withdrawalIds[i]);
require(request.timestamp > 0, "Withdrawal request not found");
require(
block.timestamp >= request.timestamp + stakingManager.withdrawalDelay(),
"Withdrawal delay not met"
);
stakingManager.confirmWithdrawal(withdrawalIds[i]);
}
totalHypeReceived = address(msg.sender).balance - balanceBefore;
emit BatchWithdrawalConfirmed(msg.sender, withdrawalIds, totalHypeReceived);
}
event BatchWithdrawalQueued(address indexed user, uint256[] withdrawalIds, uint256[] kHYPEAmounts);
event BatchWithdrawalConfirmed(address indexed user, uint256[] withdrawalIds, uint256 totalHypeReceived);
}3. Complete integration example
pragma solidity ^0.8.20;
/**
* @title KinetiqIntegrator
* @notice Complete integration example for Kinetiq protocol
*/
contract KinetiqIntegrator {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
IStakingAccountant public immutable stakingAccountant;
constructor(
address _stakingManager,
address _kHYPE,
address _stakingAccountant
) {
stakingManager = IStakingManager(_stakingManager);
kHYPE = IKHYPEToken(_kHYPE);
stakingAccountant = IStakingAccountant(_stakingAccountant);
}
/**
* @notice Get current exchange rate from HYPE to kHYPE
* @param hypeAmount Amount of HYPE tokens
* @return kHYPE amount that would be received
*/
function getKHYPEForHYPE(uint256 hypeAmount) external view returns (uint256) {
return stakingAccountant.HYPEToKHYPE(hypeAmount);
}
/**
* @notice Get current exchange rate from kHYPE to HYPE
* @param kHYPEAmount Amount of kHYPE tokens
* @return HYPE amount that would be received (before fees)
*/
function getHYPEForKHYPE(uint256 kHYPEAmount) external view returns (uint256) {
return stakingAccountant.kHYPEToHYPE(kHYPEAmount);
}
/**
* @notice Calculate withdrawal fee for unstaking
* @param kHYPEAmount Amount of kHYPE to unstake
* @return fee amount in kHYPE tokens
*/
function calculateWithdrawalFee(uint256 kHYPEAmount) external view returns (uint256) {
uint256 feeRate = stakingManager.unstakeFeeRate();
return (kHYPEAmount * feeRate) / 10000; // feeRate is in basis points
}
/**
* @notice Check if withdrawal request is ready to confirm
* @param user User address
* @param withdrawalId Withdrawal request ID
* @return isReady True if withdrawal can be confirmed
* @return timeRemaining Seconds remaining until confirmation is available
*/
function isWithdrawalReady(address user, uint256 withdrawalId)
external
view
returns (bool isReady, uint256 timeRemaining)
{
IStakingManager.WithdrawalRequest memory request =
stakingManager.withdrawalRequests(user, withdrawalId);
if (request.timestamp == 0) {
return (false, 0);
}
uint256 confirmTime = request.timestamp + stakingManager.withdrawalDelay();
if (block.timestamp >= confirmTime) {
return (true, 0);
} else {
return (false, confirmTime - block.timestamp);
}
}
/**
* @notice Get protocol status and limits
* @return minStake Minimum stake amount
* @return maxStake Maximum stake amount
* @return stakingLimit Total staking limit
* @return totalStaked Current total staked
* @return withdrawalDelay Withdrawal delay in seconds
* @return unstakeFeeRate Unstaking fee rate in basis points
*/
function getProtocolInfo()
external
view
returns (
uint256 minStake,
uint256 maxStake,
uint256 stakingLimit,
uint256 totalStaked,
uint256 withdrawalDelay,
uint256 unstakeFeeRate
)
{
return (
stakingManager.minStakeAmount(),
stakingManager.maxStakeAmount(),
stakingManager.stakingLimit(),
stakingManager.totalStaked(),
stakingManager.withdrawalDelay(),
stakingManager.unstakeFeeRate()
);
}
}Error handling and best practices
Common errors and solutions
contract KinetiqErrorHandler {
IStakingManager public immutable stakingManager;
IKHYPEToken public immutable kHYPE;
/**
* @notice Safe staking with comprehensive error handling
*/
function safeStake() external payable {
// Check basic requirements
require(msg.value > 0, "KinetiqErrorHandler: No HYPE sent");
// Check protocol limits
uint256 minStake = stakingManager.minStakeAmount();
require(msg.value >= minStake, "KinetiqErrorHandler: Below minimum stake");
uint256 maxStake = stakingManager.maxStakeAmount();
require(msg.value <= maxStake, "KinetiqErrorHandler: Above maximum stake");
// Check staking limit
uint256 totalStaked = stakingManager.totalStaked();
uint256 stakingLimit = stakingManager.stakingLimit();
require(
totalStaked + msg.value <= stakingLimit,
"KinetiqErrorHandler: Would exceed staking limit"
);
// Check if user is whitelisted (if whitelist is enabled)
if (stakingManager.whitelistLength() > 0) {
require(
stakingManager.isWhitelisted(msg.sender),
"KinetiqErrorHandler: Not whitelisted"
);
}
try stakingManager.stake{value: msg.value}() {
emit StakeSuccessful(msg.sender, msg.value);
} catch Error(string memory reason) {
emit StakeFailed(msg.sender, msg.value, reason);
// Return HYPE to user
payable(msg.sender).transfer(msg.value);
} catch {
emit StakeFailed(msg.sender, msg.value, "Unknown error");
// Return HYPE to user
payable(msg.sender).transfer(msg.value);
}
}
/**
* @notice Safe withdrawal queue with error handling
*/
function safeQueueWithdrawal(uint256 kHYPEAmount) external {
require(kHYPEAmount > 0, "KinetiqErrorHandler: Invalid amount");
require(
kHYPE.balanceOf(msg.sender) >= kHYPEAmount,
"KinetiqErrorHandler: Insufficient kHYPE balance"
);
try stakingManager.queueWithdrawal(kHYPEAmount) {
emit WithdrawalQueueSuccessful(msg.sender, kHYPEAmount);
} catch Error(string memory reason) {
emit WithdrawalQueueFailed(msg.sender, kHYPEAmount, reason);
} catch {
emit WithdrawalQueueFailed(msg.sender, kHYPEAmount, "Unknown error");
}
}
event StakeSuccessful(address indexed user, uint256 amount);
event StakeFailed(address indexed user, uint256 amount, string reason);
event WithdrawalQueueSuccessful(address indexed user, uint256 amount);
event WithdrawalQueueFailed(address indexed user, uint256 amount, string reason);
}Integration best practices
- Always check protocol limits before staking
- Calculate expected returns and set minimum slippage protection
- Handle errors gracefully with try-catch blocks
- Verify withdrawal readiness before attempting confirmation
- Track withdrawal IDs for users in your contract
- Consider gas optimization for batch operations
- Monitor protocol events for state changes
- Test thoroughly on testnet before mainnet deployment
Gas optimization tips
- Batch multiple operations when possible
- Cache frequently accessed values
- Use
viewfunctions to estimate gas before transactions - Consider using multicall patterns for complex operations
TypeScript integration with viem
This section provides examples for integrating with Kinetiq protocol using TypeScript and Viem for frontend applications.
Setup and dependencies
npm install viem wagmi
# or
yarn add viem wagmi
# or
pnpm i viem wagmiContract configuration
import {
createPublicClient,
createWalletClient,
formatEther,
http,
parseEther,
} from 'viem'
import { hyperliquid } from 'viem/chains'
// Contract addresses
export const CONTRACTS = {
KHYPE_TOKEN: '0xfD739d4e423301CE9385c1fb8850539D657C296D' as const,
STAKING_ACCOUNTANT: '0x9209648Ec9D448EF57116B73A2f081835643dc7A' as const,
STAKING_MANAGER: '0x393D0B87Ed38fc779FD9611144aE649BA6082109' as const,
VALIDATOR_MANAGER: '0x4b797A93DfC3D18Cf98B7322a2b142FA8007508f' as const,
} as const
// ABI fragments for the functions we need
export const KHYPE_ABI = [
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'totalSupply',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
name: 'approve',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
] as const
export const STAKING_ACCOUNTANT_ABI = [
{
inputs: [{ name: 'HYPEAmount', type: 'uint256' }],
name: 'HYPEToKHYPE',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'kHYPEAmount', type: 'uint256' }],
name: 'kHYPEToHYPE',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
] as const
export const STAKING_MANAGER_ABI = [
{
inputs: [],
name: 'stake',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [{ name: 'amount', type: 'uint256' }],
name: 'queueWithdrawal',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'withdrawalId', type: 'uint256' }],
name: 'confirmWithdrawal',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'user', type: 'address' },
{ name: 'id', type: 'uint256' },
],
name: 'withdrawalRequests',
outputs: [
{
components: [
{ name: 'hypeAmount', type: 'uint256' },
{ name: 'kHYPEAmount', type: 'uint256' },
{ name: 'kHYPEFee', type: 'uint256' },
{ name: 'bufferUsed', type: 'uint256' },
{ name: 'timestamp', type: 'uint256' },
],
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'user', type: 'address' }],
name: 'nextWithdrawalId',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'minStakeAmount',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'maxStakeAmount',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'withdrawalDelay',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'unstakeFeeRate',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'totalStaked',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ indexed: true, name: 'staking', type: 'address' },
{ indexed: true, name: 'staker', type: 'address' },
{ indexed: false, name: 'amount', type: 'uint256' },
],
name: 'StakeReceived',
type: 'event',
},
{
inputs: [
{ indexed: true, name: 'staking', type: 'address' },
{ indexed: true, name: 'user', type: 'address' },
{ indexed: true, name: 'withdrawalId', type: 'uint256' },
{ indexed: false, name: 'kHYPEAmount', type: 'uint256' },
{ indexed: false, name: 'hypeAmount', type: 'uint256' },
{ indexed: false, name: 'feeAmount', type: 'uint256' },
],
name: 'WithdrawalQueued',
type: 'event',
},
] as const
// Client setup
export const publicClient = createPublicClient({
chain: hyperliquid,
transport: http(),
})1. Depositing (staking) HYPE
import {
type Address,
formatEther,
parseEther,
type PublicClient,
type WalletClient,
} from 'viem'
/**
* Get current exchange rate for HYPE to kHYPE
*/
export const getKHYPERate = async ({
amount,
publicClient,
}: {
amount: bigint
publicClient: PublicClient
}): Promise<bigint> => {
const result = await publicClient.readContract({
abi: STAKING_ACCOUNTANT_ABI,
address: CONTRACTS.STAKING_ACCOUNTANT,
args: [amount],
functionName: 'HYPEToKHYPE',
})
return result
}
/**
* Get protocol limits and information
*/
export const getProtocolInfo = async ({
publicClient,
}: {
publicClient: PublicClient
}) => {
const [minStake, maxStake, totalStaked, withdrawalDelay, unstakeFeeRate] =
await Promise.all([
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'minStakeAmount',
}),
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'maxStakeAmount',
}),
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'totalStaked',
}),
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'withdrawalDelay',
}),
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'unstakeFeeRate',
}),
])
return {
maxStake,
minStake,
totalStaked,
unstakeFeeRate,
withdrawalDelay,
}
}
/**
* Validate stake amount against protocol limits
*/
export const validateStakeAmount = async ({
amount,
publicClient,
}: {
amount: bigint
publicClient: PublicClient
}): Promise<{ error?: string; valid: boolean }> => {
try {
const { maxStake, minStake } = await getProtocolInfo({ publicClient })
if (amount < minStake) {
return {
error: `Amount below minimum stake of ${formatEther(minStake)} HYPE`,
valid: false,
}
}
if (amount > maxStake) {
return {
error: `Amount above maximum stake of ${formatEther(maxStake)} HYPE`,
valid: false,
}
}
return { valid: true }
} catch (error) {
return { error: 'Failed to validate stake amount', valid: false }
}
}
/**
* Stake HYPE tokens
*/
export const stakeHYPE = async ({
amountInHYPE,
publicClient,
walletClient,
}: {
amountInHYPE: string
publicClient: PublicClient
walletClient: WalletClient
}): Promise<{ expectedKHYPE: bigint; hash: string }> => {
const amount = parseEther(amountInHYPE)
// Validate amount
const validation = await validateStakeAmount({ amount, publicClient })
if (!validation.valid) {
throw new Error(validation.error)
}
// Get expected kHYPE amount
const expectedKHYPE = await getKHYPERate({ amount, publicClient })
// Execute stake transaction
const hash = await walletClient.writeContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'stake',
value: amount,
})
return { expectedKHYPE, hash }
}
/**
* Estimate gas for staking
*/
export const estimateStakeGas = async ({
amountInHYPE,
publicClient,
walletClient,
}: {
amountInHYPE: string
publicClient: PublicClient
walletClient: WalletClient
}): Promise<bigint> => {
const amount = parseEther(amountInHYPE)
const [account] = await walletClient.getAddresses()
return await publicClient.estimateContractGas({
abi: STAKING_MANAGER_ABI,
account,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'stake',
value: amount,
})
}2. Withdrawals (unstaking)
export interface WithdrawalRequest {
bufferUsed: bigint
hypeAmount: bigint
kHYPEAmount: bigint
kHYPEFee: bigint
timestamp: bigint
}
/**
* Get HYPE amount for kHYPE tokens
*/
export const getHYPERate = async ({
amount,
publicClient,
}: {
amount: bigint
publicClient: PublicClient
}): Promise<bigint> => {
const result = await publicClient.readContract({
abi: STAKING_ACCOUNTANT_ABI,
address: CONTRACTS.STAKING_ACCOUNTANT,
args: [amount],
functionName: 'kHYPEToHYPE',
})
return result
}
/**
* Calculate withdrawal fee
*/
export const calculateWithdrawalFee = async ({
amount,
publicClient,
}: {
amount: bigint
publicClient: PublicClient
}): Promise<bigint> => {
const feeRate = await publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'unstakeFeeRate',
})
return (amount * feeRate) / 10000n // feeRate is in basis points
}
/**
* Get user's kHYPE balance
*/
export const getKHYPEBalance = async ({
publicClient,
userAddress,
}: {
publicClient: PublicClient
userAddress: Address
}): Promise<bigint> =>
await publicClient.readContract({
abi: KHYPE_ABI,
address: CONTRACTS.KHYPE_TOKEN,
args: [userAddress],
functionName: 'balanceOf',
})
/**
* Get next withdrawal ID for user
*/
export const getNextWithdrawalId = async ({
publicClient,
userAddress,
}: {
publicClient: PublicClient
userAddress: Address
}): Promise<bigint> =>
await publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
args: [userAddress],
functionName: 'nextWithdrawalId',
})
/**
* Get withdrawal request details
*/
export const getWithdrawalRequest = async ({
publicClient,
userAddress,
withdrawalId,
}: {
publicClient: PublicClient
userAddress: Address
withdrawalId: bigint
}): Promise<WithdrawalRequest> => {
const result = await publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
args: [userAddress, withdrawalId],
functionName: 'withdrawalRequests',
})
return {
bufferUsed: result[3],
hypeAmount: result[0],
kHYPEAmount: result[1],
kHYPEFee: result[2],
timestamp: result[4],
}
}
/**
* Check if withdrawal is ready to confirm
*/
export const isWithdrawalReady = async ({
publicClient,
userAddress,
withdrawalId,
}: {
publicClient: PublicClient
userAddress: Address
withdrawalId: bigint
}): Promise<{ ready: boolean; timeRemaining: number }> => {
try {
const [request, withdrawalDelay] = await Promise.all([
getWithdrawalRequest(publicClient, userAddress, withdrawalId),
publicClient.readContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
functionName: 'withdrawalDelay',
}),
])
if (request.timestamp === 0n) {
return { ready: false, timeRemaining: 0 }
}
const currentTime = BigInt(Math.floor(Date.now() / 1000))
const confirmTime = request.timestamp + withdrawalDelay
if (currentTime >= confirmTime) {
return { ready: true, timeRemaining: 0 }
} else {
return {
ready: false,
timeRemaining: Number(confirmTime - currentTime),
}
}
} catch {
return { ready: false, timeRemaining: 0 }
}
}
/**
* Queue a withdrawal request
*/
export const queueWithdrawal = async ({
kHYPEAmountStr,
publicClient,
walletClient,
}: {
kHYPEAmountStr: string
publicClient: PublicClient
walletClient: WalletClient
}): Promise<{
expectedHYPE: bigint
fee: bigint
hash: string
withdrawalId: bigint
}> => {
const [userAddress] = await walletClient.getAddresses()
const kHYPEAmount = parseEther(kHYPEAmountStr)
// Validate balance
const balance = await getKHYPEBalance({ publicClient, userAddress })
if (balance < kHYPEAmount) {
throw new Error('Insufficient kHYPE balance')
}
// Get expected values
const [expectedHYPE, fee, withdrawalId] = await Promise.all([
getHYPERate({ amount: kHYPEAmount, publicClient }),
calculateWithdrawalFee({ amount: kHYPEAmount, publicClient }),
getNextWithdrawalId({ publicClient, userAddress }),
])
// Execute withdrawal queue transaction
const hash = await walletClient.writeContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
args: [kHYPEAmount],
functionName: 'queueWithdrawal',
})
return { expectedHYPE, fee, hash, withdrawalId }
}
/**
* Confirm a withdrawal request
*/
export const confirmWithdrawal = async (
publicClient: PublicClient,
walletClient: WalletClient,
withdrawalId: bigint,
): Promise<string> => {
const [userAddress] = await walletClient.getAddresses()
// Check if withdrawal is ready
const { ready, timeRemaining } = await isWithdrawalReady({
publicClient,
userAddress,
withdrawalId,
})
if (!ready) {
throw new Error(
`Withdrawal not ready. Time remaining: ${timeRemaining} seconds`,
)
}
// Execute confirmation transaction
const hash = await walletClient.writeContract({
abi: STAKING_MANAGER_ABI,
address: CONTRACTS.STAKING_MANAGER,
args: [withdrawalId],
functionName: 'confirmWithdrawal',
})
return hash
}
/**
* Get all pending withdrawals for user
*/
export const getPendingWithdrawals = async (
publicClient: PublicClient,
userAddress: Address,
): Promise<
Array<{
id: bigint
ready: boolean
request: WithdrawalRequest
timeRemaining: number
}>
> => {
const nextId = await getNextWithdrawalId({ publicClient, userAddress })
const pendingWithdrawals = []
// Check the last 10 withdrawal IDs (adjust as needed)
const startId = nextId > 10n ? nextId - 10n : 0n
for (let id = startId; id < nextId; id += 1) {
try {
const request = await getWithdrawalRequest({
publicClient,
userAddress,
withdrawalId: id,
})
if (request.timestamp > 0n) {
const { ready, timeRemaining } = await isWithdrawalReady({
publicClient,
userAddress,
withdrawalId: id,
})
pendingWithdrawals.push({ id, ready, request, timeRemaining })
}
} catch {
// Skip invalid withdrawal IDs
continue
}
}
return pendingWithdrawals
}3. Complete integration example
import { createWalletClient, custom } from 'viem'
import { hyperliquid } from 'viem/chains'
/**
* Get comprehensive user dashboard data
*/
export const getUserDashboard = async ({
publicClient,
userAddress,
}: {
publicClient: PublicClient
userAddress: Address
}) => {
const [kHYPEBalance, protocolInfo, pendingWithdrawals] = await Promise.all([
getKHYPEBalance({ publicClient, userAddress }),
getProtocolInfo({ publicClient }),
getPendingWithdrawals({ publicClient, userAddress }),
])
// Calculate HYPE equivalent of kHYPE balance
const hypeEquivalent =
kHYPEBalance > 0n
? await getHYPERate({ amount: kHYPEBalance, publicClient })
: 0n
return {
balances: {
hypeEquivalent: formatEther(hypeEquivalent),
kHYPE: formatEther(kHYPEBalance),
},
protocol: {
maxStake: formatEther(protocolInfo.maxStake),
minStake: formatEther(protocolInfo.minStake),
totalStaked: formatEther(protocolInfo.totalStaked),
unstakeFeePercent: Number(protocolInfo.unstakeFeeRate) / 100,
withdrawalDelayHours: Number(protocolInfo.withdrawalDelay) / 3600,
},
withdrawals: pendingWithdrawals.map(
({ id, ready, request, timeRemaining }) => ({
fee: formatEther(request.kHYPEFee),
hypeAmount: formatEther(request.hypeAmount),
id: id.toString(),
kHYPEAmount: formatEther(request.kHYPEAmount),
ready: ready,
timeRemainingHours: timeRemaining / 3600,
}),
),
}
}
/**
* Stake with transaction monitoring
*/
export const stakeWithMonitoring = async ({
amountInHYPE,
publicClient,
walletClient,
}: {
amountInHYPE: string
publicClient: PublicClient
walletClient: WalletClient
}) => {
try {
// Estimate gas first
const gasEstimate = await estimateStakeGas({
amountInHYPE,
publicClient,
walletClient,
})
console.log(`Estimated gas: ${gasEstimate}`)
// Execute stake
const { expectedKHYPE, hash } = await stakeHYPE({
amountInHYPE,
publicClient,
walletClient,
})
console.log(`Transaction submitted: ${hash}`)
console.log(`Expected kHYPE: ${formatEther(expectedKHYPE)}`)
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log(`Transaction confirmed in block: ${receipt.blockNumber}`)
return { expectedKHYPE, hash, receipt }
} catch (error) {
console.error('Stake failed:', error)
throw error
}
}
/**
* Complete withdrawal flow with monitoring
*/
export const withdrawWithMonitoring = async ({
kHYPEAmount,
publicClient,
walletClient,
}: {
kHYPEAmount: string
publicClient: PublicClient
walletClient: WalletClient
}) => {
try {
// Queue withdrawal
const queueResult = await queueWithdrawal({
kHYPEAmountStr: kHYPEAmount,
publicClient,
walletClient,
})
console.log(`Withdrawal queued: ${queueResult.hash}`)
console.log(`Withdrawal ID: ${queueResult.withdrawalId}`)
console.log(`Expected HYPE: ${formatEther(queueResult.expectedHYPE)}`)
console.log(`Fee: ${formatEther(queueResult.fee)}`)
// Wait for queue confirmation
await publicClient.waitForTransactionReceipt({ hash: queueResult.hash })
return queueResult
} catch (error) {
console.error('Withdrawal queue failed:', error)
throw error
}
}
/**
* Monitor and auto-confirm ready withdrawals
*/
export const autoConfirmReadyWithdrawals = async (
publicClient: PublicClient,
walletClient: WalletClient,
userAddress: Address,
) => {
const pendingWithdrawals = await getPendingWithdrawals({
publicClient,
userAddress,
})
const readyWithdrawals = pendingWithdrawals.filter((w) => w.ready)
const confirmPromises = readyWithdrawals.map(async (withdrawal) => {
try {
const hash = await confirmWithdrawal({
publicClient,
walletClient,
withdrawalId: withdrawal.id,
})
console.log(`Confirmed withdrawal ${withdrawal.id}: ${hash}`)
return { hash, id: withdrawal.id, success: true }
} catch (error) {
console.error(`Failed to confirm withdrawal ${withdrawal.id}:`, error)
return { error, id: withdrawal.id, success: false }
}
})
return Promise.all(confirmPromises)
}
// Usage example
export const initializeKinetiq = async () => {
// Setup wallet client (assuming window.ethereum is available)
const walletClient = createWalletClient({
chain: hyperliquid,
transport: custom(window.ethereum),
})
// Get user address (assuming wallet is connected)
const accounts = await window.ethereum.request({ method: 'eth_accounts' })
const userAddress = accounts[0] as Address
// Get dashboard data
const dashboard = await getUserDashboard({ publicClient, userAddress })
console.log('User Dashboard:', dashboard)
return { dashboard, publicClient, userAddress, walletClient }
}Error handling and best practices
/**
* Handle contract errors with user-friendly messages
*/
export const handleContractError = ({ error }: { error: any }): string => {
if (error.message?.includes('insufficient funds')) {
return 'Insufficient HYPE balance for transaction'
}
if (error.message?.includes('Below minimum stake')) {
return 'Stake amount is below the minimum required'
}
if (error.message?.includes('Above maximum stake')) {
return 'Stake amount exceeds the maximum allowed'
}
if (error.message?.includes('Would exceed staking limit')) {
return 'This stake would exceed the protocol limit'
}
if (error.message?.includes('Withdrawal delay not met')) {
return 'Withdrawal is still in the delay period'
}
return error.message || 'Transaction failed'
}
/**
* Retry operation with exponential backoff
*/
export const retryWithBackoff = async <T>({
baseDelay = 1000,
maxRetries = 3,
operation,
}: {
baseDelay: number
maxRetries: number
operation: () => Promise<T>
}): Promise<T> => {
for (let tryCount = 0; tryCount < maxRetries; tryCount += 1) {
try {
return await operation()
} catch (error) {
if (tryCount === maxRetries - 1) {
throw error
}
const delay = baseDelay * Math.pow(2, tryCount)
await new Promise((resolve) => setTimeout(resolve, delay))
}
}
throw new Error('Max retries exceeded')
}
// Usage with error handling
export const safeStake = async ({
amount,
publicClient,
walletClient,
}: {
amount: string
publicClient: PublicClient
walletClient: WalletClient
}) => {
try {
const result = await retryWithBackoff({
operation: () =>
stakeWithMonitoring({
amountInHYPE: amount,
publicClient,
walletClient,
}),
})
return { result, success: true }
} catch (error) {
const message = handleContractError({ error })
return { error: message, success: false }
}
}On this page
Integration guideContract addresses (Hyperliquid Mainnet)Interface imports1. Depositing (staking) HYPE tokensBasic deposit exampleAdvanced deposit with validation2. Withdrawals (unstaking)Basic withdrawal flowAdvanced withdrawal with batching3. Complete integration exampleError handling and best practicesCommon errors and solutionsIntegration best practicesGas optimization tipsTypeScript integration with viemSetup and dependenciesContract configuration1. Depositing (staking) HYPE2. Withdrawals (unstaking)3. Complete integration exampleError handling and best practices