1.3.31 • Published 1 month ago

@shogun-sdk/one-shot v1.3.31

Weekly downloads
-
License
ISC
Repository
github
Last release
1 month ago

@shogun-sdk/one-shot

React hooks and components for the Shogun SDK. This package provides React components and hooks for building cross-chain swap interfaces with support for both EVM and Solana chains.

Table of Contents

Features

  • Cross-chain token swaps between EVM and Solana chains
  • Real-time token balance tracking
  • Automatic slippage calculation
  • Fee validation and management
  • Support for affiliate fees
  • Dynamic quote fetching
  • TypeScript support
  • React Query integration for efficient data fetching

Installation

npm install @shogun-sdk/one-shot
# or
yarn add @shogun-sdk/one-shot

Getting Started

Basic Setup

'use client';
import { useSwap } from 'your-store-library';
import { ShogunBalancesProvider, ShogunQuoteProvider, Token } from '@shogun-sdk/money-legos';
import { useMemo } from 'react';

export const ShogunProvider = ({ children }: { children: React.ReactNode }) => {
  // Get swap state from your store
  const {
    inputAmount,
    tokenIn,
    tokenOut,
    recipientAddress,
    slippage,
    setLatestSuggestedAutoSlippageValue,
    setInputAmount,
    dynamicSlippage // true/false, only for Solana transactions
  } = useSwap();

  // Your wallet addresses
  const yourAddresses = {
    evmAddress: 'your-evm-address', // starts from 0x...
    solAddress: 'your-solana-address'
  };

  // Configure affiliate wallets
  const affiliateWallets = useMemo(() => ({
    solana: process.env.SOLANA_AFFILIATE_WALLET!,
    evm: process.env.EVM_AFFILIATE_WALLET!
  }), []);

  // Configure API settings
  const api = useMemo(() => ({
    key: process.env.SHOGUN_API_KEY!,
    url: process.env.SHOGUN_API_URL!
  }), []);

  return (
    <ShogunBalancesProvider apiKey={process.env.CODEX_API_KEY!}>
      <ShogunQuoteProvider
        swap={{
          tokenIn: tokenIn as Token,
          tokenOut: tokenOut as Token,
          setLatestSuggestedAutoSlippageValue,
          inputAmount,
          setInputAmount,
          recipientAddress,
          slippage,
          dynamicSlippage,
        }}
        system={{
          api,
          systemFeePercent: 0.01,
          userEVMAddress: yourAddresses.evmAddress,
          userSolanaAddress: yourAddresses.solAddress,
          affiliateWallets,
        }}
      >
        {children}
      </ShogunQuoteProvider>
    </ShogunBalancesProvider>
  );
};

Core Concepts

Providers

The SDK provides two main providers:

  1. ShogunBalancesProvider: Manages token balances across chains
  2. ShogunQuoteProvider: Handles swap quotes and related operations

Hooks

The SDK provides several hooks for managing state and operations:

  1. useShogunQuote: Access quote information and operations
  2. useShogunBalances: Access balance information
  3. useTokenBalances: Get token balances for specific addresses

API Reference

Types

interface Token {
  address: string;  // Token contract address
  decimals: number; // Token decimals (e.g., 18 for ETH)
  chainId: number;  // Chain ID (e.g., 1 for Ethereum)
  symbol?: string;  // Token symbol (optional)
}

interface SwapConfig {
  tokenIn: Token;   // Source token
  tokenOut: Token;  // Destination token
  setLatestSuggestedAutoSlippageValue: (value: number) => void;
  inputAmount: string;
  setInputAmount: (amount: string) => void;
  recipientAddress: string;
  slippage: number;
  dynamicSlippage?: boolean; // Optional: Enable dynamic slippage for Solana
}

interface SystemConfig {
  api: {
    key: string;
    url: string;
  };
  systemFeePercent: number;
  userEVMAddress: string;
  userSolanaAddress: string;
  affiliateWallets: {
    solana: string;
    evm: string;
  };
  notifyAboutError?: (error: Error) => void; // Optional: Error notification callback
}

interface QuoteContextValue {
  quotes: QuoteTypes | undefined;
  errors: {
    feeValidationError: string | null;
    balanceError: string | null;
  };
  quoteRefetch: () => any;
  fees: ICollectedFees | undefined;
  isMaxBtnClicked: boolean;
  handleMaxBalanceInput: (quote: QuoteTypes | undefined, fees: ICollectedFees | undefined, maxBtnClicked: boolean) => void;
  setIsMaxBtnClicked: (isMaxBtnClicked: boolean) => void;
  inputValue: string;
  setInputValue: (inputValue: string) => void;
  needRecalculateMaxValue: boolean;
  isLoading: boolean;
  isRefetching: boolean;
  userInputAddress: string;
  userOutputAddress: string;
}

Examples

Basic Usage

import { useShogunQuote } from '@shogun-sdk/one-shot';

function SwapComponent() {
    const { quote, isLoading, error } = useShogunQuote();

    if (isLoading) {
        return <div>Loading quote...</div>;
    }

    if (error) {
        return <div>Error: {error.message}</div>;
    }

    return (
        <div>
            <h2>Swap Details</h2>
            <pre>{JSON.stringify(quote, null, 2)}</pre>
        </div>
    );
}

Wagmi v0 Integration

Basic Wallet Connection

import { useAccount, useConnect, useDisconnect } from 'wagmi';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { useShogunQuote } from '@shogun-sdk/one-shot';

function WalletConnect() {
  const { address, isConnected } = useAccount();
  const { connect } = useConnect({
    connector: new InjectedConnector(),
  });
  const { disconnect } = useDisconnect();

  return (
    <div>
      {isConnected ? (
        <div>
          <p>Connected to {address}</p>
          <button onClick={() => disconnect()}>Disconnect</button>
        </div>
      ) : (
        <button onClick={() => connect()}>Connect Wallet</button>
      )}
    </div>
  );
}

Complete Swap Interface

import { useAccount, useConnect, useDisconnect, useNetwork, useSwitchNetwork } from 'wagmi';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { useShogunQuote, useShogunBalances } from '@shogun-sdk/one-shot';
import { useState, useEffect } from 'react';

function SwapInterface() {
  const { address, isConnected } = useAccount();
  const { connect } = useConnect({
    connector: new InjectedConnector(),
  });
  const { disconnect } = useDisconnect();
  const { chain } = useNetwork();
  const { switchNetwork } = useSwitchNetwork();
  
  const [amount, setAmount] = useState('1000000000000000000'); // 1 ETH
  const [selectedTokenIn, setSelectedTokenIn] = useState({
    address: '0xEthereum...',
    decimals: 18,
    chainId: 1
  });
  const [selectedTokenOut, setSelectedTokenOut] = useState({
    address: '0xArbitrum...',
    decimals: 18,
    chainId: 42161
  });

  // Get quote with connected wallet
  const { 
    quote, 
    isLoading: quoteLoading, 
    error: quoteError,
    fees,
    handleMaxBalanceInput
  } = useShogunQuote();

  // Get balances for connected wallet
  const { 
    evmBalances, 
    solanaBalances, 
    isLoadingEVM, 
    isLoadingSolana 
  } = useTokenBalances({
    userEVMAddress: address || '',
    userSolanaAddress: '', // Add your Solana address if needed
    tokenIn: selectedTokenIn,
    tokenOut: selectedTokenOut
  });

  // Handle max button click
  const handleMaxClick = () => {
    handleMaxBalanceInput(quote, fees, true);
  };

  if (!isConnected) {
    return (
      <div className="wallet-connect">
        <h2>Connect Your Wallet</h2>
        <button onClick={() => connect()}>Connect Wallet</button>
      </div>
    );
  }

  return (
    <div className="swap-interface">
      <div className="wallet-info">
        <p>Connected: {address}</p>
        <p>Network: {chain?.name}</p>
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>

      <div className="token-selection">
        <div className="input-token">
          <h3>From</h3>
          <select 
            value={selectedTokenIn.address} 
            onChange={(e) => setSelectedTokenIn({
              ...selectedTokenIn,
              address: e.target.value
            })}
          >
            <option value="0xEthereum...">ETH</option>
            <option value="0xUSDC...">USDC</option>
          </select>
          <div className="amount-input">
            <input
              type="number"
              value={amount}
              onChange={(e) => setAmount(e.target.value)}
              min="0"
              step="0.000000000000000001"
            />
            <button onClick={handleMaxClick}>MAX</button>
          </div>
          <p>Balance: {evmBalances?.[selectedTokenIn.address] || '0'}</p>
        </div>

        <div className="output-token">
          <h3>To</h3>
          <select 
            value={selectedTokenOut.address}
            onChange={(e) => setSelectedTokenOut({
              ...selectedTokenOut,
              address: e.target.value
            })}
          >
            <option value="0xArbitrum...">USDC on Arbitrum</option>
            <option value="0xOptimism...">USDC on Optimism</option>
          </select>
          <p>Estimated Output: {quote?.outputAmount || '0'}</p>
          <p>Balance: {evmBalances?.[selectedTokenOut.address] || '0'}</p>
        </div>
      </div>

      {quoteLoading ? (
        <div>Loading quote...</div>
      ) : quoteError ? (
        <div>Error: {quoteError.message}</div>
      ) : (
        <div className="swap-details">
          <button 
            onClick={() => {/* Handle swap execution */}}
            disabled={!quote || quoteLoading}
          >
            Swap
          </button>
        </div>
      )}
    </div>
  );
}

Available Hooks

useShogunQuote from ShogunQuoteContext

const { 
  quote, 
  isLoading, 
  error,
  fees,
  isMaxBtnClicked,
  handleMaxBalanceInput,
  setIsMaxBtnClicked,
  inputValue,
  setInputValue,
  needRecalculateMaxValue,
  userInputAddress,
  userOutputAddress
} = useShogunQuote();

useShogunBalances from ShogunBalancesProvider

const client = useShogunBalances();

useTokenBalances

const {
  evmBalances,
  solanaBalances,
  isLoadingEVM,
  isLoadingSolana
} = useTokenBalances({
  userEVMAddress: string,
  userSolanaAddress: string,
  tokenIn?: Token,
  tokenOut?: Token
});

Components

ShogunBalancesProvider

Provider for managing token balances across chains.

<ShogunBalancesProvider apiKey="your-api-key">
  <YourApp />
</ShogunBalancesProvider>

Props:

  • apiKey: string - Your CODEX API key
  • children: React.ReactNode

ShogunQuoteProvider

Provider for managing swap quotes.

<ShogunQuoteProvider
  swap={{
    tokenIn: { address: '0x...', decimals: 18, chainId: 1 },
    tokenOut: { address: '0x...', decimals: 18, chainId: 8453 },
    setLatestSuggestedAutoSlippageValue: (value) => {},
    inputAmount: '1000000000000000000',
    setInputAmount: (amount) => {},
    recipientAddress: '0x...',
    slippage: 0.5,
    dynamicSlippage: false,
  }}
  system={{
    api: { key: '...', url: '...' },
    systemFeePercent: 0.01,
    userEVMAddress: '0x...',
    userSolanaAddress: '...',
    affiliateWallets: { solana: '...', evm: '0x...' },
  }}
>
  <YourApp />
</ShogunQuoteProvider>

Props:

  • swap: SwapConfig
  • system: SystemConfig
  • children: React.ReactNode

Complete Example

Here's a complete example of a swap interface:

import { useShogunQuote, useShogunBalances } from '@shogun-sdk/one-shot';
import { useState } from 'react';

function SwapInterface() {
  const [amount, setAmount] = useState('1000000000000000000'); // 1 ETH

  // Get quote
  const { quote, isLoading: quoteLoading, error: quoteError } = useShogunQuote();

  // Get balances
  const { balances, isLoading: balancesLoading, error: balancesError } = useShogunBalances();

  if (quoteLoading || balancesLoading) {
    return <div>Loading...</div>;
  }

  if (quoteError) {
    return <div>Error fetching quote: {quoteError.message}</div>;
  }

  if (balancesError) {
    return <div>Error fetching balances: {balancesError.message}</div>;
  }

  return (
    <div className="swap-interface">
      <h2>Cross-chain Swap</h2>
      
      {/* Amount Input */}
      <div className="input-group">
        <label>Amount (ETH)</label>
        <input
          type="number"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          min="0"
          step="0.000000000000000001"
        />
      </div>

      {/* Swap Button */}
      <button 
        onClick={() => {/* Handle swap */}}
        disabled={!quote || quoteLoading}
      >
        {quoteLoading ? 'Loading...' : 'Swap'}
      </button>
    </div>
  );
}

Types

interface Token {
  address: string;  // Token contract address
  decimals: number; // Token decimals (e.g., 18 for ETH)
  chainId: number;  // Chain ID (e.g., 1 for Ethereum)
  symbol?: string;  // Token symbol (optional)
}

interface SwapConfig {
  tokenIn: Token;   // Source token
  tokenOut: Token;  // Destination token
  setLatestSuggestedAutoSlippageValue: (value: number) => void;
  inputAmount: string;
  setInputAmount: (amount: string) => void;
  recipientAddress: string;
  slippage: number;
  dynamicSlippage?: boolean; // Optional: Enable dynamic slippage for Solana
}

interface SystemConfig {
  api: {
    key: string;
    url: string;
  };
  systemFeePercent: number;
  userEVMAddress: string;
  userSolanaAddress: string;
  affiliateWallets: {
    solana: string;
    evm: string;
  };
  notifyAboutError?: (error: Error) => void; // Optional: Error notification callback
}

interface QuoteContextValue {
  quotes: QuoteTypes | undefined;
  errors: {
    feeValidationError: string | null;
    balanceError: string | null;
  };
  quoteRefetch: () => any;
  fees: ICollectedFees | undefined;
  isMaxBtnClicked: boolean;
  handleMaxBalanceInput: (quote: QuoteTypes | undefined, fees: ICollectedFees | undefined, maxBtnClicked: boolean) => void;
  setIsMaxBtnClicked: (isMaxBtnClicked: boolean) => void;
  inputValue: string;
  setInputValue: (inputValue: string) => void;
  needRecalculateMaxValue: boolean;
  isLoading: boolean;
  isRefetching: boolean;
  userInputAddress: string;
  userOutputAddress: string;
}

Best Practices

  1. Provider Setup

    • Always wrap your app with both providers in the correct order
    • Use environment variables for sensitive data
    • Memoize configuration objects to prevent unnecessary re-renders
  2. Error Handling

    • Implement proper error boundaries
    • Use the provided error states from hooks
    • Implement the notifyAboutError callback for error tracking
  3. Performance Optimization

    • Use useMemo for expensive calculations
    • Implement proper loading states
    • Use the provided refetch functions when needed
  4. Type Safety

    • Use TypeScript for better type checking
    • Properly type your token configurations
    • Use the provided interfaces for type safety
  5. State Management

    • Keep provider state minimal
    • Use local state for UI-specific data
    • Implement proper cleanup in useEffect hooks

Troubleshooting

Common issues and their solutions:

  1. Provider Issues

    • Ensure all required providers are present
    • Check provider order (BalancesProvider should wrap QuoteProvider)
    • Verify all required props are provided
    • Check environment variables are properly set
  2. Hook Usage Issues

    • Make sure hooks are used within provider context
    • Check hook parameters are correct
    • Verify all required dependencies are installed
    • Check for proper error handling
  3. Type Errors

    • Ensure proper TypeScript types are imported
    • Check interface implementations
    • Verify type definitions match your data
    • Use proper type assertions when needed
  4. Balance Issues

    • Verify wallet addresses are correct
    • Check chain IDs match your tokens
    • Ensure proper RPC endpoints are available
    • Check token decimals are correct
  5. Quote Issues

    • Verify token addresses are correct
    • Check slippage settings
    • Ensure proper fee configuration
    • Verify affiliate wallet addresses

Support

If you encounter any issues or need help:

1.3.31

1 month ago

1.3.3

2 months ago

1.3.2

2 months ago

1.3.1

2 months ago

1.3.0

2 months ago

1.2.453

2 months ago

1.2.452

2 months ago

1.2.451

2 months ago

1.2.45

2 months ago

1.2.44

2 months ago

1.2.42-beta

2 months ago

1.2.41

2 months ago

1.2.40

2 months ago

1.2.36-avax

2 months ago

1.2.35-avax

2 months ago

1.2.34-avax

2 months ago

1.2.33-avax

2 months ago

1.2.32-avax

3 months ago

1.2.32

3 months ago

1.2.31

3 months ago

1.2.30

3 months ago

1.2.29

3 months ago

1.2.28-avax-rpc-2

3 months ago

1.2.28-avax-rpc-1

3 months ago

1.2.27-avax-rpc-3

3 months ago

1.2.27-rpc-3

3 months ago

1.2.27-rpc-2

3 months ago

1.2.27-rpc

3 months ago

1.2.26

3 months ago

1.2.26-avax

3 months ago

1.2.25

3 months ago

1.2.24

3 months ago

1.2.23

3 months ago

1.2.22

3 months ago

1.2.21

3 months ago

1.2.2

3 months ago

1.2.1

3 months ago

1.2.0

3 months ago