Skip to content

Quickstart ​

This page runs a complete round trip in one script: shield, transfer, and unshield. Set network and rpcUrl for your target chain.

Prerequisites ​

  • Installation, including the .npmrc token and the viem peer
  • Node 24 or later
  • An EVM private key funded with the asset to shield, and an RPC endpoint for the chain

One EVM private key is enough. It signs on-chain transactions and also derives the shielded spending key (nsk), so no second secret is needed.

Round trip ​

ts
import { 
connect
,
formatAmount
} from "@lelantos-org/sdk";
// 1. Connect. Resolves the network preset and derives the shielded key. // Nothing is downloaded for proving until the first spend. const
wallet
= await
connect
({
network
: "base",
rpcUrl
: "https://base-rpc.example.com", // public presets ship no RPC endpoint
privateKey
,
});
console
.
log
("shielded address:",
wallet
.
address
);
// 2. Shield 0.5 WETH. Amounts are decimal strings of the token, or values the // SDK returned. The permit and the ERC-20 pull happen in one transaction. const
deposit
= await
wallet
.
deposit
({
asset
: "WETH",
amount
: "0.5" });
// 3. Wait for the relayer to add the note to the tree, then sync. await
wallet
.
awaitDeposit
(
deposit
.
escrow
);
await
wallet
.
sync
();
const
balance
= await
wallet
.
balance
("WETH");
console
.
log
(
formatAmount
(
balance
.
spendable
,
balance
.
asset
, {
symbol
: true })); // "0.5 WETH"
// 4. Transfer to another shielded address. `autoConsolidate` merges notes and // retries rather than throwing when no single spend can cover the amount. await
wallet
.
transfer
({
asset
: "WETH",
amount
: "0.1",
recipient
:
peer
,
autoConsolidate
: true });
// 5. Unshield. `gross` leaves the pool and the protocol fee comes out of it; // pass `net` instead to name what the recipient receives. const
out
= await
wallet
.
withdraw
({
asset
: "WETH",
gross
: "0.2",
recipient
: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
});
console
.
log
("received",
formatAmount
(
out
.
net
.
amount
,
out
.
asset
), "on ladder:",
out
.
onLadder
);
// 6. Release workers. Required in any process that rebuilds the wallet. await
wallet
.
dispose
();

Every snippet on this site is compiled against the SDK at build time. Hover an identifier to see its type.

StepDetail
Networksmainnet, base, and arbitrum are deployed. rpcUrl is required for them. See Networks.
Prover artifactsResolved from @lelantos-org/circuits on Node, on first use. Browsers pass prover: { artifacts } — see Browser usage.
Amounts"0.5" is in token units; a plain bigint does not compile. See Amounts and assets.
SettlementA deposit is spendable only after the relayer adds it to the tree and the wallet syncs. See Deposit.

Asset and amount inputs ​

asset accepts a registry id, an ERC-20 address, or a symbol, and is required on every operation. amount names its unit space by its shape:

ts
import { 
circuitAmount
} from "@lelantos-org/sdk";
// Equivalent, given a registry where asset 1 is USDC at scale 1: await
wallet
.
transfer
({
recipient
:
peer
,
asset
: "USDC",
amount
: "12.50" }); // token units
await
wallet
.
transfer
({
recipient
:
peer
,
asset
: 1n,
amount
: {
baseUnits
: 12_500_000n } }); // base units
await
wallet
.
transfer
({
recipient
:
peer
,
asset
: 1n,
amount
:
circuitAmount
(12_500_000n) }); // circuit units

A symbol shared by two registered assets raises INVALID_ARGUMENT. Use the id or token address in that case. See Amounts and assets.

Handling errors ​

Every method rejects with a WalletError that carries a stable code and a retryable flag. isWalletError(err, code) narrows the error to that code's class, so its fields are typed.

ts
import { 
isWalletError
} from "@lelantos-org/sdk";
try { await
wallet
.
transfer
({
asset
: "WETH",
amount
: "1",
recipient
});
} catch (
err
) {
if (
isWalletError
(
err
, "INSUFFICIENT_COVER")) {
// The balance is spread over more notes than one spend can use.
console
.
log
("merge these first:",
err
.
consolidate
.
map
((
n
) =>
n
.
id
));
} else if (
isWalletError
(
err
, "NOTES_HELD") &&
err
.
retryable
) {
// Notes are leased by another spend in flight; retry shortly. } else { throw
err
;
} }

Pass autoConsolidate: true to merge notes and retry automatically. See Note management and Errors.

Explicit configuration ​

connect() resolves a network preset and fills in defaults. createWallet(keySource, config) from @lelantos-org/sdk/advanced takes an explicit WalletConfig and infers nothing.

ts
import { 
createWallet
} from "@lelantos-org/sdk/advanced";

See Connecting a wallet and Pluggable interfaces.

Next ​