sphere-sdk README

The project's README from github.com/unicity-sphere/sphere-sdk. As one markdown file: README.md.

Sphere SDK

A modular TypeScript SDK for Unicity wallet operations (Unicity state transition network).

Features

Installation

npm install @unicitylabs/sphere-sdk

Quick Start Guides

Choose your platform:

Platform Guide Required Optional
Browser QUICKSTART-BROWSER.md SDK only IndexedDB storage
Node.js QUICKSTART-NODEJS.md SDK + ws File storage
CLI @unicity-sphere/cli Separate package -
dApp integration CONNECT.md SDK only Sphere extension

CLI (Command Line Interface)

The CLI has moved to a dedicated package: @unicity-sphere/cli.

npm install -g @unicity-sphere/cli
sphere --help

See docs/QUICKSTART-CLI.md for the full command reference.

Quick Start

Setup is two provider layers, not one. createBrowserProviders / createNodeProviders build only the base (storage + transport + oracle). You must then attach the wallet-api transport config with createWalletApiProviders — money moves only through the wallet-api vertical. Skipping it fails loudly: Sphere.init throws INVALID_CONFIG.

import { Sphere } from '@unicitylabs/sphere-sdk';
import { createBrowserProviders } from '@unicitylabs/sphere-sdk/impl/browser';
import { createWalletApiProviders } from '@unicitylabs/sphere-sdk/impl/shared/wallet-api';

// 1. Base providers: storage + transport + oracle. `network` is REQUIRED here (no default).
//    The testnet2 gateway key is PUBLIC (not a secret); it is required at runtime for send/mint.
const base = createBrowserProviders({
  network: 'testnet',                                          // alias of testnet2 (networkId 4)
  oracle: { apiKey: 'sk_ddc3cfcc001e4a28ac3fad7407f99590' },   // public testnet2 key
});

// 2. Attach the wallet-api transport config. Returns { ...base, walletApi } —
//    a plain config object ({ network, baseUrl, deviceId?, ... }) that Sphere.init consumes.
const providers = createWalletApiProviders(base, {
  baseUrl: 'https://wallet-api.unicity.network',   // your wallet-api deployment (testnet2)
  network: 'testnet2',
  deviceId: 'my-stable-device-id',                 // persist this to avoid re-auth each launch
});

// 3. Init the wallet (auto-creates one if none exists).
const { sphere, created, generatedMnemonic } = await Sphere.init({
  ...providers,
  autoGenerate: true,
});
if (created && generatedMnemonic) {
  console.log('SAVE THIS RECOVERY PHRASE:', generatedMnemonic);
}

// 4. Send — engine-driven, certified on-chain. The recipient needs a published identity
//    (chain pubkey), e.g. a registered Unicity ID; otherwise send fails with INVALID_RECIPIENT.
const result = await sphere.payments.send({
  recipient: '@alice',
  amount: '1000000',     // decimal STRING — never a JS number
  coinId: 'UCT',         // a symbol auto-resolves to its hex coinId
  memo: 'hello',
});
console.log(result.status);   // 'completed'
// result.deliveryPending === true is NORMAL, not a failure: the token is certified on-chain but
// the recipient's mailbox delivery was deferred and will land on retry (see "Send result" below).

// 5. Receive — incoming transfers land automatically while the wallet runs (mailbox drain +
//    wake socket). To drain explicitly (e.g. a CLI/batch app), call receive():
const { transfers } = await sphere.payments.receive();
sphere.on('transfer:incoming', (t) => console.log('received from', t.senderNametag));

console.log(await sphere.payments.assets());

What just happened (the provider model)

A wallet is composed from swappable ports, layered in two steps:

Layer Built by What it supplies
Base createBrowserProviders / createNodeProviders storage (keys/identity/journals), transport (Nostr — messaging/nametags only), oracle (gateway/trust base)
wallet-api transport createWalletApiProviders(base, …) walletApi — the transport CONFIG ({ network, baseUrl, deviceId?, fetchFn?, webSocketFactory?, paymentsV2Transport? }) the payments vertical is composed from

For manual/advanced provider wiring, see Custom Providers Configuration. For the deeper integration guide, see docs/INTEGRATION.md.

Send result (TransferResult)

send() resolves with a TransferResult:

Field Meaning
status 'completed' on success. ('pending' | 'submitted' | 'confirmed' | 'delivered' | 'failed' also exist for in-flight/terminal states.)
deliveryPending true when the spend is certified on-chain but the recipient's mailbox delivery was deferred (a full inbox / transient outage). This is success, not failure — the token is finalized and the finished blob is journaled and re-delivered automatically.
deliveryState 'landed' (delivered) or 'pending-delivery' (deferred, as above).

Treat status === 'completed' as sent. Use deliveryPending only to show a "delivery pending" hint — never as an error. A stale-but-spent source is self-healed (the next live coin is selected automatically).

Handling send() rejections — CERTIFICATION_UNCONFIRMED is NOT re-sendable (money-safety)

send() throws for genuine failures (INVALID_RECIPIENT, insufficient balance, a TransferConflictError lost race) and for one indeterminate case you must handle specially: a ProofUnconfirmedError (code: 'CERTIFICATION_UNCONFIRMED', mayHaveCertified: true). It means the spend may already be on-chain but the proof fetch was inconclusive — the SDK keeps the intent open and completes it later under the same transferId.

import { isSphereError } from '@unicitylabs/sphere-sdk';

try {
  const result = await sphere.payments.send({ recipient: '@bob', amount, coinId });
  // result.status === 'completed' (or result.deliveryPending === true) → sent
} catch (err) {
  if (isSphereError(err) && err.code === 'CERTIFICATION_UNCONFIRMED') {
    // Possibly already sent on-chain — DO NOT re-send. Resume finishes it.
  } else {
    // genuine failure — safe to surface to the user / retry
  }
}

Migrating off sphere.paymentsV2

The deprecated sphere.paymentsV2 alias and the paymentsV2: true init flag are removed in 0.15.0. sphere.payments is the only accessor, and it is the same facade the alias returned.

One behavioural difference matters: while no vertical is running (init in flight, mid address-switch, destroyed) the alias returned null and sphere.payments throws SphereError with code: 'NOT_INITIALIZED'. Call sites that leaned on the nullish alias — sphere.paymentsV2?.tokens(), ?? fallback, if (sphere.paymentsV2) as a readiness probe — silently degraded to "no payments" before and now throw, so catch NOT_INITIALIZED where you used to check for null. Code that runs after await Sphere.init(…) and before destroy() — everything else in this README — reads sphere.payments directly.

The accounting: / swap: options are not part of this cleanup: they still throw a typed INVALID_CONFIG, deliberately, because 0.15.0 is the release where consumers re-integrate.

Network Configuration

The SDK ships network presets that configure all services automatically. network is required — there is no default:

Network Aggregator (gateway) Nostr Relay
testnet gateway.testnet2.unicity.network (v2) nostr-relay.testnet.unicity.network
testnet2 alias of testnet (same configuration) nostr-relay.testnet.unicity.network
mainnet gateway.mainnet.unicity.network (v3) nostr-relay.testnet.unicity.network (shared until mainnet has its own)

Live networks are testnet2 and mainnet. testnet is an alias of testnet2 (network id 4, taken from the trust base; own testnet2 token registry); mainnet is network id 1. The v1 network is discontinued — the old goggregator-test testnet spoke the removed v1 protocol, and the dev network that aliased its trust base has been removed along with every other v1 pointer. Mainnet has no wallet-api deployment yet, so its money path is not reachable even though the chain and gateway are live. The transfer wire payload is the finished token blob — the base SDK's own Token.toCBOR() bytes, with no sphere envelope around them — deposited into the recipient's wallet-api mailbox.

The network name (testnet2) and the base-SDK major (3.x since 0.15.0) are separate axes: testnet2 is still testnet2 after the 3.0.1 bump. What the bump changes is the bytes on that network — a gateway serving the v3 protocol accepts nothing a 2.x client writes, and vice versa.

// Use testnet for all services
const providers = createBrowserProviders({ network: 'testnet' });

// Override specific services while using network preset
const providers = createBrowserProviders({
  network: 'testnet',
  oracle: { url: 'https://custom-gateway.example.com' }, // custom testnet2 gateway
});

API Key

The SDK bundles no default API key. Pass the gateway key via oracle: { apiKey } — without it, gateway requests are unauthenticated and money movement on testnet2 fails.

const providers = createBrowserProviders({
  network: 'testnet',
  oracle: { apiKey: 'sk_...' },
});

The testnet2 key is not a secret — it is published in .env.example and safe to keep in docs and client code. A mainnet key, by contrast, IS a secret: keep it in your deploy environment only.

Testnet2 endpoints (the values we build with)

The testnet preset wires most of these automatically — you only pass network, oracle.apiKey, and the wallet-api baseUrl. The full set, for reference and manual wiring:

What Value
Network testnet (alias testnet2), networkId 4
Aggregator / gateway (token engine) https://gateway.testnet2.unicity.network
Aggregator API key (public — not a secret) sk_ddc3cfcc001e4a28ac3fad7407f99590
wallet-api (delivery + token storage) https://wallet-api.unicity.network
Nostr relay (messaging / nametags) wss://nostr-relay.testnet.unicity.network
Group-chat relay (NIP-29) wss://sphere-relay.unicity.network
Token registry https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json

The aggregator key above is the testnet2 key only and is safe in client code; a mainnet key is a real secret and must never be committed.

Price Provider (Optional)

Enable fiat price display by adding a price config. Currently supports CoinGecko API (free and pro tiers).

// With CoinGecko (free tier, no API key)
const providers = createBrowserProviders({
  network: 'testnet',
  price: { platform: 'coingecko' },
});

// With CoinGecko Pro
const providers = createBrowserProviders({
  network: 'testnet',
  price: { platform: 'coingecko', apiKey: 'CG-xxx' },
});

const { sphere } = await Sphere.init({ ...providers, autoGenerate: true });

// Assets with price data
const assets = await sphere.payments.assets();
// [{ coinId, symbol, totalAmount, priceUsd: 97500, fiatValueUsd: 975.00, change24h: 2.3, ... }]

// Total portfolio value in USD
const totalUsd = assets.reduce((sum, a) => sum + (a.fiatValueUsd ?? 0), 0);

Without price config, the price fields in assets() are null. All other functionality works normally.

You can also set the price provider after initialization — price is a composition-time property of the payments vertical, so verticals composed after the call (the next address switch) pick it up:

import { createPriceProvider } from '@unicitylabs/sphere-sdk';

sphere.setPriceProvider(createPriceProvider({
  platform: 'coingecko',
  apiKey: 'CG-xxx',
}));

Test Tokens on Testnet (Self-Mint)

There is no faucet. On testnet you top up your wallet by self-minting fungible tokens via the token engine — mint(coinIdHex, amount) mints a finished token directly to this wallet (journal-first: crash-safe, a replay converges idempotently):

import { getCoinIdBySymbol } from '@unicitylabs/sphere-sdk';

// Resolve the coin's hex id from the token registry (or pass a hex coinId directly)
const coinId = getCoinIdBySymbol('UCT');

const result = await sphere.payments.mint(coinId!, 1000n);
if (result.success) {
  console.log('Minted token:', result.tokenId);
} else {
  console.error('Mint failed:', result.error);
}

Note: Minting requires a working oracle config (trust base + gateway URL + API key) — it fails with an error result otherwise. See API Key above.

Multi-Address Support

The SDK supports HD (Hierarchical Deterministic) wallets with multiple addresses:

// Get current address index
const currentIndex = sphere.getCurrentAddressIndex(); // 0

// Switch to a different address
await sphere.switchToAddress(1);
console.log(sphere.identity?.directAddress); // DIRECT://... (address at index 1)

// Register nametag for this address (independent per address)
await sphere.registerNametag('bob');

// Switch back to first address
await sphere.switchToAddress(0);

// Get nametag for specific address
const bobNametag = sphere.getNametagForAddress(1); // 'bob'

// Get all address nametags
const allNametags = sphere.getAllAddressNametags();
// Map { 0 => 'alice', 1 => 'bob' }

// Derive address without switching (for display/receiving)
const addr2 = sphere.deriveAddress(2);
console.log(addr2.address, addr2.publicKey);

Identity Properties

Important: The DIRECT address is the primary address for the Unicity network.

interface Identity {
  chainPubkey: string;         // 33-byte compressed secp256k1 public key
  directAddress?: string;      // DIRECT address (DIRECT://...) - PRIMARY ADDRESS
  ipnsName?: string;           // IPNS name for token sync
  nametag?: string;            // Registered nametag (@username)
}

// Access identity - use directAddress as primary
console.log(sphere.identity?.directAddress);    // DIRECT://0000be36... (PRIMARY)
console.log(sphere.identity?.nametag);          // alice (human-readable)
console.log(sphere.identity?.chainPubkey);      // 02abc123... (33-byte compressed)

Address Change Event

// Listen for address switches
sphere.on('identity:changed', (event) => {
  console.log('Switched to address index:', event.data.addressIndex);
  console.log('L3 address:', event.data.directAddress);
  console.log('Chain pubkey:', event.data.chainPubkey);
  console.log('Nametag:', event.data.nametag);
});

// Listen for nametag recovery (when importing wallet)
sphere.on('nametag:recovered', (event) => {
  console.log('Recovered nametag from Nostr:', event.data.nametag);
});

Payment Requests

Request payments from others over the wallet-api rail (sphere.payments.requests). Request memos ride an encrypted recipient-ECDH envelope; a pay() is durably settling before any possibly-committed error can surface, so a crash never double-pays:

// Send a payment request
const result = await sphere.payments.requests.create('@bob', {
  coinId: 'UCT',
  amount: '1000000',
  memo: 'Payment for order #1234',
});

// Track status via the event stream
sphere.on('payment_request:updated', ({ id, status }) => {
  // status: 'pending' | 'settling' | 'paid' | 'rejected' | 'expired'
  if (id === result.requestId && status === 'paid') console.log('Payment received!');
});

// Handle incoming payment requests
sphere.on('payment_request:incoming', async (request) => {
  console.log(`${request.senderNametag} requests ${request.amount} ${request.symbol}`);

  // Accept and pay
  await sphere.payments.requests.pay(request.id);

  // Or decline (a server 403/409 propagates — a refused decline is not success)
  await sphere.payments.requests.decline(request.id);
});

// Current views + housekeeping
const open = sphere.payments.requests.list();
sphere.payments.requests.dismissProcessed();

Group Chat (NIP-29)

Relay-based group messaging using the NIP-29 protocol. The module embeds its own Nostr connection separate from the wallet transport.

Enabling Group Chat

// Enable with network defaults (wss://sphere-relay.unicity.network)
const { sphere } = await Sphere.init({
  ...providers,
  autoGenerate: true,
  groupChat: true,
});

// Enable with custom relay
const { sphere } = await Sphere.init({
  ...providers,
  autoGenerate: true,
  groupChat: { relays: ['wss://my-nip29-relay.com'] },
});

// Access the module
const gc = sphere.groupChat!;

Connection

// Connect to the NIP-29 relay
await gc.connect();
console.log('Connected:', gc.getConnectionStatus());

// Check if current user is a relay admin
const isRelayAdmin = await gc.isCurrentUserRela

sphere-sdk (unicity-sphere/sphere-sdk)

Searching Database…

Что это такое?Sphere SDK — это модульный SDK на TypeScript, который даёт автономным ИИ-агентам идентичность и кошелёк, а также возможность платить, договариваться и рассчитываться с другими агентами напрямую, peer-to-peer, в сети Unicity.
Это библиотека, которую я подключаю, или самостоятельное приложение-кошелёк?Это библиотека (SDK), которую вы импортируете в браузерное приложение или приложение на Node.js. Если нужен готовый самостоятельный инструмент, пакет @unicity-sphere/cli предоставляет кошелёк для командной строки.
Как поднять кошелёк с помощью этого SDK?Создайте базовые провайдеры через createBrowserProviders или createNodeProviders (хранилище, транспорт и оракул), затем подключите транспорт платежей через createWalletApiProviders и вызовите Sphere.init() с одинаковой сетевой конфигурацией для всех трёх слоёв.
Что помимо SDK нужно, чтобы переводить реальные токены?Node.js 22+ и API-ключ шлюза или оракула. Опубликованный публичный ключ работает в testnet2, но для mainnet нужен собственный секретный ключ. Без любого ключа запросы не аутентифицируются, и переводы завершаются ошибкой.
Могу ли я уже сегодня переводить деньги в mainnet?Пока нет: в mainnet не развёрнут wallet-api, поэтому платёжный путь недоступен, хотя сеть и шлюз работают. Реальные переводы поддерживает только testnet2. Тестового крана (faucet) нет, поэтому тестовые токены вы выпускаете сами.
Кто на самом деле хранит мои токены и ключи?Хранение токенов происходит на стороне сервера: серверная часть wallet-api хранит ваш список токенов. Но приватные ключи остаются на вашем устройстве и никогда не покидают клиент.
Как агенты находят друг друга и договариваются о сделках?Через модуль Market (Intents) — подписанную доску объявлений с семантическим поиском и живой лентой. Агенты публикуют предложения, находят их и откликаются на них, а затем проводят саму оплату по каналу wallet-api.
Что произойдёт, если подтверждение платежа в блокчейне окажется неоднозначным?send() выбрасывает ошибку ProofUnconfirmedError (код CERTIFICATIONUNCONFIRMED). Вручную повторять её нельзя: SDK сохраняет исходную операцию открытой и автоматически возобновляет её с тем же transferId, поэтому получатель не будет оплачен дважды.
Может ли dApp взаимодействовать с кошельком Sphere из браузерной страницы?Да, через Connect Protocol: ConnectClient и ConnectHost позволяют dApp обмениваться данными с кошельком Sphere, который работает как всплывающее окно браузерного расширения.
Можно ли проверить переводы без реальных средств?Да. Тестового крана нет, но в testnet2 вы сами выпускаете взаимозаменяемые тестовые токены напрямую через mint(coinIdHex, amount), используя публично опубликованный тестовый API-ключ.
What are similar projects?monero, gun, trystero, hush, ethereum-sdk, TrustMint, diem, nuvex
Size
25 MB
Stars
5.4K
Age
8mo
Commits · 90 Days