Valantis Documentation
Valantis Website
  • Welcome to Valantis
  • Sovereign Pool
    • The Modules
      • Pool Manager
      • Liquidity Module
      • Swap Fee Module
      • Verifier Module
      • Oracle Module
      • Sovereign Vault
        • Rebase token support
      • Gauge
    • Interacting with Pools
      • Swap Parameters
      • Swap Steps
      • Multi Token Support
      • Deposit Liquidity
      • Withdraw Liquidity
      • Flash Loans
  • Hybrid Order Type (HOT)
    • Understanding HOT: A Graphical Overview
    • HOT API
      • HTTP request parameters
      • HTTP request response
      • Reasoning for Request structure
      • Solver Integration
      • Market Maker integration
    • HOT Smart Contracts
      • Interfaces
      • State variables and roles
      • AMM
      • HOT Swap
      • AMM Swap
      • Hybrid Order Type (HOT) struct parameters
      • Alternating Nonce Bitmap
      • Deposits
      • Withdrawals
      • Deployment Assumptions
    • Liquidity Manager Docs
      • Reference Oracle
      • Deposit
      • Withdraw
      • Signature
    • Swap
      • Swap Context
      • AMM Swap
      • HOT Swap
      • HOT Quote Parameters
        • Bitmap Nonce Instructions
    • Solver Docs
      • Solver Request
      • Simple HOT Swap Example
      • Partial Fill HOT Swap Example
    • Risks and Trust Assumptions
      • Roles
        • Sovereign Pool Manager
        • HOT Manager
        • HOT Signer
        • Liquidity Provider
      • Threats
        • Deposit Sandwich
        • Malicious Price Bound
        • Malicious Signer
        • Mispriced HOT Quote
  • Validly
    • Understanding Validly
    • Swap
    • Deposits and Withdrawals
    • Deployments
  • Stake Exchange (stHYPE AMM)
    • Swap
      • Instant Withdrawals (LST -> Native Token)
      • New Stake (Native Token -> LST)
    • LP Positions
      • LP Withdrawals Instant
      • LP Withdrawal Queued
    • Ratio Fee
    • Lending of Reserves
    • Smart contracts
      • STEXAMM.sol
      • StHYPEWithdrawalModule.sol
      • StexRatioSwapFeeModule.sol
      • DepositWrapper.sol
      • AaveLendingModule.sol
    • Risks and Trust Assumptions
    • Integration examples
  • Deploy and Build
  • Resources
    • Audits
    • Links
    • Get HYPE on HyperEVM
Powered by GitBook
On this page

Was this helpful?

  1. Hybrid Order Type (HOT)
  2. Solver Docs

Partial Fill HOT Swap Example

PreviousSimple HOT Swap ExampleNextRisks and Trust Assumptions

Last updated 10 months ago

Was this helpful?

A simple Typescript example to make a HOT swap on the Arbitrum pool, also available here: Note:

  • Make sure that there are enough WETH funds in the account that is set as authorized_sender . Currently this value is 0.0001 WETH, and the authorized sender is automatically set as the public key of the account whos private key is provided as the environment variable.

  • Make sure that appropriate approvals are given to the sovereign pool address, before this request is sent. All deployment addresses can be found .

  • Currently the partial fill amount is set to the half of the original amount in. This can be changed by updating the amountInPartialFill and amountOutMinPartialFill variables.

main.ts
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum, gnosis, mainnet } from 'viem/chains';
import { Address, createWalletClient, encodeAbiParameters, http, parseAbiParameters, publicActions } from 'viem';
import { SignTypedDataReturnType, decodeAbiParameters } from 'viem';

import { fetchAmountOut } from './utils/price-api';

async function swapPartialFill() {
  const headers = {
    'Content-Type': 'application/json',
    'X-API-Key': `${process.env.API_KEY}`,
  };

  const account = privateKeyToAccount(`0x${process.env.PK}`);

  // 0.0001 * 1e18 ether
  const AMOUNT_IN = BigInt('100000000000000');
  // Fetch latest price from Binance API.
  // Liquidity Manager will attempt to match or improve it
  // Optionally: Set to 0 and it works as a standard RFQ API
  const AMOUNT_OUT = await fetchAmountOut(AMOUNT_IN);

  const requestParams = JSON.stringify({
    authorized_recipient: account.address, // address which receives token out
    authorized_sender: account.address, // should be same address which calls pool contract
    chain_id: 42161, // 1 for mainnet, 100 for arbitrum
    token_in: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', // weth on arbitrum
    token_out: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC on arbitrum
    expected_gas_price: '0', // 1 gwei gas price
    amount_in: AMOUNT_IN.toString(),
    amount_out_requested: AMOUNT_OUT.toString(),
    request_expiry: Math.ceil(Date.now() / 1000) + 30, // Expiry in 30 seconds
  });

  const requestOptions: RequestInit = {
    body: requestParams,
    method: 'POST',
    headers,
  };

  const response = await fetch('https://hot.valantis.xyz/solver/order', requestOptions);
  const data = await response.json();
  console.log(data);

  const quote = data as {
    pool_address: Address;
    signed_payload: SignTypedDataReturnType;
    volume_token_out: string;
    amount_out_min_payload_offset: number;
    amount_payload_offset: number;
    gas_price: number;
  };

  const walletClient = createWalletClient({
    name: 'Main',
    account,
    chain: arbitrum,
    transport: http(`${process.env.ARBITRUM_RPC}`),
  }).extend(publicActions);

  if (!quote.signed_payload) {
    console.log('Could not get signed payload');
    return;
  }

  // Human readable ABI params from SovereignPool::swap (pool_address)
  const swapAbiParams = parseAbiParameters(
    '(bool isSwapCallback, bool isZeroToOne, uint256 amountIn, uint256 amountOutMin, uint256 deadline, address recipient, address swapTokenOut, (bytes externalContext, bytes verifierContext, bytes swapCallbackContext, bytes swapFeeModuleContext))'
  );

  // Decode signed_payload
  const payloadSliced = `0x${quote.signed_payload.slice(10)}`;
  const decodedParams = decodeAbiParameters(swapAbiParams, payloadSliced as `0x{string}`)[0];

  // Recalculate amountIn and amountOutMin to execute a partially fillable HOT swap
  const amountInPartialFill = AMOUNT_IN / BigInt('2');
  const amountOutMinPartialFill = AMOUNT_OUT / BigInt('2');

  // Re-encode payload with amountInPartialFill and amountOutMinPartialFill
  const encodedParamsPartialFill = encodeAbiParameters(swapAbiParams, [
    [
      decodedParams[0],
      decodedParams[1],
      amountInPartialFill,
      amountOutMinPartialFill,
      decodedParams[4],
      decodedParams[5],
      decodedParams[6],
      decodedParams[7],
    ],
  ]);
  const payloadPartialFill = `0x${quote.signed_payload.slice(2, 10)}${encodedParamsPartialFill.slice(
    2
  )}` as `0x{string}`;

  console.log('payload partial fill: ', payloadPartialFill);

  const txHash = await walletClient.sendTransaction({
    account,
    chain: arbitrum,
    to: quote.pool_address,
    value: 0n,
    data: payloadPartialFill,
    gas: 1_000_000n,
  });

  console.log(txHash);
}

swapPartialFill();
main.ts
import { AddressLike, Wallet, ethers } from 'ethers';

import { fetchAmountOut } from './utils/price-api';

async function swapPartialFill() {
  const headers = {
    'Content-Type': 'application/json',
    'X-API-Key': `${process.env.API_KEY}`,
  };

  const arbitrum_provider = new ethers.JsonRpcProvider(`${process.env.ARBITRUM_RPC}`);

  const account = new Wallet(`0x${process.env.PK}`, arbitrum_provider);

  // 0.0001 * 1e18 ether
  const AMOUNT_IN = BigInt('100000000000000');
  // Fetch latest price from Binance API.
  // Liquidity Manager will attempt to match or improve it
  // Optionally: Set to 0 and it works as a standard RFQ API
  const AMOUNT_OUT = await fetchAmountOut(AMOUNT_IN);

  const requestParams = JSON.stringify({
    authorized_recipient: account.address, // address which receives token out
    authorized_sender: account.address, // should be same address which calls pool contract
    chain_id: 42161, // 1 for mainnet, 42161 for arbitrum
    token_in: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', // weth on arbitrum
    token_out: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC on arbitrum
    expected_gas_price: '0', // 1 gwei gas price
    amount_in: AMOUNT_IN.toString(), // 0.0001 * 1e18 ether
    amount_out_requested: AMOUNT_OUT.toString(), // 0.29 * 1e6 USDC
    request_expiry: Math.ceil(Date.now() / 1000) + 30, // Expiry in 30 seconds
  });

  const requestOptions = {
    body: requestParams,
    method: 'POST',
    headers,
  };

  const response = await fetch('https://hot.valantis.xyz/solver/order', requestOptions);
  const data = await response.json();

  console.log(data);

  const quote = data as {
    pool_address: AddressLike;
    signed_payload: string;
    volume_token_out: string;
    amount_out_min_payload_offset: number;
    amount_payload_offset: number;
    gas_price: number;
  };

  if (!quote.signed_payload) {
    console.log('Could not get signed payload');
    return;
  }

  // Human readable ABI params from SovereignPool::swap (pool_address)
  const abiCoder = ethers.AbiCoder.defaultAbiCoder();
  const swapAbiParams = [
    'tuple(bool isSwapCallback, bool isZeroToOne, uint256 amountIn, uint256 amountOutMin, uint256 deadline, address recipient, address swapTokenOut, tuple(bytes externalContext, bytes verifierContext, bytes swapCallbackContext, bytes swapFeeModuleContext) SwapContext) SovereignPoolSwapParams',
  ];

  // Decode signed_payload
  const decodedParams = abiCoder.decode(swapAbiParams, `0x${quote.signed_payload.slice(10)}`)[0];

  // Recalculate amountIn and amountOutMin to execute a partially fillable HOT swap
  const amountInPartialFill = AMOUNT_IN / BigInt('2');
  const amountOutMinPartialFill = AMOUNT_OUT / BigInt('2');

  // Re-encode payload with amountInPartialFill and amountOutMinPartialFill
  const encodedParamsPartialFill = abiCoder.encode(swapAbiParams, [
    [
      decodedParams[0],
      decodedParams[1],
      amountInPartialFill,
      amountOutMinPartialFill,
      decodedParams[4],
      decodedParams[5],
      decodedParams[6],
      decodedParams[7],
    ],
  ]);
  const payloadPartialFill = `0x${quote.signed_payload.slice(2, 10)}${encodedParamsPartialFill.slice(2)}`;

  const tx = await account.sendTransaction({
    to: quote.pool_address,
    data: payloadPartialFill,
    gasLimit: 1_000_000n,
  });

  const receipt = await tx.wait();

  console.log(receipt?.hash);
}

swapPartialFill();
https://github.com/ValantisLabs/valantis-hot/tree/main/scripts/hot-example
here