# Quick Start – Node.js

This guide covers installing the Atomiq SDK in Node.js and its chain-specific connectors and walks you through setting up and initializing the Atomiq SDK.

## Core SDK

Install the main SDK package, and for Node.js applications, install the SQLite storage adapter:

```
npm install @atomiqlabs/sdk@latest

npm install @atomiqlabs/storage-sqlite@latest
```

### Chain Connectors

The SDK supports multiple chains. Install only the chain connectors your project needs, and mix and match them as required:

```
npm install @atomiqlabs/chain-solana@latest

npm install @atomiqlabs/chain-starknet@latest

npm install @atomiqlabs/chain-evm@latest
```

## Setup

tip

See [setup.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/setup.ts) in the demo repo for a ready-to-use version of the setup below.

* Mainnet
* Testnet3
* Testnet4

Set your RPC URLs:

```
const solanaRpc = "https://api.mainnet-beta.solana.com";

const starknetRpc = "https://rpc.starknet.lava.build/"; //Alternatively: https://starknet.api.onfinality.io/public or https://api.zan.top/public/starknet-mainnet

const citreaRpc = "https://rpc.mainnet.citrea.xyz";
```

Create a swapper factory with your desired chain support. Use `as const` so TypeScript can properly infer the types:

```
import {SolanaInitializer} from "@atomiqlabs/chain-solana";

import {StarknetInitializer} from "@atomiqlabs/chain-starknet";

import {CitreaInitializer} from "@atomiqlabs/chain-evm";



import {BitcoinNetwork, TypedSwapper, SwapperFactory, TypedTokens} from "@atomiqlabs/sdk";

import {SqliteStorageManager, SqliteUnifiedStorage} from "@atomiqlabs/storage-sqlite";



// Define chains you want to support

const chains = [SolanaInitializer, StarknetInitializer, CitreaInitializer] as const;

type SupportedChains = typeof chains;



// Create the swapper factory

const Factory = new SwapperFactory<SupportedChains>(chains);



// Get the tokens for the supported chains

const Tokens: TypedTokens<SupportedChains> = Factory.Tokens;



// Create one swapper instance for your entire app, and use that instance for all your swaps.

const swapper: TypedSwapper<SupportedChains> = Factory.newSwapper({

  chains: {

    SOLANA: {

      rpcUrl: solanaRpc // Can also pass Connection object

    },

    STARKNET: {

      rpcUrl: starknetRpc // Can also pass Provider object

    },

    CITREA: {

      rpcUrl: citreaRpc, // Can also pass JsonRpcProvider object

    }

  },

  bitcoinNetwork: BitcoinNetwork.MAINNET,

  swapStorage: chainId => new SqliteUnifiedStorage("CHAIN_MAINNET_"+chainId+".sqlite3"),

  chainStorageCtor: name => new SqliteStorageManager("STORE_MAINNET_"+name+".sqlite3"),

});



await swapper.init();
```

info

On testnets, only native tokens are generally supported (e.g. SOL, STRK, cBTC).

Set your testnet RPC URLs:

```
const solanaRpc = "https://api.devnet.solana.com";

const starknetRpc = "https://rpc.starknet-testnet.lava.build/";
```

Create a swapper factory with your desired chain support:

```
import {SolanaInitializer} from "@atomiqlabs/chain-solana";

import {StarknetInitializer} from "@atomiqlabs/chain-starknet";



import {BitcoinNetwork, TypedSwapper, SwapperFactory, TypedTokens} from "@atomiqlabs/sdk";

import {SqliteStorageManager, SqliteUnifiedStorage} from "@atomiqlabs/storage-sqlite";



const chains = [SolanaInitializer, StarknetInitializer] as const;

type SupportedChains = typeof chains;



const Factory = new SwapperFactory<SupportedChains>(chains);

const Tokens: TypedTokens<SupportedChains> = Factory.Tokens;



const swapper: TypedSwapper<SupportedChains> = Factory.newSwapper({

  chains: {

    SOLANA: {

      rpcUrl: solanaRpc // Can also pass Connection object

    },

    STARKNET: {

      rpcUrl: starknetRpc // Can also pass Provider object

    }

  },

  bitcoinNetwork: BitcoinNetwork.TESTNET3,

  swapStorage: chainId => new SqliteUnifiedStorage("CHAIN_TESTNET3_"+chainId+".sqlite3"),

  chainStorageCtor: name => new SqliteStorageManager("STORE_TESTNET3_"+name+".sqlite3"),

});



await swapper.init();
```

info

On testnets, only native tokens are generally supported (e.g. SOL, STRK, cBTC).

Set your testnet RPC URLs:

```
const starknetRpc = "https://rpc.starknet-testnet.lava.build/";

const citreaRpc = "https://rpc.testnet.citrea.xyz";
```

Create a swapper factory with your desired chain support:

```
import {StarknetInitializer} from "@atomiqlabs/chain-starknet";

import {CitreaInitializer} from "@atomiqlabs/chain-evm";



import {BitcoinNetwork, TypedSwapper, SwapperFactory, TypedTokens} from "@atomiqlabs/sdk";

import {SqliteStorageManager, SqliteUnifiedStorage} from "@atomiqlabs/storage-sqlite";



const chains = [StarknetInitializer, CitreaInitializer] as const;

type SupportedChains = typeof chains;



const Factory = new SwapperFactory<SupportedChains>(chains);

const Tokens: TypedTokens<SupportedChains> = Factory.Tokens;



const swapper: TypedSwapper<SupportedChains> = Factory.newSwapper({

  chains: {

    STARKNET: {

      rpcUrl: starknetRpc // Can also pass Provider object

    },

    CITREA: {

      rpcUrl: citreaRpc, // Can also pass JsonRpcProvider object

    }

  },

  bitcoinNetwork: BitcoinNetwork.TESTNET4,

  swapStorage: chainId => new SqliteUnifiedStorage("CHAIN_TESTNET4_"+chainId+".sqlite3"),

  chainStorageCtor: name => new SqliteStorageManager("STORE_TESTNET4_"+name+".sqlite3"),

});



await swapper.init();
```

info

Initialize the swapper with `await swapper.init();` once when your app starts. You should create only one swapper instance for your entire app, and use that instance for all your swaps. This checks existing in-progress swaps and does initial LP discovery.

Implement swap recovery — refunds & manual claims

If your app is interrupted mid-swap or the LP fails to deliver, user funds stay locked in the swap escrow until your app explicitly recovers them — this does **not** happen automatically. After `swapper.init()`, always check for and process:

* [**Refundable swaps**](https://docs.atomiq.exchange/sdk-guide/swap-management/refunds.md#get-all-refundable-swaps) — failed Smart Chain → BTC swaps where the user's locked funds must be refunded via `swapper.getRefundableSwaps()`.
* [**Claimable swaps**](https://docs.atomiq.exchange/sdk-guide/swap-management/claiming.md#get-all-claimable-swaps) — swaps where the user still needs to claim funds on the destination chain, retrieved via `swapper.getClaimableSwaps()`.

Run these checks on every app start and periodically while running, otherwise funds can remain locked in swaps indefinitely.

## Setting Up Signers

Atomiq's [`AbstractSigner`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/type-aliases/AbstractSigner) object wraps around the wallet object of the respective chain and makes it easy to approve transactions via it by passing it to the swap methods. You can alternatively skip this step, obtain the transactions required to be signed and [sign & send them manually](https://docs.atomiq.exchange/sdk-guide/advanced/manual-transactions.md).

tip

See [wallets.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/wallets.ts) in the demo repo for a ready-to-use version of the signers below.

### Solana

Using Solana wallet with private key

```
import {Keypair} from "@solana/web3.js";

import {SolanaKeypairWallet, SolanaSigner} from "@atomiqlabs/chain-solana";



// Generate random private key

const solanaKey: Uint8Array = Keypair.generate().secretKey;



// From private key

const solanaSigner = new SolanaSigner(

  new SolanaKeypairWallet(Keypair.fromSecretKey(solanaKey)),

  Keypair.fromSecretKey(solanaKey)

);
```

### Starknet

warning

On Starknet, accounts are smart contracts (account abstraction). An address is derived from the **account class hash + deployment salt + constructor calldata**, not from the private key alone. The same private key therefore produces a **different address for every account implementation**.

Using OpenZeppelin account

`StarknetKeypairWallet` derives its address using its own bundled OpenZeppelin account class. If you import a private key from another wallet (Argent, Braavos, or a different OpenZeppelin version), you will get a **different, empty address**. Your funds still sit at the address derived by the original wallet.

```
import {StarknetSigner, StarknetKeypairWallet} from "@atomiqlabs/chain-starknet";

import {RpcProvider} from "starknet";



// Generate random private key

const starknetKey: string = StarknetKeypairWallet.generateRandomPrivateKey();



// From private key, this uses a simple openzeppelin account class with a single public key.

const starknetProvider = new RpcProvider({nodeUrl: starknetRpc});

const starknetSigner = new StarknetSigner(

  new StarknetKeypairWallet(starknetProvider, starknetKey)

);
```

The account contract does not need to be deployed up front: the SDK automatically deploys it with the first swap transaction (paying the deployment fee from the account's balance).

Using an existing Starknet account

If you already have a deployed Starknet account, keep using it: don't pass its private key to `StarknetKeypairWallet`, wrap a plain starknet.js [`Account`](https://starknetjs.com/docs/API/classes/Account) with your **existing address** in `StarknetSigner` instead:

```
import {StarknetSigner} from "@atomiqlabs/chain-starknet";

import {Account, RpcProvider} from "starknet";



const starknetProvider = new RpcProvider({nodeUrl: starknetRpc});

const starknetSigner = new StarknetSigner(

  new Account({

    provider: starknetProvider,

    address: "0x...", // your existing, already-deployed account address

    signer: starknetKey,

    cairoVersion: "1"

  })

);
```

### EVM (Citrea, etc.)

Using an EVM wallet with private key

```
import {BaseWallet, SigningKey, JsonRpcProvider, Wallet} from "ethers";

import {EVMSigner} from "@atomiqlabs/chain-evm";



// Generate random private key

const evmKey: string = Wallet.createRandom().privateKey;



// From private key

const evmProvider = new JsonRpcProvider(citreaRpc);

const wallet = new BaseWallet(new SigningKey(evmKey), evmProvider);

const evmWallet = new EVMSigner(wallet, wallet.address);
```

## Your First Swap

Here's a complete example of a Smart Chain to Bitcoin on-chain swap:

```
import {SwapAmountType} from "@atomiqlabs/sdk";



// Create a swap: SOL to Bitcoin on-chain

const swap = await swapper.swap(

  Tokens.SOLANA.SOL,              // From token

  Tokens.BITCOIN.BTC,             // To Bitcoin on-chain

  "0.0001",                       // Amount of BTC to receive

  SwapAmountType.EXACT_OUT,       // Specify amount in output token

  solanaSigner.getAddress(),      // Source address

  "bc1q..."                       // Bitcoin destination address

);



// Check quote details

console.log("Input:", swap.getInput().toString());

console.log("Output:", swap.getOutput().toString());

console.log("Expires:", new Date(swap.getQuoteExpiry()));



// Execute the swap

const success = await swap.execute(solanaSigner, {

  onSourceTransactionSent: (txId) => console.log("Tx sent:", txId),

  onSwapSettled: (btcTxId) => console.log("Bitcoin transaction sent: ", btcTxId)

});



// Handle failure

if (!success) {

  await swap.refund(solanaSigner);

}
```

## Next Steps

### Creating Quotes

Guides you through creating swap quotes.

**[Creating Quotes →](https://docs.atomiq.exchange/sdk-guide/quick-start/creating-quotes.md)**

***

### Swap Management

Retrieve & query past swaps, handle swaps which require manual refund or settlement fallback.

**[Swap Management →](https://docs.atomiq.exchange/sdk-guide/swap-management.md)**

***

### Configuration

Explains swapper configuration parameters passed in the `Factory.newSwapper()` function and debug logging config.

**[Swapper Configuration →](https://docs.atomiq.exchange/sdk-guide/advanced/configuration.md)**

***

### Utilities

SDK-exposed utilities allow you to parse addresses (including LNURL links), fetch token balances, list supported tokens and swap types.

**[Utilities →](https://docs.atomiq.exchange/sdk-guide/utilities.md)**

***
