The network
Networks and assets
Seven public test networks are supported today. All are testnets — the assets on them have no value.
| Network | Code | Ledger code | Native asset | Tokens |
|---|---|---|---|---|
| Ethereum Sepolia | sep | SEP | ETH | from the token registry — currently a Varnir-deployed test USDT |
| Tron Nile | niles | TRX-NILE | TRX | from the token registry — currently a Varnir-deployed test USDT |
| BNB Smart Chain testnet | tbnb | tBNB | BNB (test) | from the token registry |
| Base Sepolia | base-sepolia | base-sepolia | ETH | from the token registry — a Varnir test USDT (USDT), a Varnir test USDC (VUSDC) and Circle's testnet USDC (USDC), all 6 decimals |
| Arbitrum Sepolia | arb-sepolia | arb-sepolia | ETH | from the token registry — a Varnir test USDT (USDT), a Varnir test USDC (VUSDC) and Circle's testnet USDC (USDC), all 6 decimals |
| Polygon Amoy | polygon-amoy | polygon-amoy | POL | from the token registry — a Varnir test USDT (USDT), a Varnir test USDC (VUSDC) and Circle's testnet USDC (USDC), all 6 decimals |
| Bitcoin Signet | btc-signet | btc-signet | BTC (test) | none — native BTC only |
Bitcoin Testnet4 (btc-testnet4) is on hold since 2026-10-02: most of its
blocks are mined by bots and carry no transactions, so payments can wait a
long time to confirm. Varnir's apps no longer offer it for deposits or sends;
Bitcoin Signet is the Bitcoin test network to use.
The code is what you pass to the SDK (VarnirChain.Sepolia,
VarnirChain.TronNile, VarnirChain.BnbTestnet, VarnirChain.BaseSepolia, VarnirChain.ArbSepolia, VarnirChain.PolygonAmoy). The ledger code is how the
network is spelled at rest on the ledger and inside signed transactions.
@varnir/network-codes converts between the spellings, and also knows the
mainnet and Solana codes a token registry can name (eth, trx, shasta,
bnb, sol, soldev); those networks are not live.
The Phase C chains: one spelling
The codes of Base Sepolia, Arbitrum Sepolia and Polygon Amoy are the same everywhere: in the SDK, on the
ledger and in the API. They have no second spelling. Their tokens are a Varnir test USDT
(USDT), a Varnir test USDC (VUSDC) and Circle's own testnet USDC (USDC),
all with 6 decimals. BNB Smart Chain testnet's USDT has 18 decimals, like
Binance-Peg USDT, so always read decimals from the token registry rather than
assuming them from a ticker.
Which tokens a network credits is on-ledger state, not a fixed list. Ask the network rather than hardcoding it:
await client.listTokens();
// [{network: 'SEP', address: '4ea636f3…', symbol: 'USDT', decimals: 6,
// chargeable: true, enabled: true}, …]
Deposit records carry the network as TRX-NILE, while wallet and pool records
use niles. Both refer to the same network — they come from different code
paths (the L1 deposit monitors versus the wallet pool). If you match on the
network string anywhere, normalise both spellings, case-insensitively.
Wallet claiming is addressed by network code plus pool symbol — for Tron
Nile that is niles / trx, for Ethereum Sepolia sep / eth, and for BNB
Smart Chain testnet tbnb / eth, for Base Sepolia base-sepolia / eth,
for Arbitrum Sepolia arb-sepolia / eth, and for Polygon Amoy polygon-amoy / eth (an EVM chain shares Ethereum's
key type).
The SDK's getWallet fills these in for you.
An EVM deposit address is a 0x address, and the same string is valid on
every EVM chain. Varnir credits a deposit only on the network the wallet was
issued for: Base Sepolia ETH sent to a Sepolia address is not credited. It is
not lost (Varnir holds that key on every chain), but recovering it is a manual
support case.
Deposits confirm faster than withdrawals — on purpose
A deposit is credited at a shallower block depth than a withdrawal is marked confirmed, even though both are watching the same L1 transaction. So for a Varnir→Varnir transfer routed over L1, the recipient sees the deposit credited before the sender sees the withdrawal confirmed. This is deliberate, not a bug — the two sides guard against different risks:
| Deposit (credit the recipient) | Withdrawal (confirm the sender's payment) | |
|---|---|---|
| Ethereum Sepolia | 3 confirmations | 12 confirmations |
| BNB Smart Chain testnet | 6 | 12 |
| Tron Nile | shallow | 19 |
| Base Sepolia | the safe block | the safe block |
| Arbitrum Sepolia | the safe block | the safe block |
| Polygon Amoy | 32 blocks below the finalized block | 32 blocks below the finalized block |
| Bitcoin Signet | 3 confirmations | 3 confirmations |
- Crediting a deposit favours speed and accepts a small reorg risk: a shallow credit that later reorged out would have over-credited an L2 balance, so the depth is kept low enough for a responsive experience.
- Confirming a withdrawal is the higher-stakes call — it is Varnir declaring its own outbound payment irreversibly done (versus refunding or retrying it). Marking it confirmed too early and then having it reorg would mean Varnir believes it paid when it did not, so it waits deeper and re-checks on a slower cadence than the continuously-running deposit scanner.
Both depths are per-network and environment-tunable; the values above are the current testnet defaults. Nothing about this affects safety — balances are only ever moved once, checked fresh at commit (see the exchange's balance protection) — it only means the recipient hears the good news first.
How a token is identified
A token is identified by its L1 contract address, not by its ticker.
An L2 balance is stored under that address, normalised to lowercase hex with no
0x or 41 prefix — so Sepolia's test USDT is
4ea636f3ba863577dcced852b749f618951f2cca. The chain's own native asset uses
the literal key #native.
A ticker is not an identity. "USDT" named one contract on Sepolia and a different one on Nile, and two tokens on one network may legitimately share a symbol — so matching on one silently conflates assets. Match on the address; show the symbol.
const balances = await client.getBalances();
// [{chain: 'sep', token: '4ea636f3…', symbol: 'USDT',
// raw: '12500000', amount: '12.5', decimals: 6}]
token is the address (null for the native asset). symbol and decimals
are resolved from the registry, and amount is raw scaled by them. If the
registry does not describe a token, amount and decimals come back null
rather than guessed — raw is always exact.
Deposits recorded before this change carry a symbol instead of an address, and
Tron USDT appears as Tether rather than USDT. Varnir still reads those, but
anything new is address-keyed.
Currencies on an invoice
Invoices are still priced in one of exactly two currencies, native or USDT
— they have not moved to address keys yet.
Ledger nodes
The settlement layer is a four-node Activeledger testnet. Transactions are POSTed directly to a node — your signing code talks to the chain, not through a Varnir API:
https://node1-testnet.varnir.site
https://node2-testnet.varnir.site
https://node3-testnet.varnir.site
https://node4-testnet.varnir.site
Any node accepts any transaction. An integration should try them in turn and move on when one is unreachable, which is exactly what the SDK example does.
Scanner
The public scanner is at scanner.varnir.site.
It indexes ledger transactions, L1 deposits on all six networks, wallets and
identities, and hosts the browser wallet at
/wallet.
The same host serves the HTTP API described in the API reference.
Contract identities
The on-ledger contracts an integration signs against, in the varnir
namespace:
| Contract | Id |
|---|---|
| Onboard | 79bfad7705d57b81e4e15cbce425686d97e240972e5d319058c675685bc4dc98 |
| AssignOwner | fd9e832e1169d5fb1aa7a591cdbd8bb74dff7addde4e26176bf21f6569ef8e64 |
These are testnet ids and will change if the contracts are redeployed. Read them from configuration rather than hardcoding them in anything long-lived.