Bun + React
This guide will help you integrate SwapKit into your Bun-powered React application. Bun is a fast JavaScript runtime that includes a bundler, test runner, and package manager.
Installation
Section titled “Installation”Install SwapKit:
bun add @swapkit/sdk
pnpm add @swapkit/sdk
npm install @swapkit/sdk
yarn add @swapkit/sdk
Project Setup
Section titled “Project Setup”Create a new Bun + React project:
bun create react my-swapkit-app
cd my-swapkit-app
Basic Configuration
Section titled “Basic Configuration”Create a bunfig.toml
file for Bun configuration:
[serve.static]
plugins = ["bun-plugin-tailwind"]
env = "BUN_PUBLIC_*"
Server Setup
Section titled “Server Setup”Create a server file to serve your React application:
// src/server.tsx
import { function serve<T, R extends { [K in keyof R]: Bun.RouterTypes.RouteValue<K & string>; }>(options: Bun.ServeFunctionOptions<T, R> & {
static?: R;
}): Bun.Server
Bun.serve provides a high-performance HTTP server with built-in routing support.
It enables both function-based and object-based route handlers with type-safe
parameters and method-specific handling.serve } from "bun";
const const PORT: string | 3000
PORT = var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment.
See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html).
An example of this object looks like:
```js
{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}
```
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other `Worker` threads.
In other words, the following example would not work:
```bash
node -e 'process.env.foo = "bar"' && echo $foo
```
While the following will:
```js
import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);
```
Assigning a property on `process.env` will implicitly convert the value
to a string. **This behavior is deprecated.** Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
```js
import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'
```
Use `delete` to delete a property from `process.env`.
```js
import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined
```
On Windows operating systems, environment variables are case-insensitive.
```js
import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1
```
Unless explicitly specified when creating a `Worker` instance,
each `Worker` thread has its own copy of `process.env`, based on its
parent thread's `process.env`, or whatever was specified as the `env` option
to the `Worker` constructor. Changes to `process.env` will not be visible
across `Worker` threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner
unlike the main thread.env.string | undefined
PORT || 3000;
serve<unknown, {}>(options: Bun.ServeFunctionOptions<unknown, {}> & {
static?: {} | undefined;
}): Bun.Server
Bun.serve provides a high-performance HTTP server with built-in routing support.
It enables both function-based and object-based route handlers with type-safe
parameters and method-specific handling.serve({
port: string | number
port: const PORT: string | 3000
PORT,
async function fetch(this: Bun.Server, req: Request): Promise<Response>
fetch(req: Request
req) {
const const url: URL
url = new var URL: new (url: string | URL, base?: string | URL) => URL
The URL interface represents an object providing static methods used for creating object URLs.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)URL(req: Request
req.Request.url: string
Returns the URL of request as a string.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)url);
// Serve static files
if (const url: URL
url.URL.pathname: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)pathname === "/") {
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response(module "bun"
Bun.js runtime APIsBun.function file(path: string | URL, options?: BlobPropertyBag): Bun.BunFile (+2 overloads)
[`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) powered by the fastest system calls available for operating on files.
This Blob is lazy. That means it won't do any work until you read from it.
- `size` will not be valid until the contents of the file are read at least once.
- `type` is auto-set based on the file extension when possiblefile("pages/index.html"));
}
// Handle API routes if needed
if (const url: URL
url.URL.pathname: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)pathname.String.startsWith(searchString: string, position?: number): boolean
Returns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.startsWith("/api/")) {
// Add your API logic here
}
// Default 404
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response("Not Found", { ResponseInit.status?: number | undefined
status: 404 });
},
});
console.Console.log(message?: any, ...optionalParams: any[]): void (+3 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.log(`Server running at http://localhost:${const PORT: string | 3000
PORT}`);
React Application Setup
Section titled “React Application Setup”Create your main React application with SwapKit:
// src/App.tsx
import { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.useState } from 'react';
import { function createSwapKit(config?: Parameters<typeof SwapKit>[0]): {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
createSwapKit, enum Chain
Chain, enum WalletOption
WalletOption } from '@swapkit/sdk';
const const swapKit: {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
swapKit = function createSwapKit(config?: Parameters<typeof SwapKit>[0]): {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
createSwapKit();
export function function App(): React.JSX.Element
App() {
const [const isConnected: boolean
isConnected, const setIsConnected: React.Dispatch<React.SetStateAction<boolean>>
setIsConnected] = useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)
Returns a stateful value, and a function to update it.useState(false);
const [const address: string
address, const setAddress: React.Dispatch<React.SetStateAction<string>>
setAddress] = useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)
Returns a stateful value, and a function to update it.useState<string>('');
const const connectWallet: () => Promise<void>
connectWallet = async () => {
try {
await const swapKit: {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
swapKit.connectEVMWallet: (chains: Chain[], walletType?: EVMWalletOptions | undefined, eip1193Provider?: Eip1193Provider | undefined) => Promise<boolean>
connectEVMWallet([enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum], enum WalletOption
WalletOption.function (enum member) WalletOption.METAMASK = "METAMASK"
METAMASK);
const setIsConnected: (value: React.SetStateAction<boolean>) => void
setIsConnected(true);
const const ethAddress: string
ethAddress = await const swapKit: {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
swapKit.getAddress: <Chain.Ethereum>(chain: Chain.Ethereum) => string
getAddress(enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum);
const setAddress: (value: React.SetStateAction<string>) => void
setAddress(const ethAddress: string
ethAddress);
} catch (error) {
console.Console.error(message?: any, ...optionalParams: any[]): void (+3 overloads)
Prints to `stderr` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)).
```js
const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr
```
If formatting elements (e.g. `%d`) are not found in the first string then
[`util.inspect()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilinspectobject-options) is called on each argument and the
resulting string values are concatenated. See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)
for more information.error('Failed to connect wallet:', error);
}
};
const const disconnectWallet: () => void
disconnectWallet = () => {
const swapKit: {
chainflip: {
supportedSwapkitProviders: ProviderName[];
} & {
swap: (swapParams: RequestSwapDepositAddressParams) => Promise<...>;
};
... 5 more ...;
near: {
...;
} & {
...;
};
} & {
...;
} & {
...;
}
swapKit.Property 'disconnectWallet' does not exist on type '{ chainflip: { supportedSwapkitProviders: ProviderName[]; } & { swap: (swapParams: RequestSwapDepositAddressParams) => Promise<string>; }; ... 5 more ...; near: { ...; } & { ...; }; } & { ...; } & { ...; }'. Did you mean 'disconnectAll'?disconnectWallet(enum Chain
Chain.function (enum member) Chain.Ethereum = "ETH"
Ethereum);
const setIsConnected: (value: React.SetStateAction<boolean>) => void
setIsConnected(false);
const setAddress: (value: React.SetStateAction<string>) => void
setAddress('');
};
return (
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.div className="p-8">
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.h1 className="text-3xl font-bold mb-4">SwapKit + Bun</h1>
{!const isConnected: boolean
isConnected ? (
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.button
onClick={const connectWallet: () => Promise<void>
connectWallet}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Connect Wallet
</button>
) : (
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.div>
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.p className="mb-4">Connected: {const address: string
address}</p>
<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.button
onClick={const disconnectWallet: () => void
disconnectWallet}
className="px-4 py-2 bg-red-500 text-white rounded"
>
Disconnect
</button>
</div>
)}
</div>
);
}
Entry Point
Section titled “Entry Point”Create the entry point for your React application:
// pages/index.tsx
import { function createRoot(container: Container, options?: RootOptions): Root
createRoot lets you create a root to display React components inside a browser DOM node.createRoot } from 'react-dom/client';
import { import App
App } from Cannot find module '../src/App' or its corresponding type declarations.'../src/App';
import '../styles/index.css';
const container = var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.getElementById('root');
if (container) {
const const root: Root
root = function createRoot(container: Container, options?: RootOptions): Root
createRoot lets you create a root to display React components inside a browser DOM node.createRoot(container);
const root: Root
root.Root.render(children: React.ReactNode): void
render(<'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.App />);
}
HTML Template
Section titled “HTML Template”Create an HTML template:
<!-- pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SwapKit + Bun</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/pages/index.tsx"></script>
</body>
</html>
Build Configuration
Section titled “Build Configuration”Create a build script for production:
// build.ts
import { function build(config: Bun.BuildConfig): Promise<Bun.BuildOutput>
Bundles JavaScript, TypeScript, CSS, HTML and other supported files into optimized outputs.build } from "bun";
await function build(config: Bun.BuildConfig): Promise<Bun.BuildOutput>
Bundles JavaScript, TypeScript, CSS, HTML and other supported files into optimized outputs.build({
BuildConfig.entrypoints: string[]
entrypoints: ["./pages/index.tsx"],
BuildConfig.outdir?: string | undefined
outdir: "./dist",
BuildConfig.target?: Bun.Target | undefined
target: "browser",
BuildConfig.minify?: boolean | {
whitespace?: boolean;
syntax?: boolean;
identifiers?: boolean;
} | undefined
Whether to enable minification.
Use `true`/`false` to enable/disable all minification options. Alternatively,
you can pass an object for granular control over certain minifications.minify: true,
BuildConfig.splitting?: boolean | undefined
splitting: true,
BuildConfig.sourcemap?: boolean | "external" | "none" | "linked" | "inline" | undefined
Specifies if and how to generate source maps.
- `"none"` - No source maps are generated
- `"linked"` - A separate `*.ext.map` file is generated alongside each
`*.ext` file. A `//# sourceMappingURL` comment is added to the output
file to link the two. Requires `outdir` to be set.
- `"inline"` - an inline source map is appended to the output file.
- `"external"` - Generate a separate source map file for each input file.
No `//# sourceMappingURL` comment is added to the output file.
`true` and `false` are aliases for `"inline"` and `"none"`, respectively.sourcemap: "external",
});
Environment Variables
Section titled “Environment Variables”Bun uses BUN_PUBLIC_
prefix for client-side environment variables:
# .env
BUN_PUBLIC_SWAPKIT_API_KEY=your_api_key_here
BUN_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id_here
Access them in your code:
import { const SKConfig: {
getState: () => SwapKitConfigStore;
get: <T extends "apiKeys" | "apis" | "chains" | "wallets" | "explorerUrls" | "nodeUrls" | "rpcUrls" | "envs" | "integrations">(key: T) => SwapKitConfigStore[T];
... 6 more ...;
setIntegrationConfig: <T extends keyof SKConfigIntegrations>(integration: T, config: SKConfigIntegrations[T]) => void;
}
SKConfig } from '@swapkit/sdk';
if (var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment.
See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html).
An example of this object looks like:
```js
{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}
```
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other `Worker` threads.
In other words, the following example would not work:
```bash
node -e 'process.env.foo = "bar"' && echo $foo
```
While the following will:
```js
import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);
```
Assigning a property on `process.env` will implicitly convert the value
to a string. **This behavior is deprecated.** Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
```js
import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'
```
Use `delete` to delete a property from `process.env`.
```js
import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined
```
On Windows operating systems, environment variables are case-insensitive.
```js
import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1
```
Unless explicitly specified when creating a `Worker` instance,
each `Worker` thread has its own copy of `process.env`, based on its
parent thread's `process.env`, or whatever was specified as the `env` option
to the `Worker` constructor. Changes to `process.env` will not be visible
across `Worker` threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner
unlike the main thread.env.string | undefined
BUN_PUBLIC_SWAPKIT_API_KEY) {
const SKConfig: {
getState: () => SwapKitConfigStore;
get: <T extends "apiKeys" | "apis" | "chains" | "wallets" | "explorerUrls" | "nodeUrls" | "rpcUrls" | "envs" | "integrations">(key: T) => SwapKitConfigStore[T];
... 6 more ...;
setIntegrationConfig: <T extends keyof SKConfigIntegrations>(integration: T, config: SKConfigIntegrations[T]) => void;
}
SKConfig.setApiKey: <"swapKit">(key: "swapKit", apiKey: string) => void
setApiKey('swapKit', var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment.
See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html).
An example of this object looks like:
```js
{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}
```
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other `Worker` threads.
In other words, the following example would not work:
```bash
node -e 'process.env.foo = "bar"' && echo $foo
```
While the following will:
```js
import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);
```
Assigning a property on `process.env` will implicitly convert the value
to a string. **This behavior is deprecated.** Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
```js
import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'
```
Use `delete` to delete a property from `process.env`.
```js
import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined
```
On Windows operating systems, environment variables are case-insensitive.
```js
import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1
```
Unless explicitly specified when creating a `Worker` instance,
each `Worker` thread has its own copy of `process.env`, based on its
parent thread's `process.env`, or whatever was specified as the `env` option
to the `Worker` constructor. Changes to `process.env` will not be visible
across `Worker` threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner
unlike the main thread.env.string
BUN_PUBLIC_SWAPKIT_API_KEY);
}
Common Issues
Section titled “Common Issues”Module Resolution
Section titled “Module Resolution”Bun handles module resolution differently than Node.js. If you encounter issues:
- Clear the cache:
bun clean
- Reinstall dependencies:
bun install
TypeScript Configuration
Section titled “TypeScript Configuration”Bun supports TypeScript out of the box. Create a tsconfig.json
:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"jsx": "react-jsx",
"moduleResolution": "bundler",
"types": ["bun-types"]
}
}
Example Repository
Section titled “Example Repository”For a complete working example, check out the Bun + React playground in the SwapKit repository.