@unicitylabs/sphere-sdk 0.9.1-dev.3 → 0.9.1-dev.4

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,427 @@
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
+ * Realization seed for deterministic transfer/split (Part E, sdk-changes E.1/E.3):
152
+ * a client-generated UUIDv4 in canonical lowercase string form. Every value the
153
+ * transaction binds to (stateMask, per-output salts) is HKDF-derived from the
154
+ * wallet key + this id, so re-calling the op with the same `transferId` and
155
+ * inputs rebuilds the byte-identical transaction and resumes an interrupted
156
+ * attempt instead of losing funds. Persist it BEFORE calling the engine.
157
+ *
158
+ * If absent, the engine generates one internally (`crypto.randomUUID()`) — the
159
+ * derivation path is identical, but the call is NOT resumable (the seed is
160
+ * gone if the process dies mid-op).
161
+ */
162
+ readonly transferId?: string;
163
+ }
164
+ /**
165
+ * The token engine port. The wallet's secp256k1 identity, the target network,
166
+ * the aggregator client and the trust base are all bound at construction
167
+ * (see EngineConfig); operations below take only sphere-domain arguments.
168
+ */
169
+ interface ITokenEngine {
170
+ /** This engine's wallet identity (chain pubkey). Synchronous. */
171
+ getIdentity(): EngineIdentity;
172
+ /**
173
+ * Legacy `DIRECT://` address for the given pubkey (defaults to this engine's
174
+ * identity). This is the ONLY "address" in v2 and is kept stable across the
175
+ * migration (Path A) so Quest XP / Unicity IDs keyed on it survive. Async —
176
+ * the derivation hashes via the SDK.
177
+ */
178
+ deriveIdentityAddress(pubkey?: Uint8Array): Promise<string>;
179
+ /**
180
+ * Genesis-stable token id — 64-char lowercase hex of the v2 TokenId (same
181
+ * across every state). Use for dedup / history / tombstone keys. Synchronous.
182
+ */
183
+ tokenId(token: SphereToken): string;
184
+ /** Decoded value of a token (cached). Synchronous. */
185
+ readValue(token: SphereToken): SphereValue | null;
186
+ /** Balance of a single coin within a token. Synchronous. */
187
+ balanceOf(token: SphereToken, coinId: CoinId): bigint;
188
+ /**
189
+ * The opaque on-chain memo delivered with this token: the latest transfer's
190
+ * data for a transferred token, else the memo in a minted output's value
191
+ * envelope (split). Returns `null` when there is no memo — including for data
192
+ * tokens (no value envelope; use `readTokenData`) and memo-less value tokens.
193
+ * To tell a data token from a value token, check `readValue` (null ⇒
194
+ * data/value-less token). Synchronous.
195
+ */
196
+ readMemo(token: SphereToken): Uint8Array | null;
197
+ /** Raw genesis data of a token (e.g. a data-token's terms). `null` when absent. Synchronous. */
198
+ readTokenData(token: SphereToken): Uint8Array | null;
199
+ /**
200
+ * Mint (issue) a new token to a recipient pubkey. NOT a wallet end-user flow —
201
+ * this is the issuer/developer capability: an app issuing its own tokens
202
+ * (rewards, in-app currency, tickets) to users, or seeding test balances. v2
203
+ * makes standalone mint first-class (Token.mint accepts a genesis with a null
204
+ * justification). Split's per-output mint is a separate, internal path; the
205
+ * Unicity-ID/nametag mint is a distinct identity surface (see migration plan §4.4).
206
+ */
207
+ mint(params: MintParams, options?: EngineOpOptions): Promise<SphereToken>;
208
+ /**
209
+ * Mint a NON-value (data) token: opaque `data` + custom `tokenType` + deterministic
210
+ * `salt` → a stable, terms-derived `tokenId`. The result has `value === null`;
211
+ * read its bytes via `readTokenData`. (Used e.g. for on-chain invoice tokens.)
212
+ */
213
+ mintDataToken(params: MintDataTokenParams, options?: EngineOpOptions): Promise<SphereToken>;
214
+ /** Spend a token wholesale to a recipient pubkey; returns the recipient's finished token. */
215
+ transfer(params: TransferParams, options?: EngineOpOptions): Promise<SphereToken>;
216
+ /** Split a token into N value-conserving outputs (burn source + internally mint each output). */
217
+ split(params: SplitParams, options?: EngineOpOptions): Promise<SplitResult>;
218
+ /** Fully verify a token against the trust base. */
219
+ verify(token: SphereToken, options?: EngineOpOptions): Promise<EngineVerifyResult>;
220
+ /** Whether the token's current state has already been spent on the network. */
221
+ isSpent(token: SphereToken, options?: EngineOpOptions): Promise<boolean>;
222
+ /**
223
+ * Whether the token's CURRENT state is locked to `SignaturePredicate(pubkey)`.
224
+ * Local + synchronous (predicate byte-compare, no network). The receive path
225
+ * uses it to reject tokens that are not actually addressed to this wallet.
226
+ */
227
+ isOwnedBy(token: SphereToken, pubkey: Uint8Array): boolean;
228
+ /** Serialize a token for storage/transport. Synchronous. */
229
+ encodeToken(token: SphereToken): TokenBlob;
230
+ /** Reconstruct a token from its blob (decodes embedded payment data). */
231
+ decodeToken(blob: TokenBlob): Promise<SphereToken>;
232
+ }
233
+ /**
234
+ * Engine construction config. Sphere-domain inputs only: the factory maps
235
+ * `network` → SDK NetworkId, builds the aggregator client from `aggregatorUrl`,
236
+ * the signing service from `privateKey`, and loads the trust base internally.
237
+ *
238
+ * NOTE: trust-base sourcing + proof-policy defaults are finalized in Phase 0.8;
239
+ * this shape may gain fields there without affecting the ITokenEngine contract.
240
+ */
241
+ interface EngineConfig {
242
+ /** Aggregator (gateway) base URL the StateTransitionClient talks to. */
243
+ readonly aggregatorUrl: string;
244
+ /** Optional gateway API key (some gateways, e.g. testnet2, require it for auth). */
245
+ readonly apiKey?: string;
246
+ /** Wallet signing key (secp256k1 private scalar, 32 bytes). Held inside the engine only. */
247
+ readonly privateKey: Uint8Array;
248
+ /**
249
+ * Root-trust-base JSON. The single source of truth for the network — the engine's
250
+ * NetworkId is taken from it (`RootTrustBase.networkId` via `NetworkId.fromId`), so
251
+ * any network id works (e.g. testnet2 = 4) with no enum entry. Typed `unknown` to
252
+ * keep SDK types off the public surface (the factory parses it internally).
253
+ */
254
+ readonly trustBaseJson: unknown;
255
+ /** Inclusion-proof poll cadence in ms (engine owns the await policy; Spike S1). */
256
+ readonly proofPollIntervalMs?: number;
257
+ /** Inclusion-proof overall timeout in ms (0/undefined = no engine-side cap). */
258
+ readonly proofTimeoutMs?: number;
259
+ }
260
+ /** Factory signature for the real adapter (implemented in Track A). */
261
+ type CreateTokenEngine = (config: EngineConfig) => Promise<ITokenEngine>;
262
+
263
+ /**
264
+ * Derive the legacy `DIRECT://` identity address for a compressed (33-byte)
265
+ * secp256k1 public key. Deterministic; byte-identical to the v1 path (Path A).
266
+ */
267
+ declare function deriveDirectAddress(publicKey: Uint8Array): Promise<string>;
268
+
269
+ /**
270
+ * token-engine/factory.ts — the real engine constructor (A4).
271
+ *
272
+ * `createSphereTokenEngine` is the public way to obtain an ITokenEngine. It maps
273
+ * the sphere-domain EngineConfig to the SDK objects the engine needs: the
274
+ * aggregator client (from `aggregatorUrl`), the trust base (parsed from
275
+ * `trustBaseJson`), the wallet signing key (from `privateKey`), the network id,
276
+ * and a mint-justification verifier with the split verifier registered (so
277
+ * split-output tokens verify).
278
+ *
279
+ * Loading the trust base per environment (browser fetch / node file) stays with
280
+ * the caller (impl/<env>/oracle, reusing the existing trust-base loaders); it
281
+ * passes the parsed JSON in via `trustBaseJson`, keeping this factory env-agnostic.
282
+ */
283
+
284
+ declare function createSphereTokenEngine(config: EngineConfig): Promise<ITokenEngine>;
285
+
286
+ /**
287
+ * SDK Error Types
288
+ *
289
+ * Structured error codes for programmatic error handling in UI.
290
+ * UI can switch on error.code to show appropriate user-facing messages.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * import { SphereError } from '@unicitylabs/sphere-sdk';
295
+ *
296
+ * try {
297
+ * await sphere.payments.send({ ... });
298
+ * } catch (err) {
299
+ * if (err instanceof SphereError) {
300
+ * switch (err.code) {
301
+ * case 'INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;
302
+ * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;
303
+ * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;
304
+ * case 'TIMEOUT': showToast('Request timed out, try again'); break;
305
+ * default: showToast(err.message);
306
+ * }
307
+ * }
308
+ * }
309
+ * ```
310
+ */
311
+ type SphereErrorCode = 'NOT_INITIALIZED' | 'ALREADY_INITIALIZED' | 'INVALID_CONFIG' | 'INVALID_IDENTITY' | 'INSUFFICIENT_BALANCE' | 'INVALID_RECIPIENT' | 'TRANSFER_FAILED' | 'TRANSFER_CONFLICT' | 'STORAGE_ERROR' | 'TRANSPORT_ERROR' | 'AGGREGATOR_ERROR' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'DECRYPTION_ERROR' | 'MODULE_NOT_AVAILABLE' | 'SIGNING_ERROR' | 'SEND_QUEUE_TIMEOUT' | 'SEND_INSUFFICIENT_BALANCE' | 'SEND_RESERVATION_CANCELLED' | 'SEND_QUEUE_FULL' | 'MODULE_DESTROYED' | 'REENTRANT_GATE' | 'INVOICE_NO_TARGETS' | 'INVOICE_INVALID_ADDRESS' | 'INVOICE_NO_ASSETS' | 'INVOICE_INVALID_ASSET' | 'INVOICE_INVALID_AMOUNT' | 'INVOICE_INVALID_COIN' | 'INVOICE_INVALID_NFT' | 'INVOICE_PAST_DUE_DATE' | 'INVOICE_DUPLICATE_ADDRESS' | 'INVOICE_DUPLICATE_COIN' | 'INVOICE_DUPLICATE_NFT' | 'INVOICE_MINT_FAILED' | 'INVOICE_INVALID_PROOF' | 'INVOICE_WRONG_TOKEN_TYPE' | 'INVOICE_INVALID_DATA' | 'INVOICE_ALREADY_EXISTS' | 'INVOICE_NOT_FOUND' | 'INVOICE_NOT_TARGET' | 'INVOICE_ALREADY_CLOSED' | 'INVOICE_ALREADY_CANCELLED' | 'INVOICE_ORACLE_REQUIRED' | 'INVOICE_TERMINATED' | 'INVOICE_INVALID_TARGET' | 'INVOICE_INVALID_ASSET_INDEX' | 'INVOICE_RETURN_EXCEEDS_BALANCE' | 'INVOICE_INVALID_DELIVERY_METHOD' | 'INVOICE_INVALID_REFUND_ADDRESS' | 'INVOICE_INVALID_CONTACT' | 'INVOICE_INVALID_ID' | 'INVOICE_TOO_MANY_TARGETS' | 'INVOICE_TOO_MANY_ASSETS' | 'INVOICE_MEMO_TOO_LONG' | 'INVOICE_TERMS_TOO_LARGE' | 'INVOICE_NOT_TERMINATED' | 'INVOICE_NOT_CANCELLED' | 'INVOICE_STORAGE_FAILED' | 'RATE_LIMITED' | 'COMMUNICATIONS_UNAVAILABLE' | 'SWAP_INVALID_DEAL' | 'SWAP_INVALID_MANIFEST' | 'SWAP_NOT_FOUND' | 'SWAP_WRONG_STATE' | 'SWAP_RESOLVE_FAILED' | 'SWAP_DM_SEND_FAILED' | 'SWAP_ESCROW_REJECTED' | 'SWAP_DEPOSIT_FAILED' | 'SWAP_PAYOUT_VERIFICATION_FAILED' | 'SWAP_ALREADY_EXISTS' | 'SWAP_ALREADY_COMPLETED' | 'SWAP_ALREADY_CANCELLED' | 'SWAP_TIMEOUT' | 'SWAP_LIMIT_EXCEEDED' | 'SWAP_ALREADY_INITIALIZED' | 'SWAP_MODULE_DESTROYED' | 'SWAP_NOT_INITIALIZED';
312
+ declare class SphereError extends Error {
313
+ readonly code: SphereErrorCode;
314
+ readonly cause?: unknown;
315
+ constructor(message: string, code: SphereErrorCode, cause?: unknown);
316
+ }
317
+
318
+ /**
319
+ * token-engine/errors.ts — the engine's typed error surface (Part E.2).
320
+ */
321
+
322
+ /**
323
+ * The source state was already consumed by a *different* transaction — the
324
+ * fetched inclusion proof does not match the rebuilt transaction
325
+ * (`TRANSACTION_HASH_MISMATCH`). Typically the owner's other device raced the
326
+ * same source under a different `transferId` (ARCHITECTURE §7).
327
+ *
328
+ * This is a lost race, **not** an interrupted resume: the engine never applies
329
+ * the foreign proof and never retries. The caller's recovery is to abort the
330
+ * intent, drop the lost source from its plan, and re-plan the remainder under
331
+ * a NEW `transferId` (never reusing the old realization) — sdk-changes E.2.
332
+ */
333
+ declare class TransferConflictError extends SphereError {
334
+ constructor(message: string, cause?: unknown);
335
+ }
336
+
337
+ /**
338
+ * token-engine/unicity-id.ts — self-issued v2 UnicityIdToken mint (the v2 analog
339
+ * of the v1 nametag-token mint).
340
+ *
341
+ * User decision 2026-06-10: the on-chain Unicity ID claim is minted AND STORED
342
+ * again at nametag registration (it was retired with v1 in Track B / D5), but it
343
+ * is NOT used at runtime — name resolution stays Nostr-binding-only, receive
344
+ * stays SignaturePredicate(chainPubkey), and no PROXY semantics return. The
345
+ * token is kept for the future (e.g. an issuer/verification model).
346
+ *
347
+ * Trust model: SELF-ISSUED. The wallet's own key is the issuer lock script, the
348
+ * recipient AND the target predicate (the v2 local-mint path — the issuer pin is
349
+ * only meaningful when verifying third-party tokens). Because the issuer lock
350
+ * script is part of the StateId, on-chain uniqueness is per-issuer only; GLOBAL
351
+ * name uniqueness remains the Nostr first-seen-wins binding's job (unchanged).
352
+ *
353
+ * Determinism / idempotency: tokenId = SHA256(CBOR["NAMETAG_", null, name]),
354
+ * tokenType is pinned (UNICITY_TOKEN_TYPE_HEX), and all predicates derive from
355
+ * the wallet key — the whole mint transaction is reproducible byte-for-byte, so
356
+ * a re-mint (e.g. after the token was lost from local storage) re-certifies the
357
+ * same state and yields the identical token (the v1 REQUEST_ID_EXISTS recovery
358
+ * analog).
359
+ */
360
+
361
+ interface UnicityIdMintResult {
362
+ /** UnicityIdToken CBOR, hex-encoded — the storable form (UnicityIdToken.fromCBOR round-trips it). */
363
+ readonly tokenCborHex: string;
364
+ /** 64-char hex token id, derived from the name (stable across re-mints). */
365
+ readonly tokenId: string;
366
+ }
367
+ /** Self-issued Unicity ID (nametag) token minter. */
368
+ interface IUnicityIdMinter {
369
+ /**
370
+ * Mint (or idempotently re-certify) the UnicityIdToken for `name`,
371
+ * self-issued by this wallet's key. Network-bound: submits the certification
372
+ * request and waits for the inclusion proof.
373
+ */
374
+ mintUnicityIdToken(name: string, options?: EngineOpOptions): Promise<UnicityIdMintResult>;
375
+ }
376
+ /**
377
+ * Build the self-issued Unicity ID minter from the same config the token engine
378
+ * uses (trust base JSON + gateway URL + API key + wallet key).
379
+ */
380
+ declare function createUnicityIdMinter(config: EngineConfig): IUnicityIdMinter;
381
+
382
+ /**
383
+ * token-engine/SpherePaymentData.ts — the sphere value model.
384
+ *
385
+ * v2 `Token` carries no coins; value is app-defined and stored in
386
+ * `MintTransaction.data`. `SpherePaymentData` is that payload: it implements the
387
+ * SDK's `IPaymentData` (so `TokenSplit` can read it for value conservation) and
388
+ * encodes a `PaymentAssetCollection` inside a versioned, tagged CBOR envelope.
389
+ *
390
+ * The SDK never inspects our raw bytes — it only calls `decodePaymentData(data)`
391
+ * and reads `.assets` — so the envelope (tag + version) is ours, chosen for
392
+ * forward-compatible storage. `fromValue`/`toValue` bridge sphere-domain values
393
+ * (hex coin id + bigint amount) to/from the SDK asset collection.
394
+ */
395
+
396
+ /** Validate a sphere-domain asset and build the SDK Asset — the single validation point. */
397
+ declare function sphereAssetToSdk(coinId: CoinId, amount: bigint): Asset;
398
+ declare class SpherePaymentData implements IPaymentData {
399
+ readonly assets: PaymentAssetCollection;
400
+ private readonly _memo;
401
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
402
+ static readonly CBOR_TAG = 39050n;
403
+ /** Envelope version; bump when the structure changes. */
404
+ static readonly VERSION = 1n;
405
+ private constructor();
406
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
407
+ get memo(): Uint8Array | null;
408
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
409
+ static create(assets: PaymentAssetCollection, memo?: Uint8Array | null): SpherePaymentData;
410
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
411
+ static fromValue(value: SphereValue, memo?: Uint8Array | null): SpherePaymentData;
412
+ /** Decode from the CBOR envelope produced by {@link encode}. */
413
+ static fromCBOR(bytes: Uint8Array): SpherePaymentData;
414
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
415
+ encode(): Promise<Uint8Array>;
416
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
417
+ toValue(): SphereValue;
418
+ /** Balance of a single coin within this payload (0n when absent). */
419
+ balanceOf(coinId: CoinId): bigint;
420
+ }
421
+ /**
422
+ * Async payment-data decoder matching the SDK's `decodePaymentData` signature.
423
+ * Used by `TokenSplit.split` (value conservation) and `SplitMintJustificationVerifier`.
424
+ */
425
+ declare function decodeSpherePaymentData(bytes: Uint8Array): Promise<IPaymentData>;
426
+
427
+ 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, TransferConflictError, type TransferParams, type UnicityIdMintResult, createSphereTokenEngine, createUnicityIdMinter, decodeSpherePaymentData, deriveDirectAddress, sphereAssetToSdk };
@@ -0,0 +1,427 @@
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
+ * Realization seed for deterministic transfer/split (Part E, sdk-changes E.1/E.3):
152
+ * a client-generated UUIDv4 in canonical lowercase string form. Every value the
153
+ * transaction binds to (stateMask, per-output salts) is HKDF-derived from the
154
+ * wallet key + this id, so re-calling the op with the same `transferId` and
155
+ * inputs rebuilds the byte-identical transaction and resumes an interrupted
156
+ * attempt instead of losing funds. Persist it BEFORE calling the engine.
157
+ *
158
+ * If absent, the engine generates one internally (`crypto.randomUUID()`) — the
159
+ * derivation path is identical, but the call is NOT resumable (the seed is
160
+ * gone if the process dies mid-op).
161
+ */
162
+ readonly transferId?: string;
163
+ }
164
+ /**
165
+ * The token engine port. The wallet's secp256k1 identity, the target network,
166
+ * the aggregator client and the trust base are all bound at construction
167
+ * (see EngineConfig); operations below take only sphere-domain arguments.
168
+ */
169
+ interface ITokenEngine {
170
+ /** This engine's wallet identity (chain pubkey). Synchronous. */
171
+ getIdentity(): EngineIdentity;
172
+ /**
173
+ * Legacy `DIRECT://` address for the given pubkey (defaults to this engine's
174
+ * identity). This is the ONLY "address" in v2 and is kept stable across the
175
+ * migration (Path A) so Quest XP / Unicity IDs keyed on it survive. Async —
176
+ * the derivation hashes via the SDK.
177
+ */
178
+ deriveIdentityAddress(pubkey?: Uint8Array): Promise<string>;
179
+ /**
180
+ * Genesis-stable token id — 64-char lowercase hex of the v2 TokenId (same
181
+ * across every state). Use for dedup / history / tombstone keys. Synchronous.
182
+ */
183
+ tokenId(token: SphereToken): string;
184
+ /** Decoded value of a token (cached). Synchronous. */
185
+ readValue(token: SphereToken): SphereValue | null;
186
+ /** Balance of a single coin within a token. Synchronous. */
187
+ balanceOf(token: SphereToken, coinId: CoinId): bigint;
188
+ /**
189
+ * The opaque on-chain memo delivered with this token: the latest transfer's
190
+ * data for a transferred token, else the memo in a minted output's value
191
+ * envelope (split). Returns `null` when there is no memo — including for data
192
+ * tokens (no value envelope; use `readTokenData`) and memo-less value tokens.
193
+ * To tell a data token from a value token, check `readValue` (null ⇒
194
+ * data/value-less token). Synchronous.
195
+ */
196
+ readMemo(token: SphereToken): Uint8Array | null;
197
+ /** Raw genesis data of a token (e.g. a data-token's terms). `null` when absent. Synchronous. */
198
+ readTokenData(token: SphereToken): Uint8Array | null;
199
+ /**
200
+ * Mint (issue) a new token to a recipient pubkey. NOT a wallet end-user flow —
201
+ * this is the issuer/developer capability: an app issuing its own tokens
202
+ * (rewards, in-app currency, tickets) to users, or seeding test balances. v2
203
+ * makes standalone mint first-class (Token.mint accepts a genesis with a null
204
+ * justification). Split's per-output mint is a separate, internal path; the
205
+ * Unicity-ID/nametag mint is a distinct identity surface (see migration plan §4.4).
206
+ */
207
+ mint(params: MintParams, options?: EngineOpOptions): Promise<SphereToken>;
208
+ /**
209
+ * Mint a NON-value (data) token: opaque `data` + custom `tokenType` + deterministic
210
+ * `salt` → a stable, terms-derived `tokenId`. The result has `value === null`;
211
+ * read its bytes via `readTokenData`. (Used e.g. for on-chain invoice tokens.)
212
+ */
213
+ mintDataToken(params: MintDataTokenParams, options?: EngineOpOptions): Promise<SphereToken>;
214
+ /** Spend a token wholesale to a recipient pubkey; returns the recipient's finished token. */
215
+ transfer(params: TransferParams, options?: EngineOpOptions): Promise<SphereToken>;
216
+ /** Split a token into N value-conserving outputs (burn source + internally mint each output). */
217
+ split(params: SplitParams, options?: EngineOpOptions): Promise<SplitResult>;
218
+ /** Fully verify a token against the trust base. */
219
+ verify(token: SphereToken, options?: EngineOpOptions): Promise<EngineVerifyResult>;
220
+ /** Whether the token's current state has already been spent on the network. */
221
+ isSpent(token: SphereToken, options?: EngineOpOptions): Promise<boolean>;
222
+ /**
223
+ * Whether the token's CURRENT state is locked to `SignaturePredicate(pubkey)`.
224
+ * Local + synchronous (predicate byte-compare, no network). The receive path
225
+ * uses it to reject tokens that are not actually addressed to this wallet.
226
+ */
227
+ isOwnedBy(token: SphereToken, pubkey: Uint8Array): boolean;
228
+ /** Serialize a token for storage/transport. Synchronous. */
229
+ encodeToken(token: SphereToken): TokenBlob;
230
+ /** Reconstruct a token from its blob (decodes embedded payment data). */
231
+ decodeToken(blob: TokenBlob): Promise<SphereToken>;
232
+ }
233
+ /**
234
+ * Engine construction config. Sphere-domain inputs only: the factory maps
235
+ * `network` → SDK NetworkId, builds the aggregator client from `aggregatorUrl`,
236
+ * the signing service from `privateKey`, and loads the trust base internally.
237
+ *
238
+ * NOTE: trust-base sourcing + proof-policy defaults are finalized in Phase 0.8;
239
+ * this shape may gain fields there without affecting the ITokenEngine contract.
240
+ */
241
+ interface EngineConfig {
242
+ /** Aggregator (gateway) base URL the StateTransitionClient talks to. */
243
+ readonly aggregatorUrl: string;
244
+ /** Optional gateway API key (some gateways, e.g. testnet2, require it for auth). */
245
+ readonly apiKey?: string;
246
+ /** Wallet signing key (secp256k1 private scalar, 32 bytes). Held inside the engine only. */
247
+ readonly privateKey: Uint8Array;
248
+ /**
249
+ * Root-trust-base JSON. The single source of truth for the network — the engine's
250
+ * NetworkId is taken from it (`RootTrustBase.networkId` via `NetworkId.fromId`), so
251
+ * any network id works (e.g. testnet2 = 4) with no enum entry. Typed `unknown` to
252
+ * keep SDK types off the public surface (the factory parses it internally).
253
+ */
254
+ readonly trustBaseJson: unknown;
255
+ /** Inclusion-proof poll cadence in ms (engine owns the await policy; Spike S1). */
256
+ readonly proofPollIntervalMs?: number;
257
+ /** Inclusion-proof overall timeout in ms (0/undefined = no engine-side cap). */
258
+ readonly proofTimeoutMs?: number;
259
+ }
260
+ /** Factory signature for the real adapter (implemented in Track A). */
261
+ type CreateTokenEngine = (config: EngineConfig) => Promise<ITokenEngine>;
262
+
263
+ /**
264
+ * Derive the legacy `DIRECT://` identity address for a compressed (33-byte)
265
+ * secp256k1 public key. Deterministic; byte-identical to the v1 path (Path A).
266
+ */
267
+ declare function deriveDirectAddress(publicKey: Uint8Array): Promise<string>;
268
+
269
+ /**
270
+ * token-engine/factory.ts — the real engine constructor (A4).
271
+ *
272
+ * `createSphereTokenEngine` is the public way to obtain an ITokenEngine. It maps
273
+ * the sphere-domain EngineConfig to the SDK objects the engine needs: the
274
+ * aggregator client (from `aggregatorUrl`), the trust base (parsed from
275
+ * `trustBaseJson`), the wallet signing key (from `privateKey`), the network id,
276
+ * and a mint-justification verifier with the split verifier registered (so
277
+ * split-output tokens verify).
278
+ *
279
+ * Loading the trust base per environment (browser fetch / node file) stays with
280
+ * the caller (impl/<env>/oracle, reusing the existing trust-base loaders); it
281
+ * passes the parsed JSON in via `trustBaseJson`, keeping this factory env-agnostic.
282
+ */
283
+
284
+ declare function createSphereTokenEngine(config: EngineConfig): Promise<ITokenEngine>;
285
+
286
+ /**
287
+ * SDK Error Types
288
+ *
289
+ * Structured error codes for programmatic error handling in UI.
290
+ * UI can switch on error.code to show appropriate user-facing messages.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * import { SphereError } from '@unicitylabs/sphere-sdk';
295
+ *
296
+ * try {
297
+ * await sphere.payments.send({ ... });
298
+ * } catch (err) {
299
+ * if (err instanceof SphereError) {
300
+ * switch (err.code) {
301
+ * case 'INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;
302
+ * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;
303
+ * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;
304
+ * case 'TIMEOUT': showToast('Request timed out, try again'); break;
305
+ * default: showToast(err.message);
306
+ * }
307
+ * }
308
+ * }
309
+ * ```
310
+ */
311
+ type SphereErrorCode = 'NOT_INITIALIZED' | 'ALREADY_INITIALIZED' | 'INVALID_CONFIG' | 'INVALID_IDENTITY' | 'INSUFFICIENT_BALANCE' | 'INVALID_RECIPIENT' | 'TRANSFER_FAILED' | 'TRANSFER_CONFLICT' | 'STORAGE_ERROR' | 'TRANSPORT_ERROR' | 'AGGREGATOR_ERROR' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'DECRYPTION_ERROR' | 'MODULE_NOT_AVAILABLE' | 'SIGNING_ERROR' | 'SEND_QUEUE_TIMEOUT' | 'SEND_INSUFFICIENT_BALANCE' | 'SEND_RESERVATION_CANCELLED' | 'SEND_QUEUE_FULL' | 'MODULE_DESTROYED' | 'REENTRANT_GATE' | 'INVOICE_NO_TARGETS' | 'INVOICE_INVALID_ADDRESS' | 'INVOICE_NO_ASSETS' | 'INVOICE_INVALID_ASSET' | 'INVOICE_INVALID_AMOUNT' | 'INVOICE_INVALID_COIN' | 'INVOICE_INVALID_NFT' | 'INVOICE_PAST_DUE_DATE' | 'INVOICE_DUPLICATE_ADDRESS' | 'INVOICE_DUPLICATE_COIN' | 'INVOICE_DUPLICATE_NFT' | 'INVOICE_MINT_FAILED' | 'INVOICE_INVALID_PROOF' | 'INVOICE_WRONG_TOKEN_TYPE' | 'INVOICE_INVALID_DATA' | 'INVOICE_ALREADY_EXISTS' | 'INVOICE_NOT_FOUND' | 'INVOICE_NOT_TARGET' | 'INVOICE_ALREADY_CLOSED' | 'INVOICE_ALREADY_CANCELLED' | 'INVOICE_ORACLE_REQUIRED' | 'INVOICE_TERMINATED' | 'INVOICE_INVALID_TARGET' | 'INVOICE_INVALID_ASSET_INDEX' | 'INVOICE_RETURN_EXCEEDS_BALANCE' | 'INVOICE_INVALID_DELIVERY_METHOD' | 'INVOICE_INVALID_REFUND_ADDRESS' | 'INVOICE_INVALID_CONTACT' | 'INVOICE_INVALID_ID' | 'INVOICE_TOO_MANY_TARGETS' | 'INVOICE_TOO_MANY_ASSETS' | 'INVOICE_MEMO_TOO_LONG' | 'INVOICE_TERMS_TOO_LARGE' | 'INVOICE_NOT_TERMINATED' | 'INVOICE_NOT_CANCELLED' | 'INVOICE_STORAGE_FAILED' | 'RATE_LIMITED' | 'COMMUNICATIONS_UNAVAILABLE' | 'SWAP_INVALID_DEAL' | 'SWAP_INVALID_MANIFEST' | 'SWAP_NOT_FOUND' | 'SWAP_WRONG_STATE' | 'SWAP_RESOLVE_FAILED' | 'SWAP_DM_SEND_FAILED' | 'SWAP_ESCROW_REJECTED' | 'SWAP_DEPOSIT_FAILED' | 'SWAP_PAYOUT_VERIFICATION_FAILED' | 'SWAP_ALREADY_EXISTS' | 'SWAP_ALREADY_COMPLETED' | 'SWAP_ALREADY_CANCELLED' | 'SWAP_TIMEOUT' | 'SWAP_LIMIT_EXCEEDED' | 'SWAP_ALREADY_INITIALIZED' | 'SWAP_MODULE_DESTROYED' | 'SWAP_NOT_INITIALIZED';
312
+ declare class SphereError extends Error {
313
+ readonly code: SphereErrorCode;
314
+ readonly cause?: unknown;
315
+ constructor(message: string, code: SphereErrorCode, cause?: unknown);
316
+ }
317
+
318
+ /**
319
+ * token-engine/errors.ts — the engine's typed error surface (Part E.2).
320
+ */
321
+
322
+ /**
323
+ * The source state was already consumed by a *different* transaction — the
324
+ * fetched inclusion proof does not match the rebuilt transaction
325
+ * (`TRANSACTION_HASH_MISMATCH`). Typically the owner's other device raced the
326
+ * same source under a different `transferId` (ARCHITECTURE §7).
327
+ *
328
+ * This is a lost race, **not** an interrupted resume: the engine never applies
329
+ * the foreign proof and never retries. The caller's recovery is to abort the
330
+ * intent, drop the lost source from its plan, and re-plan the remainder under
331
+ * a NEW `transferId` (never reusing the old realization) — sdk-changes E.2.
332
+ */
333
+ declare class TransferConflictError extends SphereError {
334
+ constructor(message: string, cause?: unknown);
335
+ }
336
+
337
+ /**
338
+ * token-engine/unicity-id.ts — self-issued v2 UnicityIdToken mint (the v2 analog
339
+ * of the v1 nametag-token mint).
340
+ *
341
+ * User decision 2026-06-10: the on-chain Unicity ID claim is minted AND STORED
342
+ * again at nametag registration (it was retired with v1 in Track B / D5), but it
343
+ * is NOT used at runtime — name resolution stays Nostr-binding-only, receive
344
+ * stays SignaturePredicate(chainPubkey), and no PROXY semantics return. The
345
+ * token is kept for the future (e.g. an issuer/verification model).
346
+ *
347
+ * Trust model: SELF-ISSUED. The wallet's own key is the issuer lock script, the
348
+ * recipient AND the target predicate (the v2 local-mint path — the issuer pin is
349
+ * only meaningful when verifying third-party tokens). Because the issuer lock
350
+ * script is part of the StateId, on-chain uniqueness is per-issuer only; GLOBAL
351
+ * name uniqueness remains the Nostr first-seen-wins binding's job (unchanged).
352
+ *
353
+ * Determinism / idempotency: tokenId = SHA256(CBOR["NAMETAG_", null, name]),
354
+ * tokenType is pinned (UNICITY_TOKEN_TYPE_HEX), and all predicates derive from
355
+ * the wallet key — the whole mint transaction is reproducible byte-for-byte, so
356
+ * a re-mint (e.g. after the token was lost from local storage) re-certifies the
357
+ * same state and yields the identical token (the v1 REQUEST_ID_EXISTS recovery
358
+ * analog).
359
+ */
360
+
361
+ interface UnicityIdMintResult {
362
+ /** UnicityIdToken CBOR, hex-encoded — the storable form (UnicityIdToken.fromCBOR round-trips it). */
363
+ readonly tokenCborHex: string;
364
+ /** 64-char hex token id, derived from the name (stable across re-mints). */
365
+ readonly tokenId: string;
366
+ }
367
+ /** Self-issued Unicity ID (nametag) token minter. */
368
+ interface IUnicityIdMinter {
369
+ /**
370
+ * Mint (or idempotently re-certify) the UnicityIdToken for `name`,
371
+ * self-issued by this wallet's key. Network-bound: submits the certification
372
+ * request and waits for the inclusion proof.
373
+ */
374
+ mintUnicityIdToken(name: string, options?: EngineOpOptions): Promise<UnicityIdMintResult>;
375
+ }
376
+ /**
377
+ * Build the self-issued Unicity ID minter from the same config the token engine
378
+ * uses (trust base JSON + gateway URL + API key + wallet key).
379
+ */
380
+ declare function createUnicityIdMinter(config: EngineConfig): IUnicityIdMinter;
381
+
382
+ /**
383
+ * token-engine/SpherePaymentData.ts — the sphere value model.
384
+ *
385
+ * v2 `Token` carries no coins; value is app-defined and stored in
386
+ * `MintTransaction.data`. `SpherePaymentData` is that payload: it implements the
387
+ * SDK's `IPaymentData` (so `TokenSplit` can read it for value conservation) and
388
+ * encodes a `PaymentAssetCollection` inside a versioned, tagged CBOR envelope.
389
+ *
390
+ * The SDK never inspects our raw bytes — it only calls `decodePaymentData(data)`
391
+ * and reads `.assets` — so the envelope (tag + version) is ours, chosen for
392
+ * forward-compatible storage. `fromValue`/`toValue` bridge sphere-domain values
393
+ * (hex coin id + bigint amount) to/from the SDK asset collection.
394
+ */
395
+
396
+ /** Validate a sphere-domain asset and build the SDK Asset — the single validation point. */
397
+ declare function sphereAssetToSdk(coinId: CoinId, amount: bigint): Asset;
398
+ declare class SpherePaymentData implements IPaymentData {
399
+ readonly assets: PaymentAssetCollection;
400
+ private readonly _memo;
401
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
402
+ static readonly CBOR_TAG = 39050n;
403
+ /** Envelope version; bump when the structure changes. */
404
+ static readonly VERSION = 1n;
405
+ private constructor();
406
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
407
+ get memo(): Uint8Array | null;
408
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
409
+ static create(assets: PaymentAssetCollection, memo?: Uint8Array | null): SpherePaymentData;
410
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
411
+ static fromValue(value: SphereValue, memo?: Uint8Array | null): SpherePaymentData;
412
+ /** Decode from the CBOR envelope produced by {@link encode}. */
413
+ static fromCBOR(bytes: Uint8Array): SpherePaymentData;
414
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
415
+ encode(): Promise<Uint8Array>;
416
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
417
+ toValue(): SphereValue;
418
+ /** Balance of a single coin within this payload (0n when absent). */
419
+ balanceOf(coinId: CoinId): bigint;
420
+ }
421
+ /**
422
+ * Async payment-data decoder matching the SDK's `decodePaymentData` signature.
423
+ * Used by `TokenSplit.split` (value conservation) and `SplitMintJustificationVerifier`.
424
+ */
425
+ declare function decodeSpherePaymentData(bytes: Uint8Array): Promise<IPaymentData>;
426
+
427
+ 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, TransferConflictError, type TransferParams, type UnicityIdMintResult, createSphereTokenEngine, createUnicityIdMinter, decodeSpherePaymentData, deriveDirectAddress, sphereAssetToSdk };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/sphere-sdk",
3
- "version": "0.9.1-dev.3",
3
+ "version": "0.9.1-dev.4",
4
4
  "description": "Modular TypeScript SDK for Unicity wallet operations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -115,7 +115,7 @@
115
115
  "build": "tsup",
116
116
  "build:watch": "tsup --watch",
117
117
  "clean": "rm -rf dist",
118
- "prepublishOnly": "npm run clean && npm run build",
118
+ "prepublishOnly": "node -e \"const fs=require('fs');const must=['dist/index.d.ts','dist/core/index.d.ts','dist/token-engine/index.d.ts','dist/wallet-api/index.d.ts','dist/token-engine/index.js','dist/wallet-api/index.js'];const missing=must.filter(f=>!fs.existsSync(f));if(missing.length){console.error('prepublishOnly: dist is stale or incomplete — run npm run build first. Missing: '+missing.join(', '));process.exit(1)}\"",
119
119
  "test": "vitest",
120
120
  "test:run": "vitest run",
121
121
  "test:e2e": "vitest run --config vitest.e2e.config.ts",