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
fuelsumbrella 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-fuelsalso installs the Fuel toolchain (forcandfuel-core) for you viafuelup, pinned to thetestnetchannel. 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
forcorfuel-coreto 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 withfuelup.
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
forcandfuel-corerelease. Mismatched versions are the most common source of confusing errors. Runnpx fuels versionsto 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
Provideris 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
fetchimplementation 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 likelock()work, but any method that touches the network —getCoins(),transfer()— throwsProvider not setuntil 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 generatedMyScriptLoaderclass. 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
consolidateCoinsruns multiple transactions when needed.mode: 'parallel'is faster;'sequential'is gentler on the node. The defaultoutputNumis1.
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-pare mutually exclusive. Combining them fails witherror: 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 devrebuilds and regenerates types → your TypeScript instantly sees the new ABI. Runfuels nodeseparately 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
+/-. UseBNmethods (add,sub,mul,div) to avoid precision loss. Convert tonumberonly 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
usingkeyword (TypeScript 5.2+ explicit resource management) automatically shuts the node down when the test scope ends.launchTestNodeacceptscontractsConfigsto auto-deploy contracts and gives you pre-fundedwalletsand a readyprovider.
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_REVERTEDand surfaces any decodedrequire/assertlog. Read thelogs/groupedLogson 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