@glyphteck/veyl 0.76.0 → 0.77.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.
package/docs/api.md CHANGED
@@ -46,16 +46,12 @@ const auth = openAuth({
46
46
  cloud,
47
47
  origin: () => window.location.origin,
48
48
  ceremony: {
49
- create: async (options) => encodeCredential(
50
- await navigator.credentials.create({
51
- publicKey: decodePublicKeyOptions(options),
52
- }),
53
- ),
54
- get: async (options) => encodeCredential(
55
- await navigator.credentials.get({
56
- publicKey: decodePublicKeyOptions(options),
57
- }),
58
- ),
49
+ create: (options) => navigator.credentials.create({
50
+ publicKey: decodePublicKeyOptions(options),
51
+ }),
52
+ get: (options) => navigator.credentials.get({
53
+ publicKey: decodePublicKeyOptions(options),
54
+ }),
59
55
  },
60
56
  });
61
57
 
@@ -78,7 +74,7 @@ unsubscribeAuth();
78
74
  await auth.logout();
79
75
  ```
80
76
 
81
- `openAuth()` owns the shared register, login, token, one-use link, passkey inventory, verified deletion, session observation, and sign-out choreography. `activateToken()` is the generic in-memory account-session activation boundary for a custom token returned by a token-only login, registration, or passkey-link flow; it is not nested under passkey links because those flows share the same isolated auth-slot operation. Never persist that token. The `cloud` input is a transport rather than a Firebase dependency. A platform supplies only the local credential ceremony: browser WebAuthn on web, `react-native-passkeys` on iOS, or a browser-assisted port in the Node runtime. Browser adapters must decode the server's binary options before the ceremony and return a JSON-safe credential afterward, as the placeholder helpers above indicate. Errors are normalized at this boundary so every client receives the same recoverable registration and login outcomes. The platform still owns when to prompt, how to present the prompt, navigation, local credential storage, and UI feedback.
77
+ `openAuth()` owns the shared register, login, token, one-use link, passkey inventory, verified deletion, session observation, and sign-out choreography. `activateToken()` is the generic in-memory account-session activation boundary for a custom token returned by a token-only login, registration, or passkey-link flow; it is not nested under passkey links because those flows share the same isolated auth-slot operation. Never persist that token. The `cloud` input is a transport rather than a Firebase dependency. A platform supplies only the local credential ceremony: browser WebAuthn on web, `react-native-passkeys` on iOS, or a browser-assisted port in the Node runtime. browser adapters decode binary options and return the native credential object; ios returns its library's credential json. the shared owner extracts an explicit allowlist of authentication proof fields before any transport call. never serialize a raw browser credential with `toJSON()` or `JSON.stringify()`: extension results can contain encryption secrets. Errors are normalized at this boundary so every client receives the same recoverable registration and login outcomes. The platform still owns when to prompt, how to present the prompt, navigation, local credential storage, and UI feedback.
82
78
 
83
79
  ## Account profiles
84
80
 
@@ -140,8 +136,7 @@ const publicRequest = await account.payment.createRequest({ amountSats: 1250 });
140
136
  console.log(publicRequest.link);
141
137
  await account.profile.clearAvatar();
142
138
 
143
- await account.support.feedback('message', {
144
- type: 'other',
139
+ await account.support.submit('message', {
145
140
  platform: 'web',
146
141
  route: '/settings',
147
142
  appVersion,
@@ -414,7 +409,9 @@ const parsed = veyl.invite.read(created.link);
414
409
 
415
410
  ## Wallet
416
411
 
417
- All amounts are integer sats.
412
+ bitcoin amounts are integer sats. `send` and `request` also accept an exact integer string or bigint of token base units when `tokenIdentifier` is supplied. the network-specific asset registry validates the identifier, issuer, and decimals; token values are never converted through btc or a floating-point number.
413
+
414
+ structured `wallet_send` and `wallet_request` commands use `{ peer, amount, tokenIdentifier? }`, with `amount` as an integer string. `wallet_pay_request` derives the asset and full amount from the request.
418
415
 
419
416
  ```js
420
417
  await veyl.wallet.balance();
@@ -436,6 +433,27 @@ await veyl.wallet.search('@alice', { count: 50 });
436
433
 
437
434
  Wallet boot, signing, balance, transaction aggregation, peer attribution, claim, payment-request validation, and wallet mutation serialization are shared. `raw: true` adds JSON-safe SDK detail only where supported.
438
435
 
436
+ the regtest prototype asset is usd veyl (`USDV`), with six decimals and a fixed test valuation of exactly one usd per token. the one-million-token supply is held by `@faucet`. no mainnet stablecoin is enabled.
437
+
438
+ ```js
439
+ const balance = await veyl.wallet.balance();
440
+ const usdv = balance.tokens.find(asset => asset.ticker === 'USDV');
441
+ // the registry has already verified this record; use its identifier for actions.
442
+ const request = await veyl.wallet.request('@faucet', '1250000', {
443
+ tokenIdentifier: usdv.tokenIdentifier, // 1.25 usdv, expressed in base units
444
+ });
445
+ await veyl.wallet.send('@alice', '1000000', { tokenIdentifier: usdv.tokenIdentifier });
446
+ const page = await veyl.wallet.transactions({ tokenIdentifier: usdv.tokenIdentifier, count: 50 });
447
+ ```
448
+
449
+ `balance.tokens` reports `ownedUnits`, `availableUnits`, `unavailableUnits`, formatted `amount`, `usdPrice`, and `usdValue`. null quantities mean unavailable rather than zero; inspect `tokenBalancesReady` and `tokenBalanceError`. only usdv has the fixed test valuation. values have no mainnet backing or redemption rights.
450
+
451
+ token history uses `cursor` and `direction` (`PREVIOUS` for newest-first pages), returning `{ transfers, page }`; it rejects btc-style offsets. rows carry exact `amountUnits`, asset, direction, status, timestamp, and counterparties. the recent page is encrypted in the existing vault cache and scoped to wallet identity/network. token amounts are excluded from btc-only charts and aggregates. pay a token request with the ordinary `wallet.pay(requestId)` command; its encrypted asset identity determines which balance is spent and must match the confirmation.
452
+
453
+ token requests use message type `tokenreq`, and their encrypted settlement actions use `tokenreqpay`. bitcoin retains `req` and `reqpay`. sdk token request events carry `tokenIdentifier`, `amountUnits`, `paidAmountUnits`, and `remainingAmountUnits` without an `amountSats` field. ios `0.76.0 (26)` ignores the new token message types; its bitcoin wallet remains usable, but it cannot display or pay token requests. never place token base units in a bitcoin request payload.
454
+
455
+ the current request renderer and payment review preserve token identity on web and ios. the web cash selector chooses the payment asset independently of display currency: showing a token amount in btc never authorizes spending bitcoin. token invoices/qr and stablecoin swaps remain separate product work. the approved ios build's limitations are described above.
456
+
439
457
  ## Lightning and withdrawal
440
458
 
441
459
  ```js
@@ -473,12 +491,15 @@ Rows combine pending links and verified passkeys, mark the passkey currently aut
473
491
  ## Support
474
492
 
475
493
  ```js
476
- await veyl.support.feedback('message', { type: 'other', context: 'agent' });
477
- await veyl.support.bug('description');
494
+ const ticket = await veyl.support.submit('description', {
495
+ id: 'stable_submission_id', // reuse this id for retries of the same draft
496
+ attachments: [{ name: 'screenshot.png', mimeType: 'image/png', bytes }],
497
+ });
498
+ console.log(ticket.id);
478
499
  await veyl.support.report('@alice', { messageId, note: 'context' });
479
500
  ```
480
501
 
481
- These methods use the same account-scoped validation, encrypted-message lookup, report metadata, evidence reservation, and backend submission owner as web/iOS. When `messageId` selects an attachment, the runtime reads and uploads the attachment as report evidence before submitting because attachment reports are invalid without their evidence path. Automated tests should not submit fake reports or support messages to shared environments.
502
+ Support submissions use the same ticket owner as web/iOS. The agent classifies them after submission. A ticket accepts up to 4,000 characters and three files of up to 5 MiB each (PNG, JPEG, WebP, GIF, PDF or UTF-8 text). Attachments are deliberately shared with the operator for support. Abuse reports separately use account-scoped encrypted-message lookup, report metadata and evidence reservation. When `messageId` selects an attachment, the runtime reads and uploads the attachment as report evidence before submitting because attachment reports are invalid without their evidence path. Automated tests should not submit fake reports or support messages to shared environments.
482
503
 
483
504
  ## Live events
484
505
 
package/docs/cli.md CHANGED
@@ -135,8 +135,8 @@ Amounts are integer sats.
135
135
  veyl wallet balance
136
136
  veyl wallet address
137
137
  veyl wallet claim [--count 100]
138
- veyl wallet send @alice 10 [--operation-id AGENT_JOB_ID]
139
- veyl wallet request @alice 10
138
+ veyl wallet send @alice 10 [--operation-id AGENT_JOB_ID] [--token-identifier id]
139
+ veyl wallet request @alice 10 [--token-identifier id]
140
140
  veyl wallet pay-request REQUEST_ID [--operation-id AGENT_JOB_ID]
141
141
  veyl wallet pay-invoice INVOICE [--amount SATS] [--operation-id AGENT_JOB_ID]
142
142
  veyl wallet transactions [--count 50] [--offset 0]
@@ -179,8 +179,7 @@ veyl passkeys list
179
179
  veyl passkeys link [--web-url ORIGIN]
180
180
  veyl passkeys delete ID [--web-url ORIGIN]
181
181
 
182
- veyl support feedback "message" [--context TEXT]
183
- veyl support bug "message"
182
+ veyl support submit "message"
184
183
  veyl support report @alice [MESSAGE_ID] [--note TEXT] [--no-attachment]
185
184
  ```
186
185
 
@@ -18,7 +18,7 @@ The Node SDK composes the same framework-free owners as web and iOS:
18
18
  | Public invites | `core/invite.js` | direct Veyl URL strings |
19
19
  | Passkeys | shared passkey eligibility and normal backend link/passkey owners | browser URL for WebAuthn verification |
20
20
  | Settings/cache | shared settings normalization and vault-encrypted cache | JSON patches and byte counts |
21
- | Support | shared feedback, bug, report, and evidence builders | terminal text and existing message attachment |
21
+ | Support | shared ticket, report, and evidence builders | terminal text and existing message attachment |
22
22
 
23
23
  The obsolete direct-cloud `createAccountSessionActions` implementation was removed during this pass. There is now one live product path: the SDK adapts inputs around the same chat, wallet, peer, user, vault, cache, search, and cloud owners used by the interactive clients.
24
24
 
@@ -51,7 +51,7 @@ API version 4 keeps `open()` as the public SDK composition root and adds stable-
51
51
  | Withdrawal | 3 / 3 | shared wallet session, fee normalization, and generic payment-intent/L1 review owner |
52
52
  | Invite | 2 / 2 | `core/invite.js`; direct URL strings only |
53
53
  | Passkeys | 3 / 2 | shared passkey eligibility plus the normal cloud passkey owners; bearer-link creation is one-shot only and WebAuthn stays browser-assisted |
54
- | Support | 3 / 3 | shared feedback, bug, report, message lookup, and evidence builders |
54
+ | Support | 2 / 2 | shared ticket, report, message lookup, and evidence builders |
55
55
  | Session | 4 / 0 | local process/socket lifecycle around the same account runtime |
56
56
  | Docs | 1 / 0 | local stdio MCP exposing allowlisted packaged Markdown resources only |
57
57
 
@@ -76,7 +76,7 @@ API version 4 keeps `open()` as the public SDK composition root and adds stable-
76
76
  | Withdrawal quote/prepare/confirm | yes | a live 3,000-sat cooperative exit paid a 2,190-sat medium fee and funded the receiver's static address with 810 sats; the run exposed and fixed CLI re-quoting at confirm, and post-fee dust now rejects before spend |
77
77
  | Invite create/read | yes | all four shared invite kinds round-trip as direct URL strings; invalid kinds and amounts reject |
78
78
  | Passkey list/link/delete | yes | pending link create/delete exercised; list marks current credential and shared deletion eligibility |
79
- | Feedback/bug/report | yes | report-without-evidence succeeded live; feedback, bug, and reporting now share one SDK account owner and remaining cross-client checks are in the root manual queue |
79
+ | Tickets/reports | yes | report-without-evidence succeeded live; ticket submission and reporting share one SDK account owner and remaining cross-client checks are in the root manual queue |
80
80
  | Docs discovery | yes | focused tests prove the stdio MCP advertises only documentation resources, reads only allowlisted packaged Markdown, and rejects action tool calls without loading a Veyl client |
81
81
 
82
82
  ## Edge cases exercised
@@ -99,6 +99,10 @@ function isFleetGeneratedMessage(event) {
99
99
  }
100
100
 
101
101
  function requestAmount(event) {
102
+ if (event?.message?.tokenIdentifier != null) {
103
+ tokenAsset(event.message.tokenIdentifier, event.message.network);
104
+ return tokenUnits(event.message.amountUnits).toString();
105
+ }
102
106
  const amount = Number(event?.message?.amountSats);
103
107
  return Number.isSafeInteger(amount) && amount > 0 ? amount : null;
104
108
  }
@@ -215,7 +219,9 @@ async function sendUnderfunded(
215
219
  () => sendToEventChat(
216
220
  client,
217
221
  event,
218
- `i only have ${availableSats} sats`,
222
+ event.message.tokenIdentifier
223
+ ? `i only have ${formatTokenAmount(String(availableSats), tokenAsset(event.message.tokenIdentifier, event.message.network))} ${tokenAsset(event.message.tokenIdentifier, event.message.network).ticker.toLowerCase()}`
224
+ : `i only have ${availableSats} sats`,
219
225
  { cid: botActionCid(event, 'funds-balance', account) }
220
226
  )
221
227
  );
@@ -267,7 +273,7 @@ async function mirrorEcho(journal, account, client, event) {
267
273
  () => mirrorReaction(account, client, event)
268
274
  );
269
275
  }
270
- if (event.message.type === 'req') {
276
+ if (['req', 'tokenreq'].includes(event.message.type)) {
271
277
  if (event.chat?.lineage && event.chat.lineage !== 'direct') return null;
272
278
  const amountSats = requestAmount(event);
273
279
  if (!amountSats) return null;
@@ -279,6 +285,7 @@ async function mirrorEcho(journal, account, client, event) {
279
285
  { replaySafe: true },
280
286
  () => client.wallet.request(event.peer, amountSats, {
281
287
  cid: botActionCid(event, 'echo-request', account),
288
+ tokenIdentifier: event.message.tokenIdentifier,
282
289
  })
283
290
  );
284
291
  }
@@ -335,13 +342,16 @@ async function serveFaucet(journal, account, client, event) {
335
342
  );
336
343
  }
337
344
  const balance = await client.wallet.balance();
338
- if (balance.availableSats < amountSats) {
345
+ const tokenBalance = event.message.tokenIdentifier ? balance.tokens?.find((asset) => asset.tokenIdentifier === event.message.tokenIdentifier) : null;
346
+ if (event.message.tokenIdentifier && (balance.tokenBalancesReady !== true || tokenBalance?.availableUnits == null)) throw new Error('faucet token balance unavailable');
347
+ const available = event.message.tokenIdentifier ? tokenUnits(tokenBalance.availableUnits, { allowZero: true }) : BigInt(balance.availableSats);
348
+ if (available < BigInt(amountSats)) {
339
349
  await sendUnderfunded(
340
350
  journal,
341
351
  account,
342
352
  client,
343
353
  event,
344
- balance.availableSats
354
+ available.toString()
345
355
  );
346
356
  return { paid: false, reason: 'insufficient-funds' };
347
357
  }
@@ -368,6 +378,7 @@ async function serveFaucet(journal, account, client, event) {
368
378
  { replaySafe: true },
369
379
  () => client.wallet.request(event.peer, amountSats, {
370
380
  cid: botActionCid(event, 'faucet-request', account),
381
+ tokenIdentifier: event.message.tokenIdentifier,
371
382
  })
372
383
  );
373
384
  return { paid: true, payment: paid };
@@ -664,7 +675,7 @@ export function createBotFleetPolicy(options = {}) {
664
675
  ) {
665
676
  if (
666
677
  roles.has('faucet') &&
667
- event.message.type === 'req'
678
+ ['req', 'tokenreq'].includes(event.message.type)
668
679
  ) {
669
680
  result = await serveFaucet(
670
681
  journal,
@@ -689,3 +700,4 @@ export function createBotFleetPolicy(options = {}) {
689
700
  },
690
701
  });
691
702
  }
703
+ import { formatTokenAmount, tokenAsset, tokenUnits } from '@veyl/core/wallet/assets';
package/package.json CHANGED
@@ -54,5 +54,5 @@
54
54
  "start": "node src/cli.js",
55
55
  "lint": "eslint src --quiet"
56
56
  },
57
- "version": "0.76.0"
57
+ "version": "0.77.1"
58
58
  }