@unicitylabs/sphere-sdk 0.9.0-dev.0 → 0.9.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,363 @@
1
+ import { Token } from '@unicitylabs/state-transition-sdk/lib/transaction/Token.js';
2
+ import { IPaymentData } from '@unicitylabs/state-transition-sdk/lib/payment/IPaymentData.js';
3
+ import { Asset } from '@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js';
4
+ import { PaymentAssetCollection } from '@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js';
5
+
6
+ /**
7
+ * token-engine/types.ts — the FROZEN, sphere-domain contract surface.
8
+ *
9
+ * Design rule (anti-corruption): the public ITokenEngine port speaks ONLY
10
+ * sphere-domain types — `Uint8Array` pubkeys, `string` coin ids, `bigint`
11
+ * amounts, plain enums. The v2 state-transition SDK has exactly ONE foothold
12
+ * here: `SphereToken.sdkToken`, an OPAQUE handle. Callers must treat it as
13
+ * opaque (store it, hand it back to the engine) and never call methods on it —
14
+ * they cannot, since the ESLint boundary forbids them importing the SDK.
15
+ *
16
+ * Both migration tracks freeze against this file:
17
+ * Track A implements it (token-engine internals).
18
+ * Track B codes callers against it (using FakeTokenEngine until A lands).
19
+ */
20
+
21
+ /** The wallet identity at the engine boundary. The private key never appears in a DTO. */
22
+ interface EngineIdentity {
23
+ /** 33-byte compressed secp256k1 public key (stable across the migration — Path A). */
24
+ readonly chainPubkey: Uint8Array;
25
+ }
26
+ /** Which Unicity network a token/engine lives on. Maps to the SDK NetworkId inside the engine. */
27
+ type SphereNetwork = 'mainnet' | 'testnet' | 'local';
28
+ /**
29
+ * Coin identifier. Canonical form is the lowercase hex of the v2 AssetId;
30
+ * human symbols (e.g. "ALPHA") are resolved to hex via the registry before use.
31
+ */
32
+ type CoinId = string;
33
+ /** One fungible position inside a token. */
34
+ interface SphereAsset {
35
+ readonly coinId: CoinId;
36
+ readonly amount: bigint;
37
+ }
38
+ /** The decoded, app-defined value carried by a token (v2 Token itself is value-less). */
39
+ interface SphereValue {
40
+ readonly assets: readonly SphereAsset[];
41
+ }
42
+ /**
43
+ * Storage-and-display token. Format version + network let storage migrate
44
+ * independently of the SDK's own CBOR. The decoded value is re-derivable from
45
+ * `token`, so it is NOT stored — only cached at runtime on SphereToken.value.
46
+ */
47
+ interface TokenBlob {
48
+ /** Blob format version (sphere storage migrations; independent of SDK CBOR). */
49
+ readonly v: number;
50
+ /** NetworkId.id the token belongs to (mainnet=1 / testnet=2 / local=3). */
51
+ readonly network: number;
52
+ /**
53
+ * Genesis-stable token id — 64-char lowercase hex of the v2 `TokenId.bytes`
54
+ * (same across every state of the token). Stored on the blob so dedup / listing
55
+ * / tombstone keys need no engine call. `createTokenStateKey = ${tokenId}_${hash}`.
56
+ */
57
+ readonly tokenId: string;
58
+ /** CBOR bytes of the v2 Token (`Token.toCBOR()`). */
59
+ readonly token: Uint8Array;
60
+ }
61
+ /**
62
+ * A wallet token. `sdkToken` is the OPAQUE engine handle (see file header) —
63
+ * present for the engine to operate on, never to be touched by callers.
64
+ */
65
+ interface SphereToken {
66
+ /** Opaque v2 SDK handle. Do not call methods on this outside token-engine/. */
67
+ readonly sdkToken: Token;
68
+ /** Serializable form for storage/transport. */
69
+ readonly blob: TokenBlob;
70
+ /** Decoded value (cached); null when the token carries no sphere payment data. */
71
+ readonly value: SphereValue | null;
72
+ }
73
+ interface MintParams {
74
+ /** Recipient's 33-byte compressed chain pubkey; engine derives the predicate. */
75
+ readonly recipientPubkey: Uint8Array;
76
+ /** Value to embed in the mint; null mints a value-less token. */
77
+ readonly value?: SphereValue | null;
78
+ }
79
+ /**
80
+ * Mint a NON-value (data) token: arbitrary opaque `data` (e.g. serialized invoice
81
+ * terms), a custom `tokenType`, and a deterministic `salt` → a stable,
82
+ * terms-derived `tokenId`. The minted token has `value === null` (it carries data,
83
+ * not coins); read the bytes back with `readTokenData`.
84
+ */
85
+ interface MintDataTokenParams {
86
+ readonly recipientPubkey: Uint8Array;
87
+ /** Opaque token payload (the engine does not interpret it). */
88
+ readonly data: Uint8Array;
89
+ /** Token type bytes; defaults to a random type when omitted. */
90
+ readonly tokenType?: Uint8Array;
91
+ /** Salt bytes; deterministic salt → deterministic (terms-derived) tokenId. */
92
+ readonly salt?: Uint8Array;
93
+ }
94
+ interface TransferParams {
95
+ /** The token to spend (must be owned by this engine's identity). */
96
+ readonly token: SphereToken;
97
+ /** Recipient's 33-byte compressed chain pubkey. */
98
+ readonly recipientPubkey: Uint8Array;
99
+ /** Optional opaque on-chain memo carried on the transfer (read back via `readMemo`). */
100
+ readonly data?: Uint8Array;
101
+ }
102
+ /**
103
+ * One split output = one single-coin token. To split a multi-coin token, emit
104
+ * one output per coin (the recipient receives the value as several tokens; the
105
+ * SDK enforces per-coin conservation). If a single multi-coin output token is
106
+ * ever needed, generalize this to `assets: readonly SphereAsset[]` (additive).
107
+ */
108
+ interface SplitOutput {
109
+ readonly recipientPubkey: Uint8Array;
110
+ readonly coinId: CoinId;
111
+ readonly amount: bigint;
112
+ /** Optional opaque memo carried in this output's value envelope (read back via `readMemo`). */
113
+ readonly data?: Uint8Array;
114
+ }
115
+ interface SplitParams {
116
+ /** The token to split (its total per coin must equal the sum of outputs). */
117
+ readonly token: SphereToken;
118
+ /** Desired outputs; value conservation is enforced by the SDK split. */
119
+ readonly outputs: readonly SplitOutput[];
120
+ }
121
+ interface SplitResult {
122
+ /**
123
+ * One minted token per requested output, **index-aligned with
124
+ * `SplitParams.outputs`** — `outputs[i]` is the token for `params.outputs[i]`
125
+ * (so a payee/change split can rely on positional order). Guaranteed by both
126
+ * the real engine and FakeTokenEngine.
127
+ */
128
+ readonly outputs: readonly SphereToken[];
129
+ }
130
+ /** Verification outcome, flattened to sphere-domain (no SDK status enum leaks). */
131
+ interface EngineVerifyResult {
132
+ readonly ok: boolean;
133
+ /** Human-readable reason when `ok` is false (mapped from the SDK verification status). */
134
+ readonly reason?: string;
135
+ }
136
+
137
+ /**
138
+ * token-engine/engine.ts — the FROZEN public port (ITokenEngine) + its config.
139
+ *
140
+ * This is the contract both migration tracks build against. It is sphere-domain
141
+ * only (see types.ts). The granular, SDK-typed steps (buildMint, submit,
142
+ * awaitProof, certify, …) are an INTERNAL concern of the real adapter and are
143
+ * intentionally NOT part of this public interface.
144
+ */
145
+
146
+ /** Options common to the long-running, network-bound operations. */
147
+ interface EngineOpOptions {
148
+ /** Cancels the operation (including inclusion-proof polling). */
149
+ readonly signal?: AbortSignal;
150
+ }
151
+ /**
152
+ * The token engine port. The wallet's secp256k1 identity, the target network,
153
+ * the aggregator client and the trust base are all bound at construction
154
+ * (see EngineConfig); operations below take only sphere-domain arguments.
155
+ */
156
+ interface ITokenEngine {
157
+ /** This engine's wallet identity (chain pubkey). Synchronous. */
158
+ getIdentity(): EngineIdentity;
159
+ /**
160
+ * Legacy `DIRECT://` address for the given pubkey (defaults to this engine's
161
+ * identity). This is the ONLY "address" in v2 and is kept stable across the
162
+ * migration (Path A) so Quest XP / Unicity IDs keyed on it survive. Async —
163
+ * the derivation hashes via the SDK.
164
+ */
165
+ deriveIdentityAddress(pubkey?: Uint8Array): Promise<string>;
166
+ /**
167
+ * Genesis-stable token id — 64-char lowercase hex of the v2 TokenId (same
168
+ * across every state). Use for dedup / history / tombstone keys. Synchronous.
169
+ */
170
+ tokenId(token: SphereToken): string;
171
+ /** Decoded value of a token (cached). Synchronous. */
172
+ readValue(token: SphereToken): SphereValue | null;
173
+ /** Balance of a single coin within a token. Synchronous. */
174
+ balanceOf(token: SphereToken, coinId: CoinId): bigint;
175
+ /**
176
+ * The opaque on-chain memo delivered with this token: the latest transfer's
177
+ * data for a transferred token, else the memo in a minted output's value
178
+ * envelope (split). Returns `null` when there is no memo — including for data
179
+ * tokens (no value envelope; use `readTokenData`) and memo-less value tokens.
180
+ * To tell a data token from a value token, check `readValue` (null ⇒
181
+ * data/value-less token). Synchronous.
182
+ */
183
+ readMemo(token: SphereToken): Uint8Array | null;
184
+ /** Raw genesis data of a token (e.g. a data-token's terms). `null` when absent. Synchronous. */
185
+ readTokenData(token: SphereToken): Uint8Array | null;
186
+ /**
187
+ * Mint (issue) a new token to a recipient pubkey. NOT a wallet end-user flow —
188
+ * this is the issuer/developer capability: an app issuing its own tokens
189
+ * (rewards, in-app currency, tickets) to users, or seeding test balances. v2
190
+ * makes standalone mint first-class (Token.mint accepts a genesis with a null
191
+ * justification). Split's per-output mint is a separate, internal path; the
192
+ * Unicity-ID/nametag mint is a distinct identity surface (see migration plan §4.4).
193
+ */
194
+ mint(params: MintParams, options?: EngineOpOptions): Promise<SphereToken>;
195
+ /**
196
+ * Mint a NON-value (data) token: opaque `data` + custom `tokenType` + deterministic
197
+ * `salt` → a stable, terms-derived `tokenId`. The result has `value === null`;
198
+ * read its bytes via `readTokenData`. (Used e.g. for on-chain invoice tokens.)
199
+ */
200
+ mintDataToken(params: MintDataTokenParams, options?: EngineOpOptions): Promise<SphereToken>;
201
+ /** Spend a token wholesale to a recipient pubkey; returns the recipient's finished token. */
202
+ transfer(params: TransferParams, options?: EngineOpOptions): Promise<SphereToken>;
203
+ /** Split a token into N value-conserving outputs (burn source + internally mint each output). */
204
+ split(params: SplitParams, options?: EngineOpOptions): Promise<SplitResult>;
205
+ /** Fully verify a token against the trust base. */
206
+ verify(token: SphereToken, options?: EngineOpOptions): Promise<EngineVerifyResult>;
207
+ /** Whether the token's current state has already been spent on the network. */
208
+ isSpent(token: SphereToken, options?: EngineOpOptions): Promise<boolean>;
209
+ /**
210
+ * Whether the token's CURRENT state is locked to `SignaturePredicate(pubkey)`.
211
+ * Local + synchronous (predicate byte-compare, no network). The receive path
212
+ * uses it to reject tokens that are not actually addressed to this wallet.
213
+ */
214
+ isOwnedBy(token: SphereToken, pubkey: Uint8Array): boolean;
215
+ /** Serialize a token for storage/transport. Synchronous. */
216
+ encodeToken(token: SphereToken): TokenBlob;
217
+ /** Reconstruct a token from its blob (decodes embedded payment data). */
218
+ decodeToken(blob: TokenBlob): Promise<SphereToken>;
219
+ }
220
+ /**
221
+ * Engine construction config. Sphere-domain inputs only: the factory maps
222
+ * `network` → SDK NetworkId, builds the aggregator client from `aggregatorUrl`,
223
+ * the signing service from `privateKey`, and loads the trust base internally.
224
+ *
225
+ * NOTE: trust-base sourcing + proof-policy defaults are finalized in Phase 0.8;
226
+ * this shape may gain fields there without affecting the ITokenEngine contract.
227
+ */
228
+ interface EngineConfig {
229
+ /** Aggregator (gateway) base URL the StateTransitionClient talks to. */
230
+ readonly aggregatorUrl: string;
231
+ /** Optional gateway API key (some gateways, e.g. testnet2, require it for auth). */
232
+ readonly apiKey?: string;
233
+ /** Wallet signing key (secp256k1 private scalar, 32 bytes). Held inside the engine only. */
234
+ readonly privateKey: Uint8Array;
235
+ /**
236
+ * Root-trust-base JSON. The single source of truth for the network — the engine's
237
+ * NetworkId is taken from it (`RootTrustBase.networkId` via `NetworkId.fromId`), so
238
+ * any network id works (e.g. testnet2 = 4) with no enum entry. Typed `unknown` to
239
+ * keep SDK types off the public surface (the factory parses it internally).
240
+ */
241
+ readonly trustBaseJson: unknown;
242
+ /** Inclusion-proof poll cadence in ms (engine owns the await policy; Spike S1). */
243
+ readonly proofPollIntervalMs?: number;
244
+ /** Inclusion-proof overall timeout in ms (0/undefined = no engine-side cap). */
245
+ readonly proofTimeoutMs?: number;
246
+ }
247
+ /** Factory signature for the real adapter (implemented in Track A). */
248
+ type CreateTokenEngine = (config: EngineConfig) => Promise<ITokenEngine>;
249
+
250
+ /**
251
+ * Derive the legacy `DIRECT://` identity address for a compressed (33-byte)
252
+ * secp256k1 public key. Deterministic; byte-identical to the v1 path (Path A).
253
+ */
254
+ declare function deriveDirectAddress(publicKey: Uint8Array): Promise<string>;
255
+
256
+ /**
257
+ * token-engine/factory.ts — the real engine constructor (A4).
258
+ *
259
+ * `createSphereTokenEngine` is the public way to obtain an ITokenEngine. It maps
260
+ * the sphere-domain EngineConfig to the SDK objects the engine needs: the
261
+ * aggregator client (from `aggregatorUrl`), the trust base (parsed from
262
+ * `trustBaseJson`), the wallet signing key (from `privateKey`), the network id,
263
+ * and a mint-justification verifier with the split verifier registered (so
264
+ * split-output tokens verify).
265
+ *
266
+ * Loading the trust base per environment (browser fetch / node file) stays with
267
+ * the caller (impl/<env>/oracle, reusing the existing trust-base loaders); it
268
+ * passes the parsed JSON in via `trustBaseJson`, keeping this factory env-agnostic.
269
+ */
270
+
271
+ declare function createSphereTokenEngine(config: EngineConfig): Promise<ITokenEngine>;
272
+
273
+ /**
274
+ * token-engine/unicity-id.ts — self-issued v2 UnicityIdToken mint (the v2 analog
275
+ * of the v1 nametag-token mint).
276
+ *
277
+ * User decision 2026-06-10: the on-chain Unicity ID claim is minted AND STORED
278
+ * again at nametag registration (it was retired with v1 in Track B / D5), but it
279
+ * is NOT used at runtime — name resolution stays Nostr-binding-only, receive
280
+ * stays SignaturePredicate(chainPubkey), and no PROXY semantics return. The
281
+ * token is kept for the future (e.g. an issuer/verification model).
282
+ *
283
+ * Trust model: SELF-ISSUED. The wallet's own key is the issuer lock script, the
284
+ * recipient AND the target predicate (the v2 local-mint path — the issuer pin is
285
+ * only meaningful when verifying third-party tokens). Because the issuer lock
286
+ * script is part of the StateId, on-chain uniqueness is per-issuer only; GLOBAL
287
+ * name uniqueness remains the Nostr first-seen-wins binding's job (unchanged).
288
+ *
289
+ * Determinism / idempotency: tokenId = SHA256(CBOR["NAMETAG_", null, name]),
290
+ * tokenType is pinned (UNICITY_TOKEN_TYPE_HEX), and all predicates derive from
291
+ * the wallet key — the whole mint transaction is reproducible byte-for-byte, so
292
+ * a re-mint (e.g. after the token was lost from local storage) re-certifies the
293
+ * same state and yields the identical token (the v1 REQUEST_ID_EXISTS recovery
294
+ * analog).
295
+ */
296
+
297
+ interface UnicityIdMintResult {
298
+ /** UnicityIdToken CBOR, hex-encoded — the storable form (UnicityIdToken.fromCBOR round-trips it). */
299
+ readonly tokenCborHex: string;
300
+ /** 64-char hex token id, derived from the name (stable across re-mints). */
301
+ readonly tokenId: string;
302
+ }
303
+ /** Self-issued Unicity ID (nametag) token minter. */
304
+ interface IUnicityIdMinter {
305
+ /**
306
+ * Mint (or idempotently re-certify) the UnicityIdToken for `name`,
307
+ * self-issued by this wallet's key. Network-bound: submits the certification
308
+ * request and waits for the inclusion proof.
309
+ */
310
+ mintUnicityIdToken(name: string, options?: EngineOpOptions): Promise<UnicityIdMintResult>;
311
+ }
312
+ /**
313
+ * Build the self-issued Unicity ID minter from the same config the token engine
314
+ * uses (trust base JSON + gateway URL + API key + wallet key).
315
+ */
316
+ declare function createUnicityIdMinter(config: EngineConfig): IUnicityIdMinter;
317
+
318
+ /**
319
+ * token-engine/SpherePaymentData.ts — the sphere value model.
320
+ *
321
+ * v2 `Token` carries no coins; value is app-defined and stored in
322
+ * `MintTransaction.data`. `SpherePaymentData` is that payload: it implements the
323
+ * SDK's `IPaymentData` (so `TokenSplit` can read it for value conservation) and
324
+ * encodes a `PaymentAssetCollection` inside a versioned, tagged CBOR envelope.
325
+ *
326
+ * The SDK never inspects our raw bytes — it only calls `decodePaymentData(data)`
327
+ * and reads `.assets` — so the envelope (tag + version) is ours, chosen for
328
+ * forward-compatible storage. `fromValue`/`toValue` bridge sphere-domain values
329
+ * (hex coin id + bigint amount) to/from the SDK asset collection.
330
+ */
331
+
332
+ /** Validate a sphere-domain asset and build the SDK Asset — the single validation point. */
333
+ declare function sphereAssetToSdk(coinId: CoinId, amount: bigint): Asset;
334
+ declare class SpherePaymentData implements IPaymentData {
335
+ readonly assets: PaymentAssetCollection;
336
+ private readonly _memo;
337
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
338
+ static readonly CBOR_TAG = 39050n;
339
+ /** Envelope version; bump when the structure changes. */
340
+ static readonly VERSION = 1n;
341
+ private constructor();
342
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
343
+ get memo(): Uint8Array | null;
344
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
345
+ static create(assets: PaymentAssetCollection, memo?: Uint8Array | null): SpherePaymentData;
346
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
347
+ static fromValue(value: SphereValue, memo?: Uint8Array | null): SpherePaymentData;
348
+ /** Decode from the CBOR envelope produced by {@link encode}. */
349
+ static fromCBOR(bytes: Uint8Array): SpherePaymentData;
350
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
351
+ encode(): Promise<Uint8Array>;
352
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
353
+ toValue(): SphereValue;
354
+ /** Balance of a single coin within this payload (0n when absent). */
355
+ balanceOf(coinId: CoinId): bigint;
356
+ }
357
+ /**
358
+ * Async payment-data decoder matching the SDK's `decodePaymentData` signature.
359
+ * Used by `TokenSplit.split` (value conservation) and `SplitMintJustificationVerifier`.
360
+ */
361
+ declare function decodeSpherePaymentData(bytes: Uint8Array): Promise<IPaymentData>;
362
+
363
+ export { type CoinId, type CreateTokenEngine, type EngineConfig, type EngineIdentity, type EngineOpOptions, type EngineVerifyResult, type ITokenEngine, type IUnicityIdMinter, type MintDataTokenParams, type MintParams, type SphereAsset, type SphereNetwork, SpherePaymentData, type SphereToken, type SphereValue, type SplitOutput, type SplitParams, type SplitResult, type TokenBlob, type TransferParams, type UnicityIdMintResult, createSphereTokenEngine, createUnicityIdMinter, decodeSpherePaymentData, deriveDirectAddress, sphereAssetToSdk };