1.3.31 • Published 8 months ago

@shogun-sdk/one-shot v1.3.31

Weekly downloads
-
License
ISC
Repository
github
Last release
8 months 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

8 months ago

1.3.3

8 months ago

1.3.2

8 months ago

1.3.1

8 months ago

1.3.0

8 months ago

1.2.453

8 months ago

1.2.452

8 months ago

1.2.451

8 months ago

1.2.45

8 months ago

1.2.44

8 months ago

1.2.42-beta

9 months ago

1.2.41

9 months ago

1.2.40

9 months ago

1.2.36-avax

9 months ago

1.2.35-avax

9 months ago

1.2.34-avax

9 months ago

1.2.33-avax

9 months ago

1.2.32-avax

9 months ago

1.2.32

9 months ago

1.2.31

10 months ago

1.2.30

10 months ago

1.2.29

10 months ago

1.2.28-avax-rpc-2

10 months ago

1.2.28-avax-rpc-1

10 months ago

1.2.27-avax-rpc-3

10 months ago

1.2.27-rpc-3

10 months ago

1.2.27-rpc-2

10 months ago

1.2.27-rpc

10 months ago

1.2.26

10 months ago

1.2.26-avax

10 months ago

1.2.25

10 months ago

1.2.24

10 months ago

1.2.23

10 months ago

1.2.22

10 months ago

1.2.21

10 months ago

1.2.2

10 months ago

1.2.1

10 months ago

1.2.0

10 months ago