Creating a Custom Wallet
Creating a Custom Wallet Adapter
Section titled “Creating a Custom Wallet Adapter”This guide explains how to create a custom wallet adapter for SwapKit using the createWallet
function.
Wallet Adapter Structure
Section titled “Wallet Adapter Structure”A SwapKit wallet adapter consists of:
- A wallet factory created with
createWallet
- Configuration for supported chains and derivation paths
- Connection methods for interacting with the wallet
Basic Wallet Adapter Example
Section titled “Basic Wallet Adapter Example”Here’s a simple example of creating a custom wallet adapter:
import {
enum Chain
Chain,
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.Polygon
EVMChain
} 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 | undefined
walletType: "custom", // Unique identifier for your wallet
supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]
supportedChains: [
enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum,
enum Chain
Chain.function (enum member) Chain.BinanceSmartChain = "BSC"
BinanceSmartChain,
enum Chain
Chain.function (enum member) Chain.Avalanche = "AVAX"
Avalanche,
enum Chain
Chain.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: AddChainType
addChain, supportedChains: (Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon)[]
supportedChains, walletType: WalletOption
walletType }) =>
// 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.filter(
(chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon
chain) => 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.includes(chain: Chain.Avalanche | Chain.BinanceSmartChain | Chain.Ethereum | Chain.Polygon
chain)
);
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.Polygon
chain 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: any
provider = (var window: Window & typeof globalThis
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)window as any).ethereum;
if (!const provider: any
provider) {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+2 overloads)
Error("Provider not found");
}
// Request accounts
const const accounts: any
accounts = await const provider: any
provider.request({ method: string
method: "eth_requestAccounts" });
const const address: any
address = const accounts: any
accounts[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.Polygon
chain, {
provider?: BrowserProvider | JsonRpcProvider | undefined
provider,
// 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.Polygon
chain,
walletType: string
walletType,
address: string
address,
});
}
return true;
},
});
Wallet with Hardware Connection
Section titled “Wallet with Hardware Connection”Here’s an example of creating a wallet adapter for a hardware wallet:
import {
enum Chain
Chain,
const NetworkDerivationPath: Record<Chain, DerivationPathArray>
NetworkDerivationPath,
enum WalletOption
WalletOption,
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 | undefined
walletType: "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 Chain
Chain.function (enum member) Chain.Bitcoin = "BTC"
Bitcoin,
enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum,
enum Chain
Chain.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: AddChainType
addChain, supportedChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]
supportedChains, walletType: WalletOption
walletType }) => {
// 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: Chain
chain: enum Chain
Chain, deviceId: string
deviceId: string) {
return {
address: string
address: chain: Chain
chain === enum Chain
Chain.function (enum member) Chain.Bitcoin = "BTC"
Bitcoin
? "bc1q...test"
: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
publicKey: string
publicKey: "dummy-public-key"
};
}
// custom device signing logic
async function function (local function) signTransactionWithDevice(deviceId: string, tx: any, chain: Chain): Promise<string>
signTransactionWithDevice(deviceId: string
deviceId: string, tx: any
tx: any, chain: Chain
chain: enum Chain
Chain) {
return "signed-transaction-hex";
}
// Create a provider that communicates with hardware wallet
function function (local function) createHardwareProvider(deviceId: string, chain: Chain): any
createHardwareProvider(deviceId: string
deviceId: string, chain: Chain
chain: enum Chain
Chain) {
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 | undefined
deviceId?: 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.filter(
(chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum
chain) => 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.includes(chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum
chain)
);
const const deviceId: string
deviceId = options: {
deviceId?: string;
} | undefined
options?.deviceId?: string | undefined
deviceId || 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: string
deviceId) {
// depends on your wallet implementation - you can throw or return false
return false;
}
for (const const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum
chain of const filteredChains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[]
filteredChains) {
// Get address from the hardware device
const { const address: string
address, const publicKey: string
publicKey } = await function (local function) getAddressFromDevice(chain: Chain, deviceId: string): Promise<{
address: string;
publicKey: string;
}>
getAddressFromDevice(const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum
chain, const deviceId: string
deviceId);
if (const chain: Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum
chain === enum Chain
Chain.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 Chain
Chain.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.Bitcoin
chain,
walletType: string
walletType,
address: string
address,
publicKey: string
publicKey,
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: string
address),
transfer: (params: Parameters<({ memo, recipient, feeOptionKey, feeRate, assetValue, }: UTXOTransferParams) => Promise<string>>[0]) => Promise<string>
transfer: async (params: GenericTransferParams
params: type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tupleParameters<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: AssetValue
assetValue, const recipient: string
recipient, const memo: string | undefined
memo } = params: GenericTransferParams
params;
const const tx: any
tx = 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({ recipient: string
recipient,
assetValue: AssetValue
assetValue,
sender: string
sender: const address: string
address,
feeRate: number
feeRate: 1.33,
memo: string | undefined
memo
});
// Request signing from device
const const signedTx: string
signedTx = await function (local function) signTransactionWithDevice(deviceId: string, tx: any, chain: Chain): Promise<string>
signTransactionWithDevice(const deviceId: string
deviceId, const tx: any
tx, const chain: Chain.Bitcoin
chain);
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: string
signedTx);
},
// 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.Ethereum
chain, {
// Initialize with a special provider that communicates with hardware
provider?: BrowserProvider | JsonRpcProvider | undefined
provider: function (local function) createHardwareProvider(deviceId: string, chain: Chain): any
createHardwareProvider(const deviceId: string
deviceId, const chain: Chain.BinanceSmartChain | Chain.Ethereum
chain),
});
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: string
address),
chain: Chain.BinanceSmartChain | Chain.Ethereum
chain,
walletType: string
walletType,
address: string
address,
});
}
}
return true;
}
return function (local function) connectCustomHardware(chains: (Chain.BinanceSmartChain | Chain.Bitcoin | Chain.Ethereum)[], options?: {
deviceId?: string;
}): Promise<boolean>
connectCustomHardware;
}
});
Using Your Custom Wallet with SwapKit
Section titled “Using Your Custom Wallet with SwapKit”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 Chain
Chain } from "@swapkit/core";
import { import myCustomWallet
myCustomWallet } 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?: any
wallets: { ...import myCustomWallet
myCustomWallet },
});
// 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<...> & {
...;
})
getWallet("ETH");
}
Testing Your Wallet Adapter
Section titled “Testing Your Wallet Adapter”Test your wallet adapter thoroughly before using it in production:
import { const describe: Describe
Describes a group of related tests.describe, const jest: Jest
jest, const it: Test
Runs a test.it, const expect: Expect
Asserts that a value matches some criteria.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.beforeEach } from "bun:test";
import { enum Chain
Chain, class AssetValue
AssetValue } 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 myCustomWallet
myCustomWallet } from "./my-custom-wallet";
function describe(label: DescribeLabel, fn: () => void): void (+1 overload)
Describes a group of related 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 typeReturnType<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.beforeEach(() => {
// Mock window.ethereum
var global: typeof globalThis
global.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: Jest
jest.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: any
myWallet: import myCustomWallet
myCustomWallet,
},
});
});
function it(label: string, fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void), options?: number | TestOptions): void
Runs a test.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 Chain
Chain.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<...>
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 Chain
Chain.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`)toBeDefined();
expect<string>(actual?: string | undefined, customFailMessage?: string): Matchers<string>
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 Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum].address: string
address).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.toBe("0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
});
function it(label: string, fn: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void), options?: number | TestOptions): void
Runs a test.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 Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum]);
// Mock the transfer method for testing
const const mockTransfer: Mock<(...args: any[]) => any>
mockTransfer = const jest: Jest
jest.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;
}
getWallet(enum Chain
Chain.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: any
result = 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: AssetValue
assetValue: class AssetValue
AssetValue.AssetValue.from<{
chain: Chain.Ethereum;
value: string;
}>({ value, fromBaseDecimal, asyncTokenLookup, ...fromAssetOrChain }: {
chain: Chain.Ethereum;
value: string;
} & AssetValueFromParams): AssetValue
from({chain: Chain.Ethereum
chain: enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum, value: string
value: "0.1" }),
recipient: string
recipient: "0xabc123...",
});
expect<Mock<(...args: any[]) => any>>(actual?: Mock<(...args: any[]) => any> | undefined, customFailMessage?: string): Matchers<Mock<(...args: any[]) => any>>
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>
expect(const result: any
result).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.toBe("0xtxhash");
});
});
Best Practices
Section titled “Best Practices”When creating a custom wallet adapter, follow these best practices:
- Error Handling: Provide clear error messages when connection or operations fail
- Chain Filtering: Properly filter and validate supported chains
- Feature Detection: Check if features like signing or specific networks are supported
- Security: Be careful with private keys and sensitive operations
- Testing: Test thoroughly with both happy and error paths
- 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.