Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

The Fuel TypeScript SDK (fuels) — User Guide

Generated June 2026 from FuelLabs/fuels-ts at b3f37c9

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

fuels is the official TypeScript SDK for the Fuel blockchain. It is the toolkit you use from Node.js, the browser, or Bun to talk to a Fuel network: create wallets, deploy and call Sway smart contracts, run scripts and predicates, build and send transactions, and generate fully-typed TypeScript bindings from your Sway program ABIs.

If you have used ethers.js or web3.js on Ethereum, fuels fills the same role on Fuel — with first-class TypeScript types, a UTXO-based transaction model, and a command-line tool (fuels) that wires your Sway programs into your TypeScript project.

This guide shows you how to use the SDK. For architecture, the full configuration reference, internals, and contribution details, see the companion Admin Guide.

Note

Everything in this guide comes from a single dependency: the fuels umbrella package. It re-exports every individual @fuel-ts/* package (@fuel-ts/account, @fuel-ts/contract, @fuel-ts/abi-coder, and so on), so you almost always import from 'fuels' directly.


1. Quick Start

The fastest way to a working full-stack Fuel dApp is the create-fuels scaffolding tool. It creates a project that already contains Sway programs, generated TypeScript types, and a front-end wired to the SDK.

npm

npm create fuels@latest

pnpm

pnpm create fuels@latest

bun

bun create fuels@latest

You will be prompted for a project name. After the files are copied and dependencies are installed, the tool prints next steps:

⚡️ Success! Created a fullstack Fuel dapp at my-fuel-project

To get started:

- cd into the project directory: cd my-fuel-project
- Start a local Fuel dev server: npm run fuels:dev
- Run the frontend: npm run dev
cd my-fuel-project
npm run fuels:dev   # builds Sway programs, starts a local node, hot-reloads
npm run dev         # starts the front-end

Tip

create-fuels also installs the Fuel toolchain (forc and fuel-core) for you via fuelup, pinned to the testnet channel. You do not need to install them separately when you scaffold this way.

Minimal SDK usage

If you only want to talk to an existing network from a plain script, you need just three things — a Provider, a Wallet, and a balance query:

import { Provider, Wallet } from 'fuels';

const provider = new Provider('https://testnet.fuel.network/v1/graphql');
const wallet = Wallet.fromAddress(
  '0x767caf5b08eba21c561078a4d5be09bbd7f16b9eca22699a61f1edd9e456126f',
  provider
);

const { balances } = await wallet.getBalances();
console.log('Balances', balances);
Balances [ { assetId: '0x...', amount: <BN: 0x...> } ]

2. Installation

Requirements

Requirement Version
Node.js ^20, ^22, or ^24
Package manager npm, pnpm ^9.4, yarn, or bun
forc (Sway compiler) Required only to build Sway programs
fuel-core (node binary) Required only to run a local node

Important

You do not need forc or fuel-core to use the SDK against a remote network (testnet/mainnet). You only need them locally when you compile Sway programs or run a local development node. Install both with fuelup.

Add the SDK to an existing project

npm

npm install fuels

pnpm

pnpm add fuels

yarn

yarn add fuels

bun

bun add fuels

The package ships CommonJS, ESM, and browser builds, so it works in Node.js, bundlers (Vite, Next.js, Webpack), and the browser without extra configuration.

Note

The SDK is versioned in lock-step with a specific forc and fuel-core release. Mismatched versions are the most common source of confusing errors. Run npx fuels versions to check compatibility (see §12).


3. Core Concepts

A few Fuel-specific ideas make the rest of this guide click. Read this once; you can skip back later.

The UTXO model. Fuel does not track an account "balance" as a single number. Instead, every wallet owns a set of discrete coins (UTXOs — unspent transaction outputs), each with an amount and an asset ID. A transfer spends one or more coins and produces new ones (including a "change" coin back to you). The SDK handles coin selection automatically, but the model explains behaviors like the "too many coins" limit covered in Troubleshooting.

Assets are first-class. Every coin has an assetId. The network's fee-paying asset is the base asset; fetch its ID with provider.getBaseAssetId(). Never hard-code it — it differs per network.

Programs come in four flavors, all written in Sway and compiled by forc:

Program What it is SDK class
Contract Stateful, deployed once, called by ID Contract / ContractFactory
Script Stateless executable logic run inside one transaction Script
Predicate A pure function guarding an address; returns true/false to authorize spends Predicate
Library Reusable Sway code (not deployable on its own) —

Typegen bindings. You rarely hand-write ABIs. The fuels CLI reads your compiled program's ABI JSON and generates typed classes — MyContract, MyContractFactory, MyScript, MyPredicate — so calls like contract.functions.transfer(...) are fully type-checked.


4. Connecting to a Network — the Provider

The Provider is your connection to a Fuel node. It is read-only by itself (it does not hold keys) and is required by almost everything else.

import { Provider } from 'fuels';

// Pick the network you want to talk to:
const local = new Provider('http://127.0.0.1:4000/v1/graphql');
const testnet = new Provider('https://testnet.fuel.network/v1/graphql');
const mainnet = new Provider('https://mainnet.fuel.network/v1/graphql');

Note

Constructing a Provider is synchronous, but it fetches and caches chain information on first use. Network details (chain ID, base asset, gas configuration) are then available without extra round-trips.

Querying the network

const provider = new Provider('https://mainnet.fuel.network/v1/graphql');

const baseAssetId = await provider.getBaseAssetId();
const chainId = await provider.getChainId();
const gasConfig = await provider.getGasConfig();
const { consensusParameters } = await provider.getChain();
Method Returns
getBaseAssetId() The fee-paying asset ID for this network
getChainId() Numeric chain ID
getChain() Full chain info, including consensusParameters
getGasConfig() Gas price factors and limits
getCoins(address, assetId?, paginationArgs?) A wallet's coins (UTXOs), paginated
getBlocks(...) A list of blocks
dryRun(request) Simulate a transaction without submitting it
assembleTx(params) Estimate fees and fund a transaction (see §9)
operations.getChain() / operations.getNodeInfo() Low-level GraphQL operations

Provider options

The second constructor argument tunes networking behavior:

new Provider(NETWORK_URL, {
  // Mutate every outgoing request (e.g. inject auth headers)
  requestMiddleware: (request) => {
    request.credentials = 'omit';
    return request;
  },
  // Abort a request after N milliseconds
  timeout: 3000,
  // Automatic retry policy for failed requests
  retryOptions: { maxRetries: 5, baseDelay: 100, backoff: 'linear' },
  // Cache fetched Coins/Messages for N ms to avoid double-spend races
  resourceCacheTTL: 5000,
});

requestMiddleware may be async, which is handy for fetching credentials before each call:

new Provider(NETWORK_URL, {
  requestMiddleware: async (request) => {
    const credentials = await fetchSomeExternalCredentials();
    request.headers ??= {};
    (request.headers as Record<string, string>).auth = credentials;
    return request;
  },
});

Tip

Pass a custom fetch implementation through the options to integrate with proxies, instrumentation, or testing frameworks.

Paginating results

List queries like getCoins use cursor pagination:

import type { CursorPaginationArgs } from 'fuels';

let args: CursorPaginationArgs = { first: 10 }; // first 10 coins
const { coins, pageInfo } = await provider.getCoins(WALLET_ADDRESS, baseAssetId, args);

if (pageInfo.hasNextPage) {
  args = { after: pageInfo.endCursor, first: 10 }; // next page
  await provider.getCoins(WALLET_ADDRESS, baseAssetId, args);
}

CursorPaginationArgs supports forward (first + after) and backward (last + before) paging. Calling getCoins with no pagination args returns the first 100 coins.


5. Wallets & Accounts

A wallet signs transactions and pays fees. The SDK distinguishes two states:

  • WalletUnlocked — holds a private key and can sign and send transactions.
  • WalletLocked — knows only an address; can read balances but cannot sign.

You create wallets through the Wallet factory.

import { Wallet } from 'fuels';
import type { WalletLocked, WalletUnlocked } from 'fuels';

// Generate a brand-new random wallet
const fresh: WalletUnlocked = Wallet.generate({ provider });

// From a known private key
const fromKey = Wallet.fromPrivateKey('0x...', provider);

// From just an address (read-only)
const readonly: WalletLocked = Wallet.fromAddress('0x...', provider);

Locking and unlocking

const unlocked = Wallet.generate({ provider });
const privateKey = unlocked.privateKey;

// Lock it (drop the key, keep the address)
const locked: WalletLocked = unlocked.lock();

// Unlock again with the key
const reUnlocked: WalletUnlocked = locked.unlock(privateKey);

Note

A wallet can exist without a provider (Wallet.generate() with no args). Key-only operations like lock() work, but any method that touches the network — getCoins(), transfer() — throws Provider not set until you attach one.

Checking balances

import type { BN } from 'fuels';

// One asset — returns a BigNumber (BN)
const balance: BN = await wallet.getBalance(await provider.getBaseAssetId());

// All assets at once
const { balances } = await wallet.getBalances();

Amounts are always BN (big-number) instances — never plain JavaScript numbers — to safely represent on-chain u64/u256 values.

Encrypted JSON wallets

You can serialize a wallet to a password-encrypted JSON keystore and restore it later:

// Encrypt
const jsonWallet = await Wallet.generate({ provider }).encrypt('my-password');
// e.g. fs.writeFileSync('secure-path/my-wallet.json', jsonWallet);

// Decrypt
const wallet = await Wallet.fromEncryptedJson(jsonWallet, 'my-password', provider);

Managing multiple keys — WalletManager

For apps that hold several keys behind one password, WalletManager wraps encrypted vaults:

import { WalletManager } from 'fuels';

const walletManager = new WalletManager();
await walletManager.unlock('my-password');

await walletManager.addVault({
  type: 'privateKey',
  secret: myWallet.privateKey,
  title: 'My first private key vault',
});

const vaults = walletManager.getVaults();
const wallet = walletManager.getWallet(myWallet.address);

await walletManager.lock(); // always lock when done

Useful WalletManager members:

Member Purpose
unlock(password) / lock() Open / close the manager
isLocked Current state
addVault({ type, secret, title? }) Add a privateKey or mnemonic vault
getVaults() / getWallet(address) Enumerate vaults / retrieve a wallet
updatePassphrase(old, new) Rotate the password

Caution

Always call walletManager.lock() once you are finished. Leaving the manager unlocked keeps decrypted secrets in memory.

Connecting browser wallets — Fuel and connectors

In a dApp you usually connect to an installed browser wallet rather than holding keys yourself. The Fuel class is the connector entry point:

import { Fuel } from 'fuels';

const sdk = new Fuel();
await sdk.init(); // await init to avoid connector race conditions

You can pass connectors and storage:

import { Fuel, MemoryStorage } from 'fuels';

const sdk = await new Fuel({
  connectors: defaultConnectors({ devMode: true }),
  storage: new MemoryStorage(),
}).init();

For React apps, the @fuels/react and @fuels/connectors packages provide hooks (useConnectUI, useIsConnected, useNetwork) and a FuelProvider. To write a custom connector, extend FuelConnector and emit the standard events (accounts, connection, currentAccount, networks, assets, …).


6. Contracts

Contracts are stateful programs deployed to the network. After generating types (see §11), you get a MyContractFactory (for deploying) and a MyContract (for interacting).

Deploying a contract

import { Provider, Wallet } from 'fuels';
import { EchoValuesFactory } from './typegend';

const provider = new Provider(LOCAL_NETWORK_URL);
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);

const deploy = await EchoValuesFactory.deploy(wallet);
const { contract } = await deploy.waitForResult();

The two-step deploy() → waitForResult() pattern recurs throughout the SDK: the first call submits, the second waits for the network to finalize and returns the result.

Calling contract methods

Every ABI function is exposed under contract.functions:

// State-changing call (submitted as a transaction)
const call = await contract.functions.return_input(1337).call();
const { value } = await call.waitForResult();

// Read-only — `.get()` runs the function without spending gas
const { value: count } = await contract.functions.get_count().get();

// Dry-run / simulate — execute against current state without submitting
const res = await contract.functions.echo_u8(10).simulate();
const sim = await contract.functions.echo_u8(10).dryRun();
Terminator Submits a tx? Use it for
.call() Yes State changes; returns { waitForResult }
.get() No Reading state cheaply
.simulate() No Validating a call against funded state
.dryRun() No Validating a call without resources

Connecting to an already-deployed contract

If you have a contract's ID and ABI, instantiate Contract directly — no redeploy needed:

import { Contract } from 'fuels';
import { EchoValues } from './typegend';

const contract = new Contract(contractId, EchoValues.abi, wallet);
const { value } = await contract.functions.echo_u8(10).simulate();

Configurable constants

Sway programs can expose configurables — constants you override at deploy time:

const deploy = await EchoConfigurablesFactory.deploy(wallet, {
  configurableConstants: { age: 10 },
});
const { contract } = await deploy.waitForResult();

const { value: [age, tag] } = await contract.functions.echo_configurables().get();
// age === 10 (overridden); tag === 'fuel' (default)

Warning

Configurable values are validated against the Sway type. Supplying an incomplete struct throws "Error setting configurable constants on contract: Invalid struct … Field … not present."

Forwarding assets and call parameters

Use .callParams() to forward coins into a contract call, and .txParams() to set transaction parameters:

const { waitForResult } = await contract.functions
  .transfer(amountToTransfer, asset, recipient.address.toB256())
  .callParams({ forward: [amountToForward, baseAssetId] })
  .call();

await waitForResult();
const contractBalance = await contract.getBalance(baseAssetId);

Reading logs

Sway log statements surface as decoded values on the call result:

const { waitForResult } = await contract.functions.log_simple('ContractA').call();
const { logs } = await waitForResult();
// logs === ['ContractA']

For calls spanning multiple contracts, groupedLogs keys the logs by contract ID:

const { groupedLogs } = await waitForResult();
// { [contractA.id.toB256()]: ['ContractA'], [contractB.id.toB256()]: ['ContractB'] }

Multi-calls

Batch several functions into one transaction with multiCall:

const { waitForResult } = await contractA
  .multiCall([
    contractA.functions.log_simple('Contract1'),
    contractB.functions.log_simple('Contract2'),
  ])
  .call();

Inter-contract calls

When a contract calls another, register the external contract(s) with .addContracts() so the SDK includes the right inputs:

const call = await tokenDepositor.functions
  .deposit_to_simple_token(simpleToken.id.toB256(), 70)
  .addContracts([simpleToken])
  .call();
await call.waitForResult();

Upgradeable (proxy) contracts

The SDK ships a ready-made SRC-14 owned-proxy contract (Src14OwnedProxy / Src14OwnedProxyFactory) for upgradeable deployments. The pattern: deploy your implementation, deploy the proxy with the implementation as its target, then always interact through the proxy ID (which stays stable across upgrades).

import { Src14OwnedProxy, Src14OwnedProxyFactory } from 'fuels';

// Combine implementation + proxy storage slots
const storageSlots = counterContractFactory.storageSlots.concat(
  Src14OwnedProxy.storageSlots
);

const proxyDeploy = await new Src14OwnedProxyFactory(wallet).deploy({
  storageSlots,
  configurableConstants: {
    INITIAL_TARGET: { bits: counterContract.id.toB256() },
    INITIAL_OWNER: { Initialized: { Address: { bits: wallet.address.toB256() } } },
  },
});
const { contract: proxyContract } = await proxyDeploy.waitForResult();
await (await proxyContract.functions.initialize_proxy().call()).waitForResult();

// Interact via the proxy ID, using your typed implementation class
const proxied = new Counter(proxyContract.id, wallet);
await (await proxied.functions.increment_count(1).call()).waitForResult();

To upgrade, deploy a new implementation and point the proxy at it with set_proxy_target. Existing code keeps using the same proxy ID.


7. Scripts

A script is stateless logic executed inside a single transaction — ideal for orchestrating several actions atomically. After typegen you get a MyScript class.

import { ScriptMainArgs } from './typegend';

const script = new ScriptMainArgs(wallet);
const { waitForResult } = await script.functions.main(3).call();
const { value, logs } = await waitForResult();

Scripts support configurables, just like contracts:

import { Script } from 'fuels';
import { ScriptSum } from './typegend/scripts';

const script = new Script(ScriptSum.bytecode, ScriptSum.abi, wallet);
script.setConfigurableConstants({ AMOUNT: 81 });

const { waitForResult } = await script.functions.main(10).call();
const { value } = await waitForResult(); // 10 + 81

You can also fetch the underlying transaction request before sending — useful for adding signatures or inspecting the tx ID:

const tx = script.functions.main(argument);
tx.callParams({ gasLimit: 7500 });
const txRequest = await tx.getTransactionRequest();
const txId = txRequest.getTransactionId(await provider.getChainId());

Tip

Scripts can be deployed as loader bytecode with fuels deploy, then instantiated via the generated MyScriptLoader class. This keeps the on-chain blob reusable and your transactions small.


8. Predicates

A predicate is a pure, stateless Sway program that guards an address. Funds sent to a predicate's address can only be spent if the predicate returns true for the provided data — no private key involved. They power escrow, multisig, and conditional-spend patterns.

import { ConfigurablePin } from './typegend';

// Instantiate with the data the predicate expects
const predicate = new ConfigurablePin({ provider, data: [1337] });

// Fund the predicate's address
const fundTx = await sender.transfer(predicate.address, 300_000, assetId);
await fundTx.waitForResult();

// Spend from it — the predicate validates the transaction
const transferTx = await predicate.transfer(receiver.address, 1000, assetId);
const { isStatusSuccess } = await transferTx.waitForResult();

Predicates can carry configurable constants and be deployed as reusable loader bytecode, exactly like scripts (ConfigurablePinLoader). For full control, build the transaction by hand and let the predicate pay:

import { ScriptTransactionRequest } from 'fuels';

const request = new ScriptTransactionRequest();
request.addCoinOutput(receiver.address, amountToReceiver, assetId);

const { assembledRequest } = await provider.assembleTx({
  request,
  feePayerAccount: predicate,
  accountCoinQuantities: [
    { amount: amountToReceiver, assetId, account: predicate, changeOutputAccount: predicate },
  ],
});

const tx = await predicate.sendTransaction(assembledRequest);
await tx.waitForResult();

9. Building Transactions — the UTXO model

For everyday transfers you never touch a transaction object. For advanced flows you build one explicitly.

Simple transfers

// Send 100 of an asset
const tx = await sender.transfer(receiver.address, 100, assetId);
await tx.waitForResult();

Batch transfers

Send to many recipients (or split your own coins) in one transaction:

const transfers = [
  { amount: 200, assetId: baseAssetId, destination: walletA.address },
  { amount: 200, assetId: baseAssetId, destination: walletB.address },
];
const batchTx = await wallet.batchTransfer(transfers);
await batchTx.waitForResult();

Transaction parameters

TxParams lets you control fees and lifetime. Apply it with .txParams() on a call, or pass it to a ScriptTransactionRequest constructor.

import type { TxParams } from 'fuels';
import { bn } from 'fuels';

const txParams: TxParams = {
  gasLimit: bn(70935),    // max gas the script may consume
  maxFee: bn(69242),      // max fee you will pay
  tip: bn(100),           // optional priority tip
  maturity: 1,            // earliest block the tx is valid
  witnessLimit: bn(5000), // max size of witness data
  expiration: 200,        // latest block the tx is valid
};

const { waitForResult } = await contract.functions
  .increment_count(15)
  .txParams(txParams)
  .call();

Funding and estimating with assembleTx

provider.assembleTx() is the modern way to estimate fees and select coins for a hand-built request. You give it the request, a fee payer, and the coin quantities each account must supply; it returns a fully-funded assembledRequest.

const request = new ScriptTransactionRequest({ script: ScriptTransferToContract.bytecode });
request
  .setData(ScriptTransferToContract.abi, scriptArguments)
  .addContractInputAndOutput(contract.id);

const { assembledRequest } = await provider.assembleTx({
  request,
  feePayerAccount: wallet,
  accountCoinQuantities: [
    { amount: 1000, assetId: ASSET_A, account: wallet, changeOutputAccount: wallet },
  ],
});

const tx = await wallet.sendTransaction(assembledRequest);
await tx.waitForResult();

Working with UTXOs directly

Because balances are sets of coins, you sometimes need to reshape them.

Consolidate many small coins into fewer (the fix for the "too many coins" limit):

const result = await wallet.consolidateCoins({
  assetId: baseAssetId,
  mode: 'parallel',  // or 'sequential'
  outputNum: 1,      // how many output coins to leave
});
console.log('Successful:', result.txResponses, 'Failed:', result.errors);

Split one large coin into several:

const splits = new Array(5).fill({ amount: 200, assetId: baseAssetId, destination: dest.address });
const batchTx = await wallet.batchTransfer(splits);
await batchTx.waitForResult();

Note

consolidateCoins runs multiple transactions when needed. mode: 'parallel' is faster; 'sequential' is gentler on the node. The default outputNum is 1.

Inspecting the chain's limits

const { maxInputs, maxOutputs } = (await provider.getChain())
  .consensusParameters.txParameters;

10. Signing transactions

When you need a signature on a custom transaction (for example, a script that verifies a signature on-chain):

const scope = script.functions.main(signer.address.toB256(), 0);
const request = await scope.getTransactionRequest();

let signature = await signer.signTransaction(request);
request.addWitness(signature);

const { assembledRequest } = await provider.assembleTx({ request, feePayerAccount: signer });

// assembleTx may modify the request, so re-sign and update the witness
signature = await signer.signTransaction(assembledRequest);
assembledRequest.updateWitness(0, signature);

const { waitForResult } = await scope.call({ skipAssembleTx: true });
const { transactionResult } = await waitForResult();

11. Generating TypeScript types (typegen)

Typegen turns a compiled Sway program's ABI JSON into typed TypeScript classes. You normally run it through the fuels CLI, which wraps the underlying fuels-typegen tool.

# Contracts (the default)
npx fuels typegen -i ./out/*-abi.json -o ./src/sway-api -c

# Scripts
npx fuels typegen -i ./out/my-script-abi.json -o ./src/sway-api -s

# Predicates
npx fuels typegen -i ./out/my-predicate-abi.json -o ./src/sway-api -p
Flag Meaning
-i, --inputs <path|glob...> ABI JSON file(s) — globs allowed (required)
-o, --output <dir> Output directory for generated files (required)
-c, --contract Generate contract types (default)
-s, --script Generate script types
-p, --predicate Generate predicate types
-S, --silent Suppress output messages

Caution

-c, -s, and -p are mutually exclusive. Combining them fails with error: option '-c, --contract' cannot be used with option '-s, --script'.

The generated files give you, per program: a typed program class (MyContract), a factory (MyContractFactory) with deploy() and storageSlots, a *Loader variant for deployed scripts/predicates, plus a contract-ids.json you import for addresses. Using them:

import { Sample } from './sway-programs-api';
import contractsIds from './sway-programs-api/contract-ids.json';

const contract = new Sample(contractsIds.sample, wallet);
const { value } = await contract.functions.return_input(1337).dryRun();

12. The fuels CLI

The fuels command orchestrates the Sway-to-TypeScript workflow. Install it via the fuels package; run it with npx fuels <command> (or your scaffolded fuels:dev script).

Global options

Option Effect
-D, --debug Enable verbose logging
-S, --silent Omit output messages
-v, --version Print the version
-h, --help Show help

Commands

Command Purpose
fuels init Create a fuels.config.ts file
fuels build Build Sway programs and generate TypeScript
fuels deploy Deploy contracts/scripts/predicates to the network
fuels dev Build + deploy + watch with hot-reload
fuels node Start a local fuel-core node from your config
fuels typegen Generate types from ABI JSON (see §11)
fuels versions Check for version incompatibilities

fuels init

Generates a config file. You must point it at your Sway programs, either as a Forc workspace or as individual paths, and give it an output directory:

npx fuels init --path . -w ./sway-programs -o ./src/sway-api
Option Meaning
--path <path> Project root (defaults to current directory)
-w, --workspace <path> Path to a Forc workspace
-c, --contracts [paths...] Individual contract paths (conflicts with --workspace)
-s, --scripts [paths...] Individual script paths (conflicts with --workspace)
-p, --predicates [paths...] Individual predicate paths (conflicts with --workspace)
-o, --output <path> TypeScript output directory (required)
--forc-path <path> Path to the forc binary
--fuel-core-path <path> Path to the fuel-core binary
--auto-start-fuel-core Auto-start a node during dev
--fuel-core-port <port> Port for the local node

fuels build

npx fuels build            # compile Sway + generate types
npx fuels build --deploy   # also deploy (auto-starts a node if needed)

fuels dev

The everyday loop. It builds, deploys, and watches your fuels.config.ts, Forc workspace *.sw/*.toml files, and snapshot dir — rebuilding and redeploying on change.

npx fuels dev

Tip

The development workflow is: edit Sway → fuels dev rebuilds and regenerates types → your TypeScript instantly sees the new ABI. Run fuels node separately if you only want a local node without the build/deploy loop.

The configuration file that drives these commands is documented in full in the Admin Guide (§ Configuration Mastery). The everyday essentials: output (required), one of workspace/contracts/scripts/predicates, and autoStartFuelCore (default true).


13. Numbers and the BN type

On-chain integers can exceed JavaScript's safe range, so the SDK uses a big-number type, BN, for all amounts.

import { bn, BN } from 'fuels';

const a = bn(70935);          // from a number
const b = bn('0x2710', 'hex'); // from a hex string
const sum: BN = a.add(b);

sum.toNumber();   // convert back (throws if too big)
sum.toString();   // decimal string
sum.toHex();      // hex string

Warning

Never do arithmetic on raw balances with +/-. Use BN methods (add, sub, mul, div) to avoid precision loss. Convert to number only for display, and only when you know the value is small.

Other common utilities exported from fuels:

Helper Purpose
Address Parse/convert Fuel addresses (toB256(), toAddress())
hexlify / arrayify Convert between hex strings and byte arrays
concat Concatenate byte arrays
toHex Convert a value to a hex string
ZeroBytes32 The all-zero 32-byte constant
Interface Encode/decode against an ABI directly

14. Testing your integration

The SDK includes a test helper, exported from fuels/test-utils, that spins up a real ephemeral fuel-core node with funded wallets:

import { launchTestNode } from 'fuels/test-utils';
import { SampleFactory } from './sway-programs-api';

using launched = await launchTestNode();
const { wallets: [wallet] } = launched;

const deploy = await SampleFactory.deploy(wallet);
const { contract } = await deploy.waitForResult();

const { value } = await (await contract.functions.return_input(1337).call()).waitForResult();

Note

The using keyword (TypeScript 5.2+ explicit resource management) automatically shuts the node down when the test scope ends. launchTestNode accepts contractsConfigs to auto-deploy contracts and gives you pre-funded wallets and a ready provider.

fuels/test-utils also exports helpers like expectToThrowFuelError (assert a specific FuelError) and asset constants (ASSET_A, ASSET_B).


15. Troubleshooting

Every error thrown by the SDK is a FuelError carrying a stable code (a member of the ErrorCode enum). Catch and branch on the code rather than the message:

import { FuelError, ErrorCode } from 'fuels';

try {
  await contract.functions.return_input(1337).simulate();
} catch (e) {
  if (e instanceof FuelError && e.code === ErrorCode.INSUFFICIENT_FUNDS_OR_MAX_COINS) {
    // handle insufficient funds
  }
}

Error: not-enough-funds-or-max-coins-reached

Message: "Insufficient funds or too many small value coins. Consider combining UTXOs. For the following asset ID: '0x…'." Cause: The wallet either lacks funds for the amount + fee, or owns more small coins than a transaction may reference (the coin-input limit). This is the same condition behind the community report "No Coins Available or exceeding 255 Coin Limit". Solution: Fund the wallet, or consolidate coins with wallet.consolidateCoins({ assetId }) (see §9). Prevention: Periodically consolidate wallets that receive many small payments.

Error: Provider not set

Cause: You called a network method on a wallet created without a provider. Solution: Pass a Provider when constructing the wallet, or re-create it with Wallet.fromPrivateKey(key, provider).

Error: config-file-not-found — "Config file not found!"

Cause: A fuels CLI command ran where no fuels.config.{ts,js,cjs,mjs} exists. Solution: Run fuels init … first, or --path to the directory that contains the config.

Error: Unable to find the following binaries on the filesystem

Cause: forc or fuel-core could not be located at the configured path. Solution: Install the toolchain via fuelup, or set forcPath/fuelCorePath (or --forc-path/--fuel-core-path) to the binaries.

Error: workspace-not-detected — "Forc workspace not detected"

Cause: You passed a single program directory to workspace instead of an actual Forc workspace. Solution: Point workspace at a directory whose Forc.toml declares [workspace], or use contracts/scripts/predicates for individual programs.

Error: setting configurable constants — "Invalid struct … Field … not present."

Cause: A configurableConstants value does not fully match the Sway type. Solution: Provide all required struct fields with correct types.

Error: invalid-configurable-constants, gas-limit-too-low, max-fee-too-low

Cause: Gas limit or max fee below what the transaction needs, or invalid configurables. Solution: Raise gasLimit/maxFee in TxParams, or rely on assembleTx/simulate to estimate them for you.

Tip

When a contract call reverts, the SDK throws with ErrorCode.SCRIPT_REVERTED and surfaces any decoded require/assert log. Read the logs/groupedLogs on the result to see what the program reported.


16. Frequently Asked Questions

Q: Do I have to install forc and fuel-core? Only to compile Sway programs or run a local node. To call an existing contract on testnet/mainnet you need just the fuels npm package.

Q: What is the difference between .call(), .get(), .simulate(), and .dryRun()? .call() submits a real transaction (state changes). .get() reads state cheaply without a transaction. .simulate() and .dryRun() execute a function without submitting — .simulate() requires funded resources, .dryRun() does not.

Q: Why are balances BN objects and not numbers? On-chain integers can exceed JavaScript's safe integer range. BN preserves precision; convert to a number only for display.

Q: What is a predicate, and how is it different from a contract? A predicate is a stateless pure function that authorizes spends from its own address by returning true/false. A contract is stateful, has an ID, and is called to mutate state. Predicates need no private key.

Q: How do I connect a browser wallet like the Fuel Wallet? Use the Fuel class with connectors, or the @fuels/react hooks (useConnectUI, useIsConnected) and FuelProvider in React apps.

Q: My wallet has plenty of funds but transactions fail with a "max coins" error. Why? A transaction can only reference a limited number of coin inputs. Many tiny coins exceed that limit. Run wallet.consolidateCoins({ assetId }).

Q: How do I run automated tests without a public network? Use launchTestNode() from fuels/test-utils. It starts an ephemeral node with funded wallets and tears it down automatically.

Q: Can I use the SDK in the browser? Yes. The package ships a browser build and works with Vite, Next.js, and other bundlers out of the box.

Q: How do I keep the SDK, forc, and fuel-core in sync? Run npx fuels versions. The scaffolded fuel-toolchain file pins compatible forc and fuel-core versions for you.

Q: Where can I find more? The official docs at docs.fuel.network/docs/fuels-ts, the Sway language docs, and the Fuel forum.


17. Quick Reference

Import map (everything comes from fuels)

You need to… Import
Connect to a network Provider
Hold/sign keys Wallet, WalletUnlocked, WalletLocked, WalletManager
Connect a browser wallet Fuel, FuelConnector
Interact with a contract Contract, ContractFactory (or generated classes)
Run a script Script, ScriptTransactionRequest
Guard an address Predicate
Numbers & encoding bn, BN, hexlify, arrayify, Address
Errors FuelError, ErrorCode
Testing launchTestNode (from fuels/test-utils)

Common call pattern

const provider = new Provider(URL);
const wallet = Wallet.fromPrivateKey(KEY, provider);
const { contract } = await (await MyContractFactory.deploy(wallet)).waitForResult();
const { value } = await (await contract.functions.my_method(arg).call()).waitForResult();

CLI cheat-sheet

npm create fuels@latest        # scaffold a full-stack dApp
npx fuels init -o ./src/api    # create fuels.config.ts
npx fuels build                # compile Sway + generate types
npx fuels dev                  # build + deploy + watch
npx fuels deploy               # deploy to the configured network
npx fuels typegen -i ./out/*-abi.json -o ./src/api -c
npx fuels versions             # check version compatibility