Skip to main content

Kraken Integration

The Dynamic SDK provides a seamless way to transfer cryptocurrency from users’ Kraken exchange accounts to external wallets. This enables your users to fund their wallets directly from their Kraken holdings without leaving your application.

Overview

The Kraken integration consists of three functions:
FunctionPurpose
getKrakenAccountsFetch account balances from Kraken
getKrakenWhitelistedAddressesGet addresses approved for withdrawals
createKrakenExchangeTransferTransfer crypto to an external wallet

Prerequisites

Before using the Kraken integration:
  1. Kraken Account Connection - Users must connect their Kraken account through Dynamic’s exchange connection flow
  2. Address Whitelisting - If enabled on the user’s Kraken account, destination addresses must be whitelisted in Kraken’s security settings
  3. Sufficient Balance - The Kraken account must have enough funds for the transfer

Quick Start

Here’s a minimal example showing the complete flow:
import {
  getKrakenAccounts,
  getKrakenWhitelistedAddresses,
  createKrakenExchangeTransfer,
} from '@dynamic-labs-sdk/client';

// 1. Get the user's Kraken accounts and balances
const accounts = await getKrakenAccounts();
console.log('Available accounts:', accounts);

// 2. Check whitelisted addresses
const { destinations, enforcesAddressWhitelist } =
  await getKrakenWhitelistedAddresses();

if (enforcesAddressWhitelist) {
  console.log('Whitelisted addresses:', destinations);
}

// 3. Create a transfer
const transfer = await createKrakenExchangeTransfer({
  accountId: accounts[0].id,
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f7ABCD',
  amount: 0.1,
  currency: 'ETH',
});

console.log('Transfer status:', transfer.status);

Understanding the Data

Account Structure

When you call getKrakenAccounts(), you receive an array of accounts, each containing multiple currency balances:
const accounts = await getKrakenAccounts();

// Example account structure:
// {
//   id: 'acc_abc123',
//   exchange: 'kraken',
//   name: 'Main Account',
//   type: 'spot',
//   balances: [
//     { currency: 'ETH', balance: 2.5, availableBalance: 2.5 },
//     { currency: 'BTC', balance: 0.15, availableBalance: 0.15 },
//     { currency: 'USDC', balance: 1500.00, availableBalance: 1500.00 }
//   ]
// }

Whitelisted Addresses

Kraken users can enable address whitelisting as a security measure. When enabled, withdrawals can only go to pre-approved addresses:
const { destinations, enforcesAddressWhitelist } =
  await getKrakenWhitelistedAddresses();

if (enforcesAddressWhitelist) {
  // User must select from whitelisted addresses
  console.log('Available destinations:', destinations);
  // destinations: [
  //   { address: '0x742d35...', tokens: ['ETH', 'USDC'] },
  //   { address: '0x8ba1f1...', tokens: ['ETH'] }
  // ]
} else {
  // User can enter any address
  console.log('Any address can be used');
}

Complete React Example

Here’s a production-ready React component for Kraken transfers:
import { useState, useEffect } from 'react';
import {
  getKrakenAccounts,
  getKrakenWhitelistedAddresses,
  createKrakenExchangeTransfer,
} from '@dynamic-labs-sdk/client';

const KrakenTransfer = () => {
  // State
  const [accounts, setAccounts] = useState([]);
  const [whitelistData, setWhitelistData] = useState(null);
  const [selectedAccountId, setSelectedAccountId] = useState('');
  const [currency, setCurrency] = useState('');
  const [amount, setAmount] = useState('');
  const [destination, setDestination] = useState('');
  const [loading, setLoading] = useState(true);
  const [submitting, setSubmitting] = useState(false);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  // Load accounts and whitelisted addresses on mount
  useEffect(() => {
    const loadData = async () => {
      try {
        const [accountsData, whitelistResponse] = await Promise.all([
          getKrakenAccounts(),
          getKrakenWhitelistedAddresses(),
        ]);
        setAccounts(accountsData);
        setWhitelistData(whitelistResponse);
      } catch (err) {
        setError('Failed to load Kraken data. Is your account connected?');
      } finally {
        setLoading(false);
      }
    };
    loadData();
  }, []);

  // Get selected account
  const selectedAccount = accounts.find(acc => acc.id === selectedAccountId);

  // Get selected balance for the currency
  const selectedBalance = selectedAccount?.balances.find(
    b => b.currency === currency
  );

  // Filter whitelisted addresses by currency
  const availableDestinations = whitelistData?.destinations.filter(
    dest => dest.tokens?.includes(currency)
  ) || [];

  // Reset currency when account changes
  useEffect(() => {
    if (selectedAccount?.balances.length > 0) {
      setCurrency(selectedAccount.balances[0].currency);
    }
  }, [selectedAccountId]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setSubmitting(true);
    setError(null);

    try {
      const transfer = await createKrakenExchangeTransfer({
        accountId: selectedAccountId,
        to: destination,
        amount: parseFloat(amount),
        currency,
      });
      setResult(transfer);
    } catch (err) {
      setError(err.message || 'Transfer failed');
    } finally {
      setSubmitting(false);
    }
  };

  if (loading) return <div>Loading Kraken data...</div>;
  if (error && !accounts.length) return <div>Error: {error}</div>;
  if (accounts.length === 0) {
    return <div>No Kraken accounts found. Please connect your Kraken account.</div>;
  }

  if (result) {
    return (
      <div>
        <h3>Transfer Created</h3>
        <p>ID: {result.id}</p>
        <p>Status: {result.status}</p>
        <p>Amount: {result.amount} {result.currency}</p>
        <button onClick={() => setResult(null)}>Make Another Transfer</button>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <div style={{ color: 'red' }}>{error}</div>}

      {/* Account Selection */}
      <div>
        <label>Kraken Account</label>
        <select
          value={selectedAccountId}
          onChange={e => setSelectedAccountId(e.target.value)}
          required
        >
          <option value="">Select an account</option>
          {accounts.map(account => (
            <option key={account.id} value={account.id}>
              {account.name || `Account ${account.id.slice(0, 8)}`}
              {' '}({account.balances.length} currencies)
            </option>
          ))}
        </select>
      </div>

      {selectedAccount && (
        <>
          {/* Currency Selection */}
          <div>
            <label>Currency</label>
            <select
              value={currency}
              onChange={e => setCurrency(e.target.value)}
              required
            >
              {selectedAccount.balances.map(balance => (
                <option key={balance.currency} value={balance.currency}>
                  {balance.currency} (Balance: {balance.balance})
                </option>
              ))}
            </select>
          </div>

          {/* Amount Input */}
          <div>
            <label>Amount</label>
            <input
              type="number"
              step="any"
              min="0"
              value={amount}
              onChange={e => setAmount(e.target.value)}
              placeholder="0.0"
              required
            />
            {selectedBalance && (
              <small>Available: {selectedBalance.availableBalance ?? selectedBalance.balance}</small>
            )}
          </div>

          {/* Destination Address */}
          <div>
            <label>Destination Address</label>
            {whitelistData?.enforcesAddressWhitelist && availableDestinations.length > 0 ? (
              <select
                value={destination}
                onChange={e => setDestination(e.target.value)}
                required
              >
                <option value="">Select a whitelisted address</option>
                {availableDestinations.map(dest => (
                  <option key={dest.address} value={dest.address}>
                    {dest.address}
                  </option>
                ))}
              </select>
            ) : (
              <input
                type="text"
                value={destination}
                onChange={e => setDestination(e.target.value)}
                placeholder="0x..."
                required
              />
            )}
            {whitelistData?.enforcesAddressWhitelist && availableDestinations.length === 0 && (
              <small style={{ color: 'orange' }}>
                No whitelisted addresses for {currency}. Add one in Kraken settings.
              </small>
            )}
          </div>
        </>
      )}

      <button type="submit" disabled={submitting || !selectedAccountId}>
        {submitting ? 'Processing...' : 'Transfer'}
      </button>
    </form>
  );
};

export default KrakenTransfer;

Filtering Accounts by Chain

You can filter accounts to show only balances for specific blockchains:
// Get only EVM-compatible assets (ETH, USDC on Ethereum, etc.)
const evmAccounts = await getKrakenAccounts({ chainName: 'EVM' });

// Get only Solana assets
const solanaAccounts = await getKrakenAccounts({ chainName: 'SOL' });

// Filter by specific network ID (e.g., Ethereum mainnet)
const ethereumMainnet = await getKrakenAccounts({
  chainName: 'EVM',
  networkId: 1
});

Transfer with Network Specification

When transferring tokens that exist on multiple networks (like USDC), specify the network:
const transfer = await createKrakenExchangeTransfer({
  accountId: 'acc_123',
  to: '0x742d35...',
  amount: 100,
  currency: 'USDC',
  networkObject: {
    chainName: 'EVM',
    networkId: '137', // Polygon
  },
  description: 'Transfer USDC to Polygon wallet',
});

Using Idempotency Keys

Prevent duplicate transfers by providing an idempotency key:
import { v4 as uuidv4 } from 'uuid';

const transferId = uuidv4();

const transfer = await createKrakenExchangeTransfer({
  accountId: 'acc_123',
  to: '0x742d35...',
  amount: 0.5,
  currency: 'ETH',
  id: transferId, // Idempotency key
});

// If this request is retried with the same `id`,
// it will return the existing transfer instead of creating a duplicate

MFA Support

If the user has MFA enabled on their Kraken account, you may need to pass an MFA code:
const transfer = await createKrakenExchangeTransfer({
  accountId: 'acc_123',
  to: '0x742d35...',
  amount: 1.0,
  currency: 'BTC',
  mfaCode: '123456', // User-provided MFA code
});

Error Handling

Handle common error scenarios:
try {
  const transfer = await createKrakenExchangeTransfer({
    accountId: selectedAccountId,
    to: destination,
    amount: parseFloat(amount),
    currency,
  });
  console.log('Transfer successful:', transfer.id);
} catch (error) {
  if (error.message.includes('insufficient')) {
    alert('Insufficient balance for this transfer');
  } else if (error.message.includes('whitelist')) {
    alert('This address is not whitelisted. Add it in your Kraken settings.');
  } else if (error.message.includes('mfa')) {
    alert('MFA verification required');
  } else {
    alert('Transfer failed: ' + error.message);
  }
}

Best Practices

  1. Always check balances first - Use getKrakenAccounts() to verify the user has sufficient funds before showing the transfer UI
  2. Respect whitelisting - If enforcesAddressWhitelist is true, only show whitelisted addresses as options
  3. Use idempotency keys - For production applications, always include an id parameter to prevent duplicate transfers from network retries
  4. Validate amounts client-side - Check that the transfer amount doesn’t exceed the available balance before submitting
  5. Show transfer status - Display the transfer status to users so they know their transfer is being processed