@chironbuilder/ootle-sdk

中文文档
0.4.1发布于 2026/11/08MITTypeScriptES Module近 30 天下载 2478

Shared TypeScript client for Tari Ootle (L2): account derivation, balances, transactions, HTLCs, and stealth/confidential transfers — the account core behind Sapient and the Tari L1 web wallet's Ootle support.

原始使用文档
文档目录10 节
  1. Install
  2. Storage
  3. Usage
  4. Networks
  5. Claiming a Minotari (L1) burn
  6. HTLCs and atomic swaps
  7. Verifying ownership proofs
  8. What's in here, and what isn't
  9. Developing
  10. License

@chironbuilder/ootle-sdk

A shared TypeScript client for Tari Ootle (L2): account derivation, balances, transaction building/submission with automatic input resolution, HTLCs, and stealth/confidential transfers.

This is the account core extracted from two independently-built wallets that had converged on (mostly) the same code by hand — Sapient, a Chrome extension, and the Tari L1 web wallet's Ootle L2 support — after each had picked up its own fixes the other was missing. This package exists so that stops happening: one wallet core, used by both, with platform-specific concerns (storage, in this case) injected rather than hardcoded.

Status: early. Extracted and reconciled in one pass; not yet wired back into either of the two wallets above (they still carry their own local copies for now). Treat 0.x as exactly that.

Install

npm install @chironbuilder/ootle-sdk

This package vendors two small, still-unmerged patches: @tari-project/ootle (a covenant_claims serialization gap in StealthTransferStatement.toCompactJson(), harmless for every transfer this SDK builds today but not yet upstream-fixed) and @tari-project/ootle-wasm (adds createConfidentialWithdrawProofLiteral, needed for confidential-vault withdrawals and not in the published 0.39.x builds). See vendor/*/README.md for each. Both are tracked for removal once upstream ships the real fix — check there before assuming either is still needed.

Storage

The SDK never touches a concrete storage API directly. It reads and writes through a small KeyValueStore interface — configure one adapter, once, before calling anything else:

import { configureOotleStorage } from "@chironbuilder/ootle-sdk";
import { chromeStorageAdapter } from "@chironbuilder/ootle-sdk/adapters";
// or: localStorageAdapter, inMemoryAdapter (tests)

configureOotleStorage(chromeStorageAdapter());

Write your own adapter for any other host — it's three methods (get/set/remove).

This only covers what the account core itself needs (shielded outputs, pending shields, the private-payment scan cursor, the known-substate-versions cache). Address books, connected-site permissions, daemon connections, and a full transaction-history UI are application concerns with very different shapes per host — keep those in your own app's storage layer.

Usage

import { OotleAccount, toOotleNetwork } from "@chironbuilder/ootle-sdk";

const account = OotleAccount.fromSeed(entropy, /* index */ 0, "esmeralda");

const balances = await account.getBalances();
await account.send(recipientWalletAddress, resourceAddress, amount);

See src/wallet.ts's doc comments for the full OotleAccount surface — send, sendPrivately, shield/unshield, htlcFund/htlcClaim/htlcRefund, claimTestnetXtr, execute (the general instruction-builder escape hatch, with automatic missing-input discovery and lock-contention retry).

Networks

NetworkName is "mainnet" | "esmeralda" | "igor". Mainnet holds real value; isTestnet(name) tells the two apart, and claimTestnetXtr refuses on mainnet. The same seed gives the same keys on every network; only the address prefix differs (otl_1… on mainnet, otl_esm_1… on Esmeralda), and every transaction carries its network byte, so one signed for a testnet can't be replayed on mainnet.

Indexers come from the SDK's own table (indexerUrlFor): mainnet is https://indexer-a.ootle.tari.com (tari-ootle's default), Esmeralda keeps https://ootle-indexer-a.tari.com, and Igor has none. Point a network elsewhere with setIndexerUrl(Network.MainNet, url) before creating accounts.

Because the keys are the same, stored records are kept apart by network instead. An account's shielded outputs, scan cursors and watermarks, pending shields, reservations and HTLC journal live under account.ledgerId: local:N on a testnet (unchanged from earlier versions, so existing data needs no migration) and mainnet:local:N on mainnet. Read them with account.ledgerId, never localAccountId(index). The substate version cache is split the same way, and recoverPendingShields(provider, network) only reconciles that network's pending shields.

Claiming a Minotari (L1) burn

A burn addressed to this account's public key (getPublicKey()) is claimed with claimBurn. Build the proof from the L1 burn's claim material and its kernel merkle proof (a base node's /generate_kernel_merkle_proof), or read a minotari_console_wallet proof file:

import { assembleBurnClaimProof, parseConsoleWalletBurnProof } from "@chironbuilder/ootle-sdk";

const proof = assembleBurnClaimProof(l1BurnParts, kernelMerkleProof);
// or: const proof = parseConsoleWalletBurnProof(fileText);
// The fee is measured with a dry run; a stealth-revealed fee is never refunded, so it is not padded.
const { claimedAmount, fee } = await account.claimBurn(proof);

The claimed funds arrive as a stealth output owned by this account and are recorded in its private balance. Validators accept a burn only once its L1 block is well confirmed, so an early claim is rejected and can be retried.

HTLCs and atomic swaps

htlcFund / htlcClaim / htlcRefund are built for settlement safety, not just for constructing the transactions:

// Lock 5,000 units of a stealth-created asset from exact private outputs. The fee stays in its own
// native-TARI lane (here: private), the asset stays in the main lane, excess comes back as change.
const fund = await account.htlcFund(asset, 5_000n, claimantAddress, hashLockHex, refundEpoch, 50_000n,
  { kind: "private", feeResourceAddress: TARI }, { source: { kind: "stealth", commitments: [myUtxo] } });
// fund.conditions → send to the claimant. fund.outputMask is also journaled (fund.journalId).

// Claimant: nothing is revealed unless the funded output matches the agreed terms.
const check = await account.verifyHtlc(asset, fund.ownCommitment, fund.conditions, { amount: 5_000n, minEpochsBeforeRefund: 3n });
await account.htlcClaim(asset, fund.ownCommitment, fund.conditions, preimageHex, 50_000n, feeType,
  { expected: { amount: 5_000n, refunderPublicKeyHex, minEpochsBeforeRefund: 3n } });

// Funder, once currentEpoch >= refundEpoch:
await account.refundFromJournal(fund.journalId);
  • Two lanes. The fee (native TARI, public or a private UTXO) never shares a UTXO with the HTLC value. source: { kind: "stealth" } spends this account's own stealth outputs of the resource, so no public balance of it is needed.
  • Dry run first. Every fund/claim/refund dry-runs the exact transaction before the real submit (preflight: false to skip — a claim's dry run sends the preimage to the indexer's dry-run endpoint).
  • Verified before the preimage leaves the device. htlcClaim (and verifyHtlc) checks the amount, that the claim leaf is your key and the refund leaf the agreed counterparty's, that the on-chain condition root equals the tree's root, that the preimage matches the hash lock, and that enough claim epochs remain; it throws HtlcVerificationError otherwise.
  • Durable journal. The sealed transaction, the condition tree and the HTLC output's mask (the only thing that makes a refund possible) are persisted before submission. An ambiguous outcome throws HtlcUnknownOutcomeError — "pending", never "failed" — and reconcileHtlcs() (call at startup) resolves it by resubmitting the identical transaction and applying its bookkeeping. listHtlcs() shows every operation and its state.
  • Exact inputs are reserved for the duration of an operation (and while its outcome is unknown), so a concurrent send/unshield/fee selection can't pick them.
  • Epoch rules match the engine: the refund leaf is AfterEpoch (open when currentEpoch >= refundEpoch), the claim leaf BeforeEpoch — see isHtlcRefundable / isHtlcClaimableByEpoch.
  • Claimed and refunded outputs are recorded in the shielded ledger, so they're spendable at once.

Verifying ownership proofs

A site that receives { walletAddress, publicNonce, signature } from tari_signWalletOwnershipChallenge checks it with verifyWalletOwnershipProof (and a per-output proof with verifyOwnershipProof), taking the key from the address it already had in mind. The byte spec is in docs/ownership-proof.md; src/fixtures/ownershipProof.vectors.json holds fixed vectors produced with Tari's real signer (test-only keys).

What's in here, and what isn't

  • Account core (wallet.ts): OotleAccount, balance/plan-resolution helpers, the transaction auto-resolve retry loop.
  • Crypto/derivation: derivation.ts, domainHash.ts, componentAddress.ts, vault.ts, ownershipProof.ts, verifyOwnershipProof.ts, confidential.ts.
  • HTLC: htlc.ts (condition trees), htlcSafety.ts (epoch rules, pre-claim verification, per-output masks, outcome classification) + the journaled OotleAccount HTLC methods.
  • L1 burn claims: burnClaim.ts (proof assembly) + OotleAccount.claimBurn.
  • Storage abstraction: storage.ts + adapters.ts.
  • Not included: UI, an approval/permission model for dApp connections, address books, daemon (tari_ootle_walletd) relay support, transaction-history persistence. Those stay in each consuming application.

Developing

npm install
npm run typecheck
npm test
npm run build

License

MIT — see LICENSE. (The two wallets this was extracted from are licensed separately, under PolyForm Noncommercial; this SDK is deliberately more permissive so other Ootle projects can build on it without that restriction.)

版本与发布记录

@chironbuilder/ootle-sdk 共发布 37 个版本,首次发布于 2026/09/12;过去 12 个月发布了 37 个版本。

版本发布日期距今
0.4.1latest2026/11/081 天前
0.4.02026/11/081 天前
0.3.02026/11/072 天前
0.2.22026/11/045 天前
0.2.12026/11/036 天前
0.2.02026/11/027 天前

关于 @chironbuilder/ootle-sdk 的常见问题

@chironbuilder/ootle-sdk 是什么?

Shared TypeScript client for Tari Ootle (L2): account derivation, balances, transactions, HTLCs, and stealth/confidential transfers — the account core behind Sapient and the Tari L1 web wallet's Ootle support.

如何安装 @chironbuilder/ootle-sdk?

在项目目录运行 npm install @chironbuilder/ootle-sdk,或使用 pnpm add / yarn add / bun add。

@chironbuilder/ootle-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@chironbuilder/ootle-sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@chironbuilder/ootle-sdk 可以商用吗?

@chironbuilder/ootle-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@chironbuilder/ootle-sdk 的下载量有多少?

根据 npm 官方下载统计,@chironbuilder/ootle-sdk 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,478 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chironbuilder/ootle-sdk 还在维护吗?

最新版本 0.4.1 发布于 2026/11/08(1 天前);至今共发布 37 个版本,其中过去 12 个月发布了 37 个;首次发布于 2026/09/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。