@xyo-network/wallet-xl1-cli
v0.1.22
Published
Standalone XL1 Wallet CLI
Readme
@xyo-network/wallet-xl1-cli
Standalone XL1 wallet CLI for seed phrases, accounts, balances, transfers, payload signing, and wallet-signed JWTs.
Description
@xyo-network/wallet-xl1-cli provides the xl1-wallet command. It is a focused XL1 wallet tool that can create or import wallets, derive accounts, manage network and contact metadata, sign transactions and payloads, send XL1, and mint or verify wallet-backed JWTs.
The Aries CLI uses this same package for aries wallet ..., so the standalone and nested command surfaces stay aligned.
Installation
Prerequisites
- Node.js
>=22.19.0 - A package manager such as
npm,pnpm, oryarn
Global Install
npm install -g @xyo-network/wallet-xl1-cli
xl1-wallet --helpRun From This Repository
pnpm xy compile @xyo-network/wallet-xl1-cli
pnpm xl1-wallet --helpQuick Start
Create a new wallet, derive an account, check its balance, then dry-run a transfer:
xl1-wallet create --label primary
xl1-wallet account derive 0 --label main
xl1-wallet balance 0
xl1-wallet send 0x0000000000000000000000000000000000000000 1 --xl1 --dry-runImport an existing recovery phrase instead:
xl1-wallet import --label primary --phrase "test test test test test test test test test test test junk"
xl1-wallet use <wallet-id>Use the same wallet CLI through Aries:
aries wallet balance 0
aries wallet send 0x0000000000000000000000000000000000000000 1.25 --milli --jsonRequester-scoped Node signing
The supported @xyo-network/wallet-xl1-cli/signing entry exposes public wallet
summaries and short-lived, account-bound signing sessions. Install the package
locally with its @xyo-network/xl1-sdk peer. Consumers select the requester ID,
exact wallet ID, derivation offset, expected account, chain ID, and independently
verified genesis hash. The wallet itself confirms that selection and prompts on
the trusted terminal for its password; this API does not accept a password,
mnemonic, root wallet, or caller-provided signer.
import { listSigningWallets, openWalletSigningSession } from '@xyo-network/wallet-xl1-cli/signing'
const wallets = listSigningWallets()
const session = await openWalletSigningSession(selection)
try {
const snapshot = await session.snapshot()
const signed = await session.signTransaction({ requestId, transaction })
// Persist signed bytes in the participant publication journal before broadcasting.
} finally {
await session.close()
}Here selection is the complete explicit binding and transaction is an SDK
unsigned hydrated transaction. The session signs without publishing bodies or
broadcasting. It supports secp256k1 wallets; post-quantum transaction signing is
not qualified. Sessions expire within five minutes. Lock, close, password
replacement, and wallet replacement revoke authority for new signing.
The wallet persists request intent and encrypted signed results in signing.json
under its existing guarded store lease. Retrying the same request returns the
same bytes. signingOutcome(requestId) recovers a retained result through a fresh
authorized session with the same complete binding. An uncertain intent blocks
new same-account signing; an error, timeout, or 4001 is never proof that no
signature exists. Existing CLI tx sign and send use this same signing ledger,
with publication remaining a separate command step. Legacy offline signing has
an explicitly unasserted genesis scope; it cannot authorize public requester
sessions. This does not reconstruct pre-upgrade or external-key signing history.
Run the isolated packed public import, type, real terminal prompt, and process recovery fixture from the repository root after both wallet package builds:
node packages/wallet-cli/scripts/verifySigningPacked.mjs /absolute/fresh/evidence-directoryThe terminal fixture currently qualifies macOS. It creates only a temporary
public deterministic wallet. Its strict consumer type check uses skipLibCheck;
the full dependency declaration check is retained separately because upstream SDK
declarations currently fail that broader check.
Wallet Storage
Wallet CLI data is stored under ~/.xl1/wallet/cli by default.
| Variable | Purpose |
| --- | --- |
| XL1_WALLET_HOME | Overrides the wallet storage directory. |
| ARIES_WALLET_HOME | Legacy fallback storage override. Used only when XL1_WALLET_HOME is not set. |
| ARIES_WALLET_PASSWORD | Supplies the wallet password. A nonempty value is validated before any cached session is used. For a new store, it sets the initial password. |
| ARIES_WALLET_NON_INTERACTIVE=1 | Refuses terminal password and recovery-phrase prompts. Private-key access requires an unlocked session or ARIES_WALLET_PASSWORD; import requires --phrase-stdin or --phrase. |
Wallet seed phrases are encrypted at rest. Commands that need private key access prompt for the wallet password unless ARIES_WALLET_PASSWORD is set.
unlock stores a short-lived encrypted session so follow-up commands can run without repeatedly prompting:
xl1-wallet unlock --ttl 900
xl1-wallet lockNetworks
The CLI ships with XL1 mainnet and XL1 Sequence testnet network definitions.
| ID | Label | RPC URL |
| --- | --- | --- |
| xl1-mainnet | XL1 (Mainnet) | https://api.chain.xyo.network/rpc |
| xl1-sequence | XL1 Sequence (Testnet) | https://beta.api.chain.xyo.network/rpc |
List and switch networks:
xl1-wallet network list
xl1-wallet network use xl1-sequenceAdd a local or custom network:
xl1-wallet network add local http://127.0.0.1:8080/rpc --label "Local XL1" --chain-id 1234
xl1-wallet network use localAdding a network preserves the current selection and rejects duplicate IDs.
New IDs must be nonblank, contain no control characters, fit within 256 UTF-8
bytes, and not begin with -. Optional labels are preserved exactly and may
contain up to 1024 UTF-8 bytes. Optional chain IDs use the XL1 protocol's
nonempty lowercase hexadecimal form without a 0x prefix; there is no fixed
length requirement. The chain ID is stored metadata: adding a network does not
probe the endpoint or verify its chain.
For endpoints containing path or query credentials, pass the complete URL via private stdin instead of a positional argument. Stdin must contain only the URL as UTF-8, with no trailing newline, and is limited to 16 KiB. URLs must be complete HTTP or HTTPS URLs without controls or fragments. Local HTTP endpoints are supported. URL username/password information is rejected because the RPC fetch transport does not support it.
printf '%s' "$RPC_URL" | xl1-wallet network inspect-add custom \
--rpc-url-stdin --label="Custom XL1" --chain-id=1234 --json
printf '%s' "$RPC_URL" | xl1-wallet network add custom \
--rpc-url-stdin --label="Custom XL1" --chain-id=1234 \
--expected-state="$REVIEWED_STATE_HASH" --jsoninspect-add validates the proposed entry and returns its ID, label, optional
chain ID, current active ID, canonical wallet root, and stateHash. Use that
hash as REVIEWED_STATE_HASH for the corresponding add. Inspection does not
create networks.json; the add writes defaults and the custom entry atomically
under the wallet store lease. If any wallet file changes after review, the
guarded add fails and must be reviewed again. --rpc-url-stdin and a positional
URL are mutually exclusive.
Network list and add support --json. Network summaries and
success messages omit the entire RPC URL, including its hostname. The URL is
stored in the private wallet configuration for RPC use.
Remove a custom network:
xl1-wallet network remove localCommands
Wallets
| Command | Description |
| --- | --- |
| xl1-wallet create | Create a new wallet. |
| xl1-wallet import | Import a recovery phrase. |
| xl1-wallet export | Print a wallet recovery phrase using a password or unlocked session. |
| xl1-wallet list | List wallets in the current wallet home. |
| xl1-wallet use <id> | Set the active wallet. |
| xl1-wallet rename <id> <label> | Rename a wallet. |
| xl1-wallet remove [id] | Authenticate and remove a wallet; --wallet-id targets an exact ID. |
| xl1-wallet reset | Delete known wallet files after confirmation, preserving unrelated files. |
| xl1-wallet unlock | Cache an encrypted wallet session for a limited time. |
| xl1-wallet lock | Clear the cached wallet session. |
| xl1-wallet password change | Change the shared password for every wallet in the store. |
| xl1-wallet lifecycle preview --json | Inspect the canonical store path, state fingerprint, wallets, and known files. |
Examples:
xl1-wallet create
xl1-wallet create --label quant --algorithm ml-dsa-65
xl1-wallet import --label recovery --phrase "..."
xl1-wallet import --label quant-recovery --phrase "..." --algorithm ml-dsa-65
xl1-wallet list
xl1-wallet use <wallet-id>
xl1-wallet export --id <wallet-id>
xl1-wallet export --wallet-id <exact-wallet-id> --jsoncreate and import default to --algorithm secp256k1. Use --algorithm ml-dsa-65 for QuantHDWallet wallets, which derive qm65... bech32m addresses. Wallet-signed JWT commands require secp256k1 because ML-DSA-65 does not yet have a standardized JOSE algorithm.
Wallet labels must be unique, nonblank, and free of NUL characters. Other label text, including surrounding spaces, is preserved. If create is run without --label, the CLI uses the first available wallet# label, starting with wallet0.
Commands accepting a wallet ID or label prefer an exact ID match before looking
for a matching label.
For unattended imports, --phrase-stdin reads raw UTF-8 from standard input
until EOF, up to 16 KiB, and trims surrounding whitespace. It is mutually
exclusive with --phrase. The algorithm, label, and recovery phrase are
validated before the command initializes a password or stores a wallet.
export --wallet-id accepts only an exact stored ID and cannot be combined
with --id. The existing --id option also accepts labels; omitting both
options reveals the active wallet.
create --json and export --json emit only
{id, label, algorithm, mnemonic} to stdout. This output contains the recovery
phrase. import --json emits only {id, label, algorithm} and does not include
the phrase. Text output remains the default.
Password change requires the current password even with an unlocked session.
ARIES_WALLET_PASSWORD supplies only that current password. For automation,
password change --new-password-stdin --json reads the new password as raw UTF-8
until EOF, preserving all whitespace, with a 4096-byte limit and no NUL
characters. Its JSON result is {changed: true}. Interactive mode asks for and
confirms the new password separately. Rotation clears the cached session.
remove --wallet-id=<exact-id> --json returns {id, label, removed: true}.
The named selector cannot be combined with the positional ID/label. A supplied
wrong password fails even if an unlocked session exists.
Password change, removal, and reset accept --expected-state=<stateHash> from
lifecycle preview --json. The CLI checks this fingerprint under its filesystem
lease before writing; a changed store requires a new review. Preview returns
{root, stateHash, wallets, files} without credentials. If wallet metadata is
corrupt, wallets is null with a metadataError, so reset can still be reviewed.
Updated CLI processes share a canonical-path filesystem lease. Overlapping operations report a busy store; a killed owner's lease expires after 30 seconds. Older CLI versions and other programs that ignore the lease are not coordinated. Password rotation, removal, and reset commit a durable roll-forward journal before replacing data files. The journal contains encrypted wallet records and password-verifier metadata, never plaintext passwords or recovery phrases. The next leased operation completes a valid pending transaction before reading the store. After an interrupted rotation, try the new password first. Individual files and journals are limited to 16 MiB. File data is synced; directory syncing is best effort on Windows where unsupported. A corrupt or unrecoverable journal blocks operations, including reset: preserve its files for repair.
Accounts
| Command | Description |
| --- | --- |
| xl1-wallet account derive <offset> | Derive and store an account at an offset. |
| xl1-wallet account list [--wallet <id-or-label>] | List stored accounts for the active or specified wallet. |
| xl1-wallet account show <offset> | Show a derived account address. |
| xl1-wallet account label <offset> <label> | Label an account. |
| xl1-wallet account remove <offset> | Remove a stored account record. |
Examples:
xl1-wallet account derive 0 --label main
xl1-wallet account show 0
xl1-wallet account list
xl1-wallet account derive 4 --wallet-id <wallet-id> --label savingsAccount show, derive, label, and remove accept --wallet-id to target an
exact stored wallet without changing the active wallet. Labels are not accepted
by this option. Omitting it keeps the active-wallet default. Desktop account
changes require an explicit wallet ID so a reviewed change stays attached to
the intended wallet. Account removal only removes address-book metadata;
the key remains derivable and funds are unaffected. Account labeling validates
the derivation path before saving it.
Balances And Transfers
| Command | Description |
| --- | --- |
| xl1-wallet balance [offset] | Get the XL1 balance for an account offset. |
| xl1-wallet send <recipient> <amount> | Send XL1 from an account in the active wallet to an address. |
Examples:
xl1-wallet balance 0
xl1-wallet send 0x1111111111111111111111111111111111111111 1 --xl1
xl1-wallet send 0x1111111111111111111111111111111111111111 250 --micro --offset 0
xl1-wallet send 0x1111111111111111111111111111111111111111 1.5 --milli --dry-run
xl1-wallet send 0x1111111111111111111111111111111111111111 1000000000000000000 --atto --jsonsend supports --dry-run, which signs and validates the transaction plan without submitting it to the network.
Use --json when scripting:
xl1-wallet send 0x1111111111111111111111111111111111111111 1 --xl1 --jsonAmount Units
Amounts are converted to attoXL1 before signing.
| Flag | Unit | Atto multiplier | Fractional input |
| --- | --- | ---: | --- |
| --xl1 | XL1 | 1000000000000000000 | Yes |
| --milli | milliXL1 | 1000000000000000 | Yes |
| --micro | microXL1 | 1000000000000 | Yes |
| --nano | nanoXL1 | 1000000000 | Yes |
| --pico | picoXL1 | 1000000 | Yes |
| --femto | femtoXL1 | 1000 | Yes |
| --atto | attoXL1 | 1 | No |
If no unit flag is supplied, send defaults to --xl1.
Contacts
| Command | Description |
| --- | --- |
| xl1-wallet contact add <address> <label> | Save a contact. |
| xl1-wallet contact list | List saved contacts. |
| xl1-wallet contact rename <address> <label> | Rename a contact. |
| xl1-wallet contact remove <address> | Remove a contact. |
Examples:
xl1-wallet contact add 0x1111111111111111111111111111111111111111 treasury
xl1-wallet contact listTransaction Files And Payloads
Use sign to sign a payload JSON file directly:
xl1-wallet sign payload.json --output signed-payload.jsonUse tx sign and tx broadcast for XL1 transaction files. This path supports on-chain payloads. Replace the chain value with the target XL1 chain id.
{
"chain": "0x1234",
"nbf": 0,
"exp": 4102444800,
"onChainPayloads": [
{
"schema": "network.xyo.example",
"payload": {
"message": "hello xl1"
}
}
]
}xl1-wallet tx sign unsigned-transaction.json --output signed-transaction.json
xl1-wallet tx broadcast signed-transaction.jsonJWTs
Create, verify, and decode wallet-signed JWTs:
xl1-wallet jwt create --audience https://example.com --claim role=operator
xl1-wallet jwt verify <token> --audience https://example.com
xl1-wallet jwt decode <token>Create a JWT from a payload file:
xl1-wallet jwt create \
--audience https://example.com \
--schema network.xyo.example \
--payload-file payload.json \
--output token.jwtBackup And Reset
Backup exports and imports wallet CLI metadata such as contacts and address books. It does not replace saving wallet recovery phrases.
xl1-wallet backup export wallet-metadata.json
xl1-wallet backup import wallet-metadata.json
xl1-wallet resetMetadata backups retain the version 1 network.xyo.wallet.backup format. They
contain address-book offsets and labels, and contact addresses, labels, and
timestamps. They do not contain recovery phrases, passwords, session credentials,
or network configuration. Imports merge metadata: incoming defined account labels
and contact labels win, existing account labels survive when incoming labels are omitted, and existing
contact creation timestamps are preserved. Contact update timestamps never move
backward when importing an older backup.
Review an import or export before applying it:
xl1-wallet backup inspect wallet-metadata.json --direction=import --json
xl1-wallet backup inspect wallet-metadata.json --direction=export --jsonThe JSON result is {root, stateHash, path, fileStateHash, exists, addressBooks,
accounts, contacts}. root and path are canonical absolute paths. Counts refer
to incoming backup entries for import, or the stored metadata to export. The
inspection exposes only paths, counts, existence, and hashes. To require exactly
the reviewed wallet and file states, pass both hashes to the subsequent command:
xl1-wallet backup import wallet-metadata.json \
--expected-state=<stateHash> --expected-file-state=<fileStateHash> --json
xl1-wallet backup export wallet-metadata.json --overwrite \
--expected-state=<stateHash> --expected-file-state=<fileStateHash> --jsonImport returns {imported: true, addressBooks, accounts, contacts}; export returns
{exported: true, addressBooks, accounts, contacts}. Existing export destinations
require --overwrite. New exports never overwrite a file created concurrently.
Exports use private 0600 temporary files, fsync, and atomic publication. Imports
commit address books and contacts together through the wallet recovery journal
and never modify their source backup. Cooperating CLI processes coordinate by
wallet root and export destination, including exports from different wallet roots.
Files must be regular files of at most 16 MiB, with one filesystem link. Symlinks,
directories, special files, wallet storage files, and wallet transaction or lease
paths are rejected. The parent directory must already exist. Version 1 schemas
and fields are validated strictly; unknown entries, duplicate books/accounts or
contacts, invalid legacy or quant addresses, and prototype keys are rejected.
Labels preserve every string accepted by existing version 1 authoring commands,
including empty, multiline, and long values, within the overall file size limit.
Offsets use decimal HD path segments up to 2147483647, optional hardened
apostrophes and m/, at most 32 segments and 512 characters. Timestamps use UTC
ISO format with milliseconds, as produced by the CLI.
For noninteractive reset, pass both --confirm=RESET and --yes; --json
returns {reset: true, removedFiles: number}. No password is required. Reset
removes only password.json, phrases.json, active.json, address-books.json,
contacts.json, networks.json, session.json, and .machine-id. It preserves
the directory and unrelated files, and refuses to recurse into a directory
occupying one of those filenames.
Output And Automation
Most commands print readable text by default. Commands intended for automation, such as send, also support JSON output:
xl1-wallet send 0x1111111111111111111111111111111111111111 1 --xl1 --jsonFor isolated test runs, set a temporary wallet home:
XL1_WALLET_HOME="$(mktemp -d)" ARIES_WALLET_PASSWORD="test-password" xl1-wallet createDevelopment
Compile the package:
pnpm xy compile @xyo-network/wallet-xl1-cliRun the wallet CLI test suite:
pnpm exec vitest run packages/wallet-cli-lib/src/spec/*.spec.ts --project nodeSmoke-test the built bin:
node packages/wallet-cli/dist/bin/wallet.mjs --helpLicense
LGPL-3.0-only
Machine-readable output
The read/listing commands accept --json to emit structured JSON instead of formatted text:
xl1-wallet list --json
xl1-wallet account list --json
xl1-wallet account show 0 --json
xl1-wallet balance --json
xl1-wallet network list --json
xl1-wallet contact list --json
xl1-wallet export --jsonIn --json mode the command prints only JSON to stdout (no decoration), so the output can be piped directly into jq or another parser.

