Skip to content
SwapKit is a powerful suite of tools for building blockchain applications.

Creating a Custom Wallet

This guide explains how to create a custom wallet adapter for SwapKit using the createWallet function.

A SwapKit wallet adapter consists of:

  1. A wallet factory created with createWallet
  2. Configuration for supported chains and derivation paths
  3. Connection methods for interacting with the wallet

Here’s a simple example of creating a custom wallet adapter:

import {
  enum ChainChain,
  const DerivationPath: Record<Chain, string>DerivationPath,
  const NetworkDerivationPath: Record<Chain, DerivationPathArray>NetworkDerivationPath,
  
function createWallet<ConnectParams extends any[], SupportedChains extends Chain[], const Name extends string, WalletType extends WalletOption>({ connect, name, supportedChains, walletType, }: {
    connect: (connectParams: {
        addChain: AddChainType;
        walletType: WalletType;
        supportedChains: SupportedChains;
    }) => (...params: ConnectParams) => Promise<boolean>;
    name: Name;
    supportedChains: SupportedChains;
    walletType?: WalletType | string;
}): { [key in Name]: {
    connectWallet: (connectParams: {
        addChain: AddChainType;
    }) => (...params: ConnectParams) => Promise<boolean>;
    supportedChains: SupportedChains;
}; }
createWallet
,
type
type ChainWallet<T extends Chain> = {
    chain: T;
    address: string;
    balance: AssetValue[];
    walletType: WalletOption | string;
    disconnect?: () => void;
    signMessage?: (message: string) => Promise<string>;
}
ChainWallet
,
type type EVMChain = Chain.Arbitrum | Chain.Avalanche | Chain.Base | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Optimism | Chain.PolygonEVMChain } from "@swapkit/helpers"; import {
function getEvmToolbox<T extends EVMChain>(chain: T, params?: EVMToolboxParams): Promise<{
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}>
getEvmToolbox
} from "@swapkit/toolboxes/evm";
// Create a basic EVM wallet adapter export const
const myCustomWallet: {
    connectMyCustomWallet: {
        connectWallet: (connectParams: {
            addChain: AddChainType;
        }) => (chains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]) => Promise<boolean>;
        supportedChains: (Chain.Avalanche | ... 2 more ... | Chain.Polygon)[];
    };
}
myCustomWallet
=
createWallet<[chains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]], (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[], "connectMyCustomWallet", WalletOption>({ connect, name, supportedChains, walletType, }: {
    ...;
}): {
    ...;
}
createWallet
({
name: "connectMyCustomWallet"name: "connectMyCustomWallet", // This will be the name of the connect method walletType?: string | undefinedwalletType: "custom", // Unique identifier for your wallet supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]supportedChains: [ enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum, enum ChainChain.function (enum member) Chain.BinanceSmartChain = "BSC"BinanceSmartChain, enum ChainChain.function (enum member) Chain.Avalanche = "AVAX"Avalanche, enum ChainChain.function (enum member) Chain.Polygon = "MATIC"Polygon, ],
connect: (connectParams: {
    addChain: AddChainType;
    walletType: WalletOption;
    supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[];
}) => (chains: (Chain.Avalanche | ... 2 more ... | Chain.Polygon)[]) => Promise<boolean>
connect
: ({ addChain: AddChainTypeaddChain, supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]supportedChains, walletType: WalletOptionwalletType }) =>
// This function returns the actual connect method async function function (local function) connectMyCustomWallet(chains: typeof supportedChains): Promise<boolean>connectMyCustomWallet(chains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]chains: typeof supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]supportedChains) { // Filter out chains that this wallet doesn't support const const filteredChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]filteredChains = chains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]chains.Array<Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon>.filter(predicate: (value: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon, index: number, array: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]) => unknown, thisArg?: any): (Chain.Avalanche | ... 2 more ... | Chain.Polygon)[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
@parampredicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
filter
(
(chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygonchain) => supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]supportedChains.Array<Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon>.includes(searchElement: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon, fromIndex?: number): boolean
Determines whether an array includes a certain element, returning true or false as appropriate.
@paramsearchElement The element to search for.@paramfromIndex The position in this array at which to begin searching for searchElement.
includes
(chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygonchain)
); if (const filteredChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]filteredChains.Array<T>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length
=== 0) {
// depends on your wallet implementation - you can throw or return false return false; } // For each chain, create a wallet instance for (const const chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygonchain of const filteredChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]filteredChains) { // This is where you'd implement your wallet connection logic // For this example, we'll simulate connecting to MetaMask or similar // In a real implementation, you would: // 1. Request connection to the wallet // 2. Get the provider/signer // 3. Get the address // Simulate wallet connection // In a real app, properly check this const const provider: anyprovider = (var window: Window & typeof globalThis
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
as any).ethereum;
if (!const provider: anyprovider) { throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error
("Provider not found");
} // Request accounts const const accounts: anyaccounts = await const provider: anyprovider.request({ method: stringmethod: "eth_requestAccounts" }); const const address: anyaddress = const accounts: anyaccounts[0]; // Create toolbox with the provider const
const toolbox: {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<AssetValue>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}
toolbox
= await getEvmToolbox<Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon>(chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon, params?: EVMToolboxParams): Promise<...>getEvmToolbox(const chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygonchain, {
provider?: BrowserProvider | JsonRpcProvider | undefinedprovider, // You might also add a signer here i.e.: // signer: provider.getSigner(), }); // Add the connected chain to SwapKit
addChain: <Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon>(params: Omit<ChainWallet<Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon>, "balance"> & {
    ...;
}) => void
addChain
({
...
const toolbox: {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<AssetValue>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}
toolbox
,
chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygonchain, walletType: stringwalletType, address: stringaddress, }); } return true; }, });

Here’s an example of creating a wallet adapter for a hardware wallet:


import {
  enum ChainChain,
  const NetworkDerivationPath: Record<Chain, DerivationPathArray>NetworkDerivationPath,
  enum WalletOptionWalletOption,
  
function createWallet<ConnectParams extends any[], SupportedChains extends Chain[], const Name extends string, WalletType extends WalletOption>({ connect, name, supportedChains, walletType, }: {
    connect: (connectParams: {
        addChain: AddChainType;
        walletType: WalletType;
        supportedChains: SupportedChains;
    }) => (...params: ConnectParams) => Promise<boolean>;
    name: Name;
    supportedChains: SupportedChains;
    walletType?: WalletType | string;
}): { [key in Name]: {
    connectWallet: (connectParams: {
        addChain: AddChainType;
    }) => (...params: ConnectParams) => Promise<boolean>;
    supportedChains: SupportedChains;
}; }
createWallet
,
type
type ChainWallet<T extends Chain> = {
    chain: T;
    address: string;
    balance: AssetValue[];
    walletType: WalletOption | string;
    disconnect?: () => void;
    signMessage?: (message: string) => Promise<string>;
}
ChainWallet
} from "@swapkit/helpers"; import {
function getEvmToolbox<T extends EVMChain>(chain: T, params?: EVMToolboxParams): Promise<{
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}>
getEvmToolbox
} from "@swapkit/toolboxes/evm";
import {
function getUtxoToolbox<T extends keyof UTXOToolboxes>(chain: T, params?: UtxoToolboxParams[T] | {
    phrase?: string;
    derivationPath?: DerivationPathArray;
    index?: number;
}): Promise<UTXOToolboxes[T]>
getUtxoToolbox
} from "@swapkit/toolboxes/utxo";
export const
const customHardwareWallet: {
    connectCustomHardware: {
        connectWallet: (connectParams: {
            addChain: AddChainType;
        }) => (chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[], options?: {
            ...;
        } | undefined) => Promise<boolean>;
        supportedChains: (Chain.BinanceSmartChain | ... 1 more ... | Chain.Ethereum)[];
    };
}
customHardwareWallet
=
createWallet<[chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[], options?: {
    deviceId?: string;
} | undefined], (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[], "connectCustomHardware", WalletOption>({ connect, name, supportedChains, walletType, }: {
    ...;
}): {
    ...;
}
createWallet
({
name: "connectCustomHardware"name: "connectCustomHardware", // ^ This will be the name of the connect method walletType?: string | undefinedwalletType: "customHardwareName", // ^ This will be the name of the wallet type - in case of custom plugin identification supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]supportedChains: [ enum ChainChain.function (enum member) Chain.Bitcoin = "BTC"Bitcoin, enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum, enum ChainChain.function (enum member) Chain.BinanceSmartChain = "BSC"BinanceSmartChain, ],
connect: (connectParams: {
    addChain: AddChainType;
    walletType: WalletOption;
    supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[];
}) => (chains: (Chain.BinanceSmartChain | ... 1 more ... | Chain.Ethereum)[], options?: {
    ...;
} | undefined) => Promise<boolean>
connect
: ({ addChain: AddChainTypeaddChain, supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]supportedChains, walletType: WalletOptionwalletType }) => {
// device detection async function function (local function) detectDevice(): Promise<string>detectDevice() { return "device-123"; } // address derivation from device async function
function (local function) getAddressFromDevice(chain: Chain, deviceId: string): Promise<{
    address: string;
    publicKey: string;
}>
getAddressFromDevice
(chain: Chainchain: enum ChainChain, deviceId: stringdeviceId: string) {
return { address: stringaddress: chain: Chainchain === enum ChainChain.function (enum member) Chain.Bitcoin = "BTC"Bitcoin ? "bc1q...test" : "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", publicKey: stringpublicKey: "dummy-public-key" }; } // custom device signing logic async function function (local function) signTransactionWithDevice(deviceId: string, tx: any, chain: Chain): Promise<string>signTransactionWithDevice(deviceId: stringdeviceId: string, tx: anytx: any, chain: Chainchain: enum ChainChain) { return "signed-transaction-hex"; } // Create a provider that communicates with hardware wallet function function (local function) createHardwareProvider(deviceId: string, chain: Chain): anycreateHardwareProvider(deviceId: stringdeviceId: string, chain: Chainchain: enum ChainChain) { return {} as any; // Mock for example } async function
function (local function) connectCustomHardware(chains: typeof supportedChains, options?: {
    deviceId?: string;
}): Promise<boolean>
connectCustomHardware
(chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]chains: typeof supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]supportedChains,
options: {
    deviceId?: string;
} | undefined
options
?: { deviceId?: string | undefineddeviceId?: string }) {
const const filteredChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]filteredChains = chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]chains.Array<Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum>.filter(predicate: (value: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum, index: number, array: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]) => unknown, thisArg?: any): (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
@parampredicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
filter
(
(chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereumchain) => supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]supportedChains.Array<Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum>.includes(searchElement: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum, fromIndex?: number): boolean
Determines whether an array includes a certain element, returning true or false as appropriate.
@paramsearchElement The element to search for.@paramfromIndex The position in this array at which to begin searching for searchElement.
includes
(chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereumchain)
); const const deviceId: stringdeviceId =
options: {
    deviceId?: string;
} | undefined
options
?.deviceId?: string | undefineddeviceId || await function (local function) detectDevice(): Promise<string>detectDevice();
if (const filteredChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]filteredChains.Array<T>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length
=== 0 || !const deviceId: stringdeviceId) {
// depends on your wallet implementation - you can throw or return false return false; } for (const const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereumchain of const filteredChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]filteredChains) { // Get address from the hardware device const { const address: stringaddress, const publicKey: stringpublicKey } = await
function (local function) getAddressFromDevice(chain: Chain, deviceId: string): Promise<{
    address: string;
    publicKey: string;
}>
getAddressFromDevice
(const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereumchain, const deviceId: stringdeviceId);
if (const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereumchain === enum ChainChain.function (enum member) Chain.Bitcoin = "BTC"Bitcoin) { // For Bitcoin, use UTXO toolbox const
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
= await
getUtxoToolbox<Chain.Bitcoin>(chain: Chain.Bitcoin, params?: {
    phrase?: string;
    derivationPath?: DerivationPathArray;
    index?: number;
} | {
    signer: ChainSigner<Psbt, Psbt>;
} | undefined): Promise<...>
getUtxoToolbox
(enum ChainChain.function (enum member) Chain.Bitcoin = "BTC"Bitcoin);
addChain: <Chain.Bitcoin>(params: Omit<ChainWallet<Chain.Bitcoin>, "balance"> & {
    [x: string]: any;
}) => void
addChain
({
...
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
,
chain: Chain.Bitcoinchain, walletType: stringwalletType, address: stringaddress, publicKey: stringpublicKey, getBalance: () => Promise<AssetValue[]>getBalance: () =>
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
.getBalance: (address: string, scamFilter?: boolean) => Promise<AssetValue[]>getBalance(const address: stringaddress),
transfer: (params: Parameters<({ memo, recipient, feeOptionKey, feeRate, assetValue, }: UTXOTransferParams) => Promise<string>>[0]) => Promise<string>transfer: async (params: GenericTransferParamsparams: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters
<typeof
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
.transfer: ({ memo, recipient, feeOptionKey, feeRate, assetValue, }: UTXOTransferParams) => Promise<string>transfer>[0]) => {
// Implement transfer that communicates with device for signing const { const assetValue: AssetValueassetValue, const recipient: stringrecipient, const memo: string | undefinedmemo } = params: GenericTransferParamsparams; const const tx: anytx = await
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
.buildTx({
Property 'buildTx' does not exist on type '{ accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & { outputs: TargetOutput[]; chain: UTXOChain; }) => { inputs: (UTXOType | UTXOInputWithScriptType)[]; outputs: TargetOutput[]; fee: number; } | { ...; }; ... 13 more ...; estimateMaxSendableAmount: ({ from, memo, feeRat...'.
recipient: stringrecipient, assetValue: AssetValueassetValue, sender: stringsender: const address: stringaddress, feeRate: numberfeeRate: 1.33, memo: string | undefinedmemo }); // Request signing from device const const signedTx: stringsignedTx = await function (local function) signTransactionWithDevice(deviceId: string, tx: any, chain: Chain): Promise<string>signTransactionWithDevice(const deviceId: stringdeviceId, const tx: anytx, const chain: Chain.Bitcoinchain); return
const btcToolbox: {
    accumulative: ({ inputs, outputs, feeRate: initialFeeRate, chain, }: UTXOCalculateTxSizeParams & {
        outputs: TargetOutput[];
        chain: UTXOChain;
    }) => {
        ...;
    } | {
        ...;
    };
    ... 13 more ...;
    estimateMaxSendableAmount: ({ from, memo, feeRate, feeOptionKey, recipients, }: {
        from: string;
        memo?: string;
        feeRate?: number;
        feeOptionKey?: FeeOption;
        recipients?: number | TargetOutput[];
    }) => Promise<...>;
}
btcToolbox
.broadcastTx: (txHash: string) => Promise<string>broadcastTx(const signedTx: stringsignedTx);
}, // Add other necessary methods }) } else { // For EVM chains const
const evmToolbox: {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}
evmToolbox
= await
getEvmToolbox<Chain.BinanceSmartChain | Chain.Ethereum>(chain: Chain.BinanceSmartChain | Chain.Ethereum, params?: EVMToolboxParams): Promise<{
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}>
getEvmToolbox
(const chain: Chain.BinanceSmartChain | Chain.Ethereumchain, {
// Initialize with a special provider that communicates with hardware provider?: BrowserProvider | JsonRpcProvider | undefinedprovider: function (local function) createHardwareProvider(deviceId: string, chain: Chain): anycreateHardwareProvider(const deviceId: stringdeviceId, const chain: Chain.BinanceSmartChain | Chain.Ethereumchain), });
addChain: <Chain.BinanceSmartChain | Chain.Ethereum>(params: Omit<ChainWallet<Chain.BinanceSmartChain | Chain.Ethereum>, "balance"> & {
    [x: string]: any;
}) => void
addChain
({
...
const evmToolbox: {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}
evmToolbox
,
getBalance: () => Promise<AssetValue[]>getBalance: () =>
const evmToolbox: {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
} | {
    ...;
} | {
    ...;
}
evmToolbox
.getBalance: (address: string, scamFilter?: boolean) => Promise<AssetValue[]>getBalance(const address: stringaddress),
chain: Chain.BinanceSmartChain | Chain.Ethereumchain, walletType: stringwalletType, address: stringaddress, }); } } return true; } return
function (local function) connectCustomHardware(chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[], options?: {
    deviceId?: string;
}): Promise<boolean>
connectCustomHardware
;
} });

To use your custom wallet adapter with SwapKit:

import { 
function SwapKit<Plugins extends ReturnType<typeof createPlugin>, Wallets extends ReturnType<typeof createWallet>>({ config, plugins, wallets, }?: {
    config?: SKConfigState;
    plugins?: Plugins;
    wallets?: Wallets;
}): { [key in keyof Plugins]: ReturnType<...>; } & ... 1 more ... & {
    ...;
}
SwapKit
, enum ChainChain } from "@swapkit/core";
import { import myCustomWalletmyCustomWallet } from "./my-custom-wallet"; // Initialize SwapKit with your wallet adapter const
const swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: any;
} & {
    disconnectAll: () => void;
    ... 14 more ...;
    verifyMessage: ({ address, chain, message, signature, }: {
        ...;
    }) => Promise<...>;
}
swapKit
=
SwapKit<{
    [x: string]: (pluginParams: SwapKitPluginParams) => {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<...>;
}, any>({ config, plugins, wallets, }?: {
    ...;
}): {
    ...;
} & ... 1 more ... & {
    ...;
}
SwapKit
({
wallets?: anywallets: { ...import myCustomWalletmyCustomWallet }, }); // Connect using your custom wallet async function function connectWithCustomWallet(): Promise<void>connectWithCustomWallet() { await
const swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: any;
} & {
    disconnectAll: () => void;
    ... 14 more ...;
    verifyMessage: ({ address, chain, message, signature, }: {
        ...;
    }) => Promise<...>;
}
swapKit
.connectMyCustomWallet(["ETH", "BSC"]);
const
const ethWallet: (ChainWallet<Chain.Arbitrum> & {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
}) | ... 21 more ... | (ChainWallet<...> & {
    ...;
})
ethWallet
= await
const swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: any;
} & {
    disconnectAll: () => void;
    ... 14 more ...;
    verifyMessage: ({ address, chain, message, signature, }: {
        ...;
    }) => Promise<...>;
}
swapKit
.
getWallet: <Chain>(chain: Chain) => (ChainWallet<Chain.Arbitrum> & {
    estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
        feeOption: FeeOption;
        chain: EVMChain;
    }) => Promise<...>;
    ... 20 more ...;
    validateAddress: (address: string) => boolean;
}) | ... 21 more ... | (ChainWallet<...> & {
    ...;
})
@Public
getWallet
("ETH");
}

Test your wallet adapter thoroughly before using it in production:

import { const describe: Describe
Describes a group of related tests.
@examplefunction sum(a, b) { return a + b; } describe("sum()", () => { test("can sum two values", () => { expect(sum(1, 1)).toBe(2); }); });@paramlabel the label for the tests@paramfn the function that defines the tests
describe
, const jest: Jestjest, const it: Test
Runs a test.
@exampletest("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); });@paramlabel the label for the test@paramfn the test function
it
, const expect: Expect
Asserts that a value matches some criteria.
@linkhttps://jestjs.io/docs/expect#reference@exampleexpect(1 + 1).toBe(2); expect([1,2,3]).toContain(2); expect(null).toBeNull();@paramactual The actual (received) value
expect
, function beforeEach(fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void)): void
Runs a function before each test. This is useful for running set up tasks, like initializing a global variable or connecting to a database. If this function throws, the test will not run.
@paramfn the function to run
beforeEach
} from "bun:test";
import { enum ChainChain, class AssetValueAssetValue } from "@swapkit/helpers"; import {
function SwapKit<Plugins extends ReturnType<typeof createPlugin>, Wallets extends ReturnType<typeof createWallet>>({ config, plugins, wallets, }?: {
    config?: SKConfigState;
    plugins?: Plugins;
    wallets?: Wallets;
}): { [key in keyof Plugins]: ReturnType<...>; } & ... 1 more ... & {
    ...;
}
SwapKit
} from "@swapkit/core";
import { import myCustomWalletmyCustomWallet } from "./my-custom-wallet"; function describe(label: DescribeLabel, fn: () => void): void (+1 overload)
Describes a group of related tests.
@examplefunction sum(a, b) { return a + b; } describe("sum()", () => { test("can sum two values", () => { expect(sum(1, 1)).toBe(2); }); });@paramlabel the label for the tests@paramfn the function that defines the tests
describe
("MyCustomWallet", () => {
let
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType
<typeof
function SwapKit<Plugins extends ReturnType<typeof createPlugin>, Wallets extends ReturnType<typeof createWallet>>({ config, plugins, wallets, }?: {
    config?: SKConfigState;
    plugins?: Plugins;
    wallets?: Wallets;
}): { [key in keyof Plugins]: ReturnType<...>; } & ... 1 more ... & {
    ...;
}
SwapKit
>;
function beforeEach(fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void)): void
Runs a function before each test. This is useful for running set up tasks, like initializing a global variable or connecting to a database. If this function throws, the test will not run.
@paramfn the function to run
beforeEach
(() => {
// Mock window.ethereum var global: typeof globalThisglobal.var window: Window & typeof globalThis
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
= {
ethereum: {
    request: Mock<(...args: any[]) => any>;
}
ethereum
: {
request: Mock<(...args: any[]) => any>request: const jest: Jestjest.Jest.fn<(...args: any[]) => any>(func?: ((...args: any[]) => any) | undefined): Mock<(...args: any[]) => any>fn().JestMock.MockInstance<(...args: any[]) => any>.mockResolvedValue(value: unknown): Mock<(...args: any[]) => any>mockResolvedValue(["0x742d35Cc6634C0532925a3b844Bc454e4438f44e"]), // Add other methods as needed }, } as any;
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
=
SwapKit<{
    [x: string]: (pluginParams: SwapKitPluginParams) => {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<...>;
}, {
    ...;
}>({ config, plugins, wallets, }?: {
    ...;
}): {
    ...;
} & ... 1 more ... & {
    ...;
}
SwapKit
({
wallets?: {
    myWallet: any;
} | undefined
wallets
: {
myWallet: anymyWallet: import myCustomWalletmyCustomWallet, }, }); }); function it(label: string, fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void), options?: number | TestOptions): void
Runs a test.
@exampletest("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); });@paramlabel the label for the test@paramfn the test function
it
("should connect to Ethereum", async () => {
await
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
.
{
    supportedSwapkitProviders?: (ProviderName | string)[];
} & Record<string, unknown> & ((...params: any[]) => Promise<boolean>)
connectMyCustomWallet
([enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum]);
const
const wallets: {
    ARB: ChainWallet<Chain.Arbitrum> & {
        estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
            feeOption: FeeOption;
            chain: EVMChain;
        }) => Promise<...>;
        ... 20 more ...;
        validateAddress: (address: string) => boolean;
    };
    ... 21 more ...;
    SOL: ChainWallet<...> & {
        ...;
    };
}
wallets
=
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
.
getAllWallets: () => {
    ARB: ChainWallet<Chain.Arbitrum> & {
        estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
            feeOption: FeeOption;
            chain: EVMChain;
        }) => Promise<...>;
        ... 20 more ...;
        validateAddress: (address: string) => boolean;
    };
    ... 21 more ...;
    SOL: ChainWallet<...> & {
        ...;
    };
}
getAllWallets
();
expect<ChainWallet<Chain.Ethereum> & {
    multicall: (callTuples: {
        address: string;
        data: string;
    }[], multicallAddress?: string, funcName?: string, feeOptionKey?: FeeOption) => Promise<...>;
    ... 21 more ...;
    validateAddress: (address: string) => boolean;
}>(actual?: (ChainWallet<...> & {
    multicall: (callTuples: {
        address: string;
        data: string;
    }[], multicallAddress?: string, funcName?: string, feeOptionKey?: FeeOption) => Promise<...>;
    ... 21 more ...;
    validateAddress: (address: string) => boolean;
}) | undefined, customFailMessage?: string): Matchers<...>
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(
const wallets: {
    ARB: ChainWallet<Chain.Arbitrum> & {
        estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
            feeOption: FeeOption;
            chain: EVMChain;
        }) => Promise<...>;
        ... 20 more ...;
        validateAddress: (address: string) => boolean;
    };
    ... 21 more ...;
    SOL: ChainWallet<...> & {
        ...;
    };
}
wallets
[enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum]).MatchersBuiltin<ChainWallet<Chain.Ethereum> & { multicall: (callTuples: { address: string; data: string; }[], multicallAddress?: string, funcName?: string, feeOptionKey?: FeeOption) => Promise<...>; ... 21 more ...; validateAddress: (address: string) => boolean; }>.toBeDefined(): void
Asserts that a value is defined. (e.g. is not `undefined`)
@exampleexpect(true).toBeDefined(); expect(undefined).toBeDefined(); // fail
toBeDefined
();
expect<string>(actual?: string | undefined, customFailMessage?: string): Matchers<string>
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(
const wallets: {
    ARB: ChainWallet<Chain.Arbitrum> & {
        estimateTransactionFee: ({ feeOption, chain, ...txObject }: EIP1559TxParams & {
            feeOption: FeeOption;
            chain: EVMChain;
        }) => Promise<...>;
        ... 20 more ...;
        validateAddress: (address: string) => boolean;
    };
    ... 21 more ...;
    SOL: ChainWallet<...> & {
        ...;
    };
}
wallets
[enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum].address: stringaddress).MatchersBuiltin<string>.toBe(expected: string): void
Asserts that a value equals what is expected. - For non-primitive values, like objects and arrays, use `toEqual()` instead. - For floating-point numbers, use `toBeCloseTo()` instead.
@exampleexpect(100 + 23).toBe(123); expect("d" + "og").toBe("dog"); expect([123]).toBe([123]); // fail, use toEqual() expect(3 + 0.14).toBe(3.14); // fail, use toBeCloseTo()@paramexpected the expected value
toBe
("0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
}); function it(label: string, fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void), options?: number | TestOptions): void
Runs a test.
@exampletest("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); });@paramlabel the label for the test@paramfn the test function
it
("should transfer ETH", async () => {
await
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
.
{
    supportedSwapkitProviders?: (ProviderName | string)[];
} & Record<string, unknown> & ((...params: any[]) => Promise<boolean>)
connectMyCustomWallet
([enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum]);
// Mock the transfer method for testing const const mockTransfer: Mock<(...args: any[]) => any>mockTransfer = const jest: Jestjest.Jest.fn<(...args: any[]) => any>(func?: ((...args: any[]) => any) | undefined): Mock<(...args: any[]) => any>fn().JestMock.MockInstance<(...args: any[]) => any>.mockResolvedValue(value: unknown): Mock<(...args: any[]) => any>mockResolvedValue("0xtxhash");
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
.
getWallet: <Chain.Ethereum>(chain: Chain.Ethereum) => ChainWallet<Chain.Ethereum> & {
    multicall: (callTuples: {
        address: string;
        data: string;
    }[], multicallAddress?: string, funcName?: string, feeOptionKey?: FeeOption) => Promise<...>;
    ... 21 more ...;
    validateAddress: (address: string) => boolean;
}
@Public
getWallet
(enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum).transfer: ({ assetValue, memo, recipient, feeOptionKey, sender, ...tx }: EVMTransferParams) => Promise<string>transfer = const mockTransfer: Mock<(...args: any[]) => any>mockTransfer;
const const result: anyresult = await
let swapKit: {
    [x: string]: {
        supportedSwapkitProviders?: (ProviderName | string)[];
    } & Record<string, unknown>;
} & {
    [x: string]: (...params: any[]) => Promise<boolean>;
} & {
    ...;
}
swapKit
.transfer: ({ assetValue, ...params }: GenericTransferParams | EVMTransferParams) => Promise<any>transfer({
assetValue: AssetValueassetValue: class AssetValueAssetValue.
AssetValue.from<{
    chain: Chain.Ethereum;
    value: string;
}>({ value, fromBaseDecimal, asyncTokenLookup, ...fromAssetOrChain }: {
    chain: Chain.Ethereum;
    value: string;
} & AssetValueFromParams): AssetValue
from
({chain: Chain.Ethereumchain: enum ChainChain.function (enum member) Chain.Ethereum = "ETH"Ethereum, value: stringvalue: "0.1" }),
recipient: stringrecipient: "0xabc123...", }); expect<Mock<(...args: any[]) => any>>(actual?: Mock<(...args: any[]) => any> | undefined, customFailMessage?: string): Matchers<Mock<(...args: any[]) => any>>
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(const mockTransfer: Mock<(...args: any[]) => any>mockTransfer).MatchersBuiltin<Mock<(...args: any[]) => any>>.toHaveBeenCalled(): void
Ensures that a mock function is called.
toHaveBeenCalled
();
expect<any>(actual?: any, customFailMessage?: string): Matchers<any>
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(const result: anyresult).MatchersBuiltin<any>.toBe(expected: any): void
Asserts that a value equals what is expected. - For non-primitive values, like objects and arrays, use `toEqual()` instead. - For floating-point numbers, use `toBeCloseTo()` instead.
@exampleexpect(100 + 23).toBe(123); expect("d" + "og").toBe("dog"); expect([123]).toBe([123]); // fail, use toEqual() expect(3 + 0.14).toBe(3.14); // fail, use toBeCloseTo()@paramexpected the expected value
toBe
("0xtxhash");
}); });

When creating a custom wallet adapter, follow these best practices:

  1. Error Handling: Provide clear error messages when connection or operations fail
  2. Chain Filtering: Properly filter and validate supported chains
  3. Feature Detection: Check if features like signing or specific networks are supported
  4. Security: Be careful with private keys and sensitive operations
  5. Testing: Test thoroughly with both happy and error paths
  6. Documentation: Document any special requirements or features of your wallet

By following this guide, you can create a custom wallet adapter that integrates seamlessly with the SwapKit ecosystem.