@xeprotocol/sdk is the official client library for XE. It builds, signs and
proof-of-works blocks in your own process and submits them to any node's
HTTP API, so your keys never leave your machine and you never have to run a
node. It runs in Node.js 20+ and in the browser on the same code path, and it
is written in TypeScript with bigint throughout, so micro-unit amounts and
nanosecond timestamps are never rounded.
The source of truth is the repository at github.com/xeprotocol/sdk; this page mirrors its README so the SDK is discoverable alongside the HTTP API and the CLI. When they disagree, the repository wins.
[!WARNING] Pre-release — not on npm
- Not published to a registry yet.
npm install @xeprotocol/sdkfails today; build it from the repository as described below.- XE is pre-1.0 and runs on a public testnet only. Protocol changes land by wiping the testnet; balances, accounts and history are discarded when that happens, without warning.
- There is no backward compatibility, and there will be none before 1.0. The API in the SDK will change.
- XE on the testnet has no monetary value.
Install from source
git clone https://github.com/xeprotocol/sdk.git
cd sdk
npm install # runs the prepare script, which compiles src/ to dist/
npm test # unit tests, no network neededThen use it from your own project either by path or straight from git — both run the build for you:
npm install /path/to/sdk
# or
npm install github:xeprotocol/sdknpm link works too during development. The package exposes two entry points:
@xeprotocol/sdk (Node and browser) and @xeprotocol/sdk/jobs (Node only, for
running code on leased machines).
Quick start
import { Wallet, Xe, fromMicro, toMicro } from '@xeprotocol/sdk'
const wallet = Wallet.create() // or Wallet.fromSeedHex(process.env.SEED)
console.log(wallet.address) // hand this out to receive funds
const xe = new Xe({ client: 'https://ldn.core.test.network', wallet })
// Anything sent to you arrives as PENDING and is yours only once you claim it.
await xe.receiveAll()
console.log(fromMicro(await xe.balance('XE')), 'XE')
await xe.send({
to: someAddress,
amount: toMicro('1.5'),
memo: 'thanks',
})Point client at any node. https://ldn.core.test.network is the public
London testnet node; the other bootstrap nodes are listed in
Getting Started. Fund a fresh wallet with
xe faucet from the CLI or the faucet tutorial below — the faucet
sends 1,000 XE per account per day as a pending transfer, which
xe.receiveAll() claims.
Things the SDK gets right for you
Address is not public key
An address is derived from a public key, and the two are different values:
address = sha256("xe/account/v1" || pubkey)The address is identity — it goes in a block's account and destination,
and it is what you paste to receive funds. The public key is a credential.
Keeping them apart is what lets a key be rotated without the account changing,
so the SDK types them distinctly (Address, PublicKey) and will not let you
pass one where the other belongs.
Amounts are integers
Both assets carry six decimal places and every amount on the wire is an integer
count of micro-units; timestamps are unix nanoseconds. Both exceed what a
JavaScript number can hold exactly, so the SDK uses bigint throughout and
parses responses losslessly — a rounded timestamp produces a block the network
rejects, and a rounded balance is simply wrong.
toMicro('1.5') // 1500000n
fromMicro(1500000n) // '1.500000'Errors tell you whether to retry
The node distinguishes a failure worth retrying (a block that arrived before its dependency) from a terminal one (a bad signature). The SDK carries that distinction through, so you never guess from a message:
import { isRetryable } from '@xeprotocol/sdk'
try {
await xe.send({ to, amount })
} catch (err) {
if (isRetryable(err)) { /* back off and try again */ }
}A non-2xx response always throws. An empty list from the SDK means the account genuinely has nothing — never that the request failed.
Renting a machine
A lease escrows XUSD with a provider for a fixed term. To pay only for the time you use, keep the term short and renew it a minute at a time: nothing beyond the current minute is ever escrowed, so if you stop — or your process dies — the machine is released within a minute.
import { Wallet, Xe, holdLease } from '@xeprotocol/sdk'
const xe = new Xe({
client: 'https://ldn.core.test.network',
wallet: Wallet.fromSeedHex(process.env.SEED),
// Renewals are timed by the network's timekeepers, so the SDK needs to
// reach a threshold of them.
timekeepers: [
'https://ldn.core.test.network',
'https://ffm.core.test.network',
'https://nyc.core.test.network',
],
})
const sshKey = Wallet.create() // the key the machine will accept
const lease = await xe.openLease({
provider, // an address from xe.client.providers()
vcpus: 1, memoryMb: 1024, diskGb: 10,
durationSecs: 60,
accessPubKey: sshKey.publicKey,
})
// Renew a minute at a time until the term reaches ten minutes.
await holdLease(xe, lease.hash, { renewSecs: 60, totalSecs: 600 })openLease prices the lease from the provider's current performance
certificate, exactly as the ledger will. renewLease gathers timekeeper
attestations, checks them locally and locks the current oracle epoch's emission
parameters. holdLease renews ahead of each expiry and confirms every extension
on the ledger before scheduling the next. cancelLease withdraws a lease the
provider has not accepted; forceSettleLease reclaims the escrow of one the
provider never settled.
Prices. You sign the exact amount of every lease and renewal, so nobody can
charge you more than you signed for. On top of that the SDK refuses to sign a
price you did not agree to: a price ceiling (maxPricePerMinute), renewals held
at the price the lease opened at, and a total budget passed to holdLease.
When a limit is hit the SDK stops renewing; the machine runs to the end of the
minute already paid for and is then released. The full model is in
docs/budgets.md.
[!NOTE] Paying for a lease needs XUSD Leases are priced in XUSD, which only the operators'
sys.minteraccounts issue and which the faucet does not hand out, so testers can read the compute market (xe.client.providers(),GET /leases) and run a provider, but cannot fund a lease themselves yet. See Assets.
Running a job
@xeprotocol/sdk/jobs (Node only) runs your code on a rented machine and brings
the results home, holding the lease a minute at a time while it runs:
import { runJob } from '@xeprotocol/sdk/jobs'
const job = await runJob(xe, {
machine: { vcpus: 1, memoryMb: 1024, diskGb: 1 },
files: { 'main.py': 'print("hello")' },
run: 'python3 main.py',
budget: toMicro('0.01'),
})
console.log(job.status, job.stdout, fromMicro(job.paid))Five complete programs, including every way a job can fail, are in
examples/jobs/.
Tutorials
Step-by-step, in TypeScript and JavaScript, each ending in a complete program
you can run against the testnet:
examples/.
| # | Tutorial | You will |
|---|---|---|
| 1 | Your first wallet | install the SDK, create and keep a wallet, connect, read a balance |
| 2 | Getting testnet funds | use the faucet, claim pending transfers, wait for finality |
| 3 | Sending XE | send with a memo, claim it on the other side, look up a block |
| 4 | Errors and retries | tell temporary failures from final ones and retry safely |
| 5 | Renting a machine | find a provider, price a machine, rent it |
| 6 | Holding a lease open | keep a machine as long as you need it, paying a minute at a time |
Tutorials 1–4 need nothing but Node.js; 5–6 need XUSD.
What works today
| Area | Status |
|---|---|
| Wallets, addresses, signing | ✅ |
| Canonical block encoding, hashing, proof of work | ✅ verified byte-for-byte against the node |
| Send, receive, burn | ✅ |
| Balances, pending, chains, blocks, supply | ✅ |
| Providers, leases, state chain (read) | ✅ |
| Leasing a machine: open, renew, cancel, force-settle, hold a lease open | ✅ |
| Price ceiling, renewal price lock, budgets, quotes | ✅ |
| Jobs: run code on a leased machine and collect results (Node) | ✅ through the testnet SSH gateway |
| Published package | 🚧 not yet |
| Messaging and the account directory | 🚧 not yet — use the HTTP API |
| Other languages | 🚧 later — TypeScript first |
Lower-level exports
Everything the high-level Xe class uses is exported, so you can build blocks
yourself when you need to: XeClient (typed HTTP calls), Wallet,
deriveAddress, verifySignature, hashBlock, signBlock,
marshalCanonical, solvePow / validatePow, toMicro / fromMicro,
toAddress / toPublicKey / toHash, and lossless JSON helpers
(parseLossless, stringifyWithBigInts). The wire format they implement is
documented under Encoding and Cryptography.
Development
npm install
npm run typecheck
npm run lint
npm test # unit tests, no network needed
npm run build
npm run test:integration # against a public node; skips with a reason when none is reachable