@shieldedtech/moth-wallet 0.2.0

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.
Files changed (230) hide show
  1. package/README.md +62 -0
  2. package/dist/contract/args-parser.d.ts +29 -0
  3. package/dist/contract/args-parser.d.ts.map +1 -0
  4. package/dist/contract/args-parser.js +123 -0
  5. package/dist/contract/args-parser.js.map +1 -0
  6. package/dist/contract/artifact-loader.d.ts +26 -0
  7. package/dist/contract/artifact-loader.d.ts.map +1 -0
  8. package/dist/contract/artifact-loader.js +129 -0
  9. package/dist/contract/artifact-loader.js.map +1 -0
  10. package/dist/contract/call.d.ts +32 -0
  11. package/dist/contract/call.d.ts.map +1 -0
  12. package/dist/contract/call.js +245 -0
  13. package/dist/contract/call.js.map +1 -0
  14. package/dist/contract/deploy.d.ts +36 -0
  15. package/dist/contract/deploy.d.ts.map +1 -0
  16. package/dist/contract/deploy.js +388 -0
  17. package/dist/contract/deploy.js.map +1 -0
  18. package/dist/contract/fungible-token.d.ts +40 -0
  19. package/dist/contract/fungible-token.d.ts.map +1 -0
  20. package/dist/contract/fungible-token.js +88 -0
  21. package/dist/contract/fungible-token.js.map +1 -0
  22. package/dist/contract/initial-private-state.d.ts +18 -0
  23. package/dist/contract/initial-private-state.d.ts.map +1 -0
  24. package/dist/contract/initial-private-state.js +44 -0
  25. package/dist/contract/initial-private-state.js.map +1 -0
  26. package/dist/contract/maintenance.d.ts +72 -0
  27. package/dist/contract/maintenance.d.ts.map +1 -0
  28. package/dist/contract/maintenance.js +430 -0
  29. package/dist/contract/maintenance.js.map +1 -0
  30. package/dist/contract/state.d.ts +16 -0
  31. package/dist/contract/state.d.ts.map +1 -0
  32. package/dist/contract/state.js +18 -0
  33. package/dist/contract/state.js.map +1 -0
  34. package/dist/contract/witness-loader.d.ts +3 -0
  35. package/dist/contract/witness-loader.d.ts.map +1 -0
  36. package/dist/contract/witness-loader.js +27 -0
  37. package/dist/contract/witness-loader.js.map +1 -0
  38. package/dist/daemon/api-keys.d.ts +66 -0
  39. package/dist/daemon/api-keys.d.ts.map +1 -0
  40. package/dist/daemon/api-keys.js +222 -0
  41. package/dist/daemon/api-keys.js.map +1 -0
  42. package/dist/daemon/audit-log.d.ts +65 -0
  43. package/dist/daemon/audit-log.d.ts.map +1 -0
  44. package/dist/daemon/audit-log.js +73 -0
  45. package/dist/daemon/audit-log.js.map +1 -0
  46. package/dist/daemon/client.d.ts +63 -0
  47. package/dist/daemon/client.d.ts.map +1 -0
  48. package/dist/daemon/client.js +214 -0
  49. package/dist/daemon/client.js.map +1 -0
  50. package/dist/daemon/confirmation-queue.d.ts +46 -0
  51. package/dist/daemon/confirmation-queue.d.ts.map +1 -0
  52. package/dist/daemon/confirmation-queue.js +92 -0
  53. package/dist/daemon/confirmation-queue.js.map +1 -0
  54. package/dist/daemon/index.d.ts +23 -0
  55. package/dist/daemon/index.d.ts.map +1 -0
  56. package/dist/daemon/index.js +26 -0
  57. package/dist/daemon/index.js.map +1 -0
  58. package/dist/daemon/protocol.d.ts +40 -0
  59. package/dist/daemon/protocol.d.ts.map +1 -0
  60. package/dist/daemon/protocol.js +104 -0
  61. package/dist/daemon/protocol.js.map +1 -0
  62. package/dist/daemon/server.d.ts +158 -0
  63. package/dist/daemon/server.d.ts.map +1 -0
  64. package/dist/daemon/server.js +297 -0
  65. package/dist/daemon/server.js.map +1 -0
  66. package/dist/daemon/wallet-handlers.d.ts +37 -0
  67. package/dist/daemon/wallet-handlers.d.ts.map +1 -0
  68. package/dist/daemon/wallet-handlers.js +533 -0
  69. package/dist/daemon/wallet-handlers.js.map +1 -0
  70. package/dist/daemon/wallet-rpc-parsers.d.ts +14 -0
  71. package/dist/daemon/wallet-rpc-parsers.d.ts.map +1 -0
  72. package/dist/daemon/wallet-rpc-parsers.js +259 -0
  73. package/dist/daemon/wallet-rpc-parsers.js.map +1 -0
  74. package/dist/daemon/wallet-rpc-types.d.ts +138 -0
  75. package/dist/daemon/wallet-rpc-types.d.ts.map +1 -0
  76. package/dist/daemon/wallet-rpc-types.js +11 -0
  77. package/dist/daemon/wallet-rpc-types.js.map +1 -0
  78. package/dist/diagnostics/fs-timing-store.d.ts +29 -0
  79. package/dist/diagnostics/fs-timing-store.d.ts.map +1 -0
  80. package/dist/diagnostics/fs-timing-store.js +81 -0
  81. package/dist/diagnostics/fs-timing-store.js.map +1 -0
  82. package/dist/diagnostics/timings.d.ts +64 -0
  83. package/dist/diagnostics/timings.d.ts.map +1 -0
  84. package/dist/diagnostics/timings.js +100 -0
  85. package/dist/diagnostics/timings.js.map +1 -0
  86. package/dist/index.d.ts +37 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +47 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/network/indexer-client.d.ts +66 -0
  91. package/dist/network/indexer-client.d.ts.map +1 -0
  92. package/dist/network/indexer-client.js +112 -0
  93. package/dist/network/indexer-client.js.map +1 -0
  94. package/dist/network/node-client.d.ts +27 -0
  95. package/dist/network/node-client.d.ts.map +1 -0
  96. package/dist/network/node-client.js +94 -0
  97. package/dist/network/node-client.js.map +1 -0
  98. package/dist/proof/client.d.ts +15 -0
  99. package/dist/proof/client.d.ts.map +1 -0
  100. package/dist/proof/client.js +77 -0
  101. package/dist/proof/client.js.map +1 -0
  102. package/dist/proof/provider.d.ts +12 -0
  103. package/dist/proof/provider.d.ts.map +1 -0
  104. package/dist/proof/provider.js +96 -0
  105. package/dist/proof/provider.js.map +1 -0
  106. package/dist/providers/index.d.ts +2 -0
  107. package/dist/providers/index.d.ts.map +1 -0
  108. package/dist/providers/index.js +2 -0
  109. package/dist/providers/index.js.map +1 -0
  110. package/dist/providers/midnight-provider.d.ts +11 -0
  111. package/dist/providers/midnight-provider.d.ts.map +1 -0
  112. package/dist/providers/midnight-provider.js +30 -0
  113. package/dist/providers/midnight-provider.js.map +1 -0
  114. package/dist/storage/adapter.d.ts +8 -0
  115. package/dist/storage/adapter.d.ts.map +1 -0
  116. package/dist/storage/adapter.js +2 -0
  117. package/dist/storage/adapter.js.map +1 -0
  118. package/dist/storage/fs-adapter.d.ts +14 -0
  119. package/dist/storage/fs-adapter.d.ts.map +1 -0
  120. package/dist/storage/fs-adapter.js +126 -0
  121. package/dist/storage/fs-adapter.js.map +1 -0
  122. package/dist/storage/safe-path.d.ts +13 -0
  123. package/dist/storage/safe-path.d.ts.map +1 -0
  124. package/dist/storage/safe-path.js +35 -0
  125. package/dist/storage/safe-path.js.map +1 -0
  126. package/dist/sync/activity.d.ts +53 -0
  127. package/dist/sync/activity.d.ts.map +1 -0
  128. package/dist/sync/activity.js +92 -0
  129. package/dist/sync/activity.js.map +1 -0
  130. package/dist/sync/dust-registration-estimate.d.ts +54 -0
  131. package/dist/sync/dust-registration-estimate.d.ts.map +1 -0
  132. package/dist/sync/dust-registration-estimate.js +103 -0
  133. package/dist/sync/dust-registration-estimate.js.map +1 -0
  134. package/dist/sync/node-sync-store.d.ts +10 -0
  135. package/dist/sync/node-sync-store.d.ts.map +1 -0
  136. package/dist/sync/node-sync-store.js +42 -0
  137. package/dist/sync/node-sync-store.js.map +1 -0
  138. package/dist/sync/operations.d.ts +138 -0
  139. package/dist/sync/operations.d.ts.map +1 -0
  140. package/dist/sync/operations.js +395 -0
  141. package/dist/sync/operations.js.map +1 -0
  142. package/dist/sync/preseed-parts.d.ts +18 -0
  143. package/dist/sync/preseed-parts.d.ts.map +1 -0
  144. package/dist/sync/preseed-parts.js +33 -0
  145. package/dist/sync/preseed-parts.js.map +1 -0
  146. package/dist/sync/preseed.d.ts +95 -0
  147. package/dist/sync/preseed.d.ts.map +1 -0
  148. package/dist/sync/preseed.js +365 -0
  149. package/dist/sync/preseed.js.map +1 -0
  150. package/dist/sync/progress.d.ts +45 -0
  151. package/dist/sync/progress.d.ts.map +1 -0
  152. package/dist/sync/progress.js +51 -0
  153. package/dist/sync/progress.js.map +1 -0
  154. package/dist/sync/sdk-dedup.d.ts +53 -0
  155. package/dist/sync/sdk-dedup.d.ts.map +1 -0
  156. package/dist/sync/sdk-dedup.js +134 -0
  157. package/dist/sync/sdk-dedup.js.map +1 -0
  158. package/dist/sync/sync-store.d.ts +33 -0
  159. package/dist/sync/sync-store.d.ts.map +1 -0
  160. package/dist/sync/sync-store.js +45 -0
  161. package/dist/sync/sync-store.js.map +1 -0
  162. package/dist/sync/wallet-sync.d.ts +153 -0
  163. package/dist/sync/wallet-sync.d.ts.map +1 -0
  164. package/dist/sync/wallet-sync.js +943 -0
  165. package/dist/sync/wallet-sync.js.map +1 -0
  166. package/dist/types/errors.d.ts +18 -0
  167. package/dist/types/errors.d.ts.map +1 -0
  168. package/dist/types/errors.js +33 -0
  169. package/dist/types/errors.js.map +1 -0
  170. package/dist/types/exit-codes.d.ts +8 -0
  171. package/dist/types/exit-codes.d.ts.map +1 -0
  172. package/dist/types/exit-codes.js +7 -0
  173. package/dist/types/exit-codes.js.map +1 -0
  174. package/dist/types/index.d.ts +6 -0
  175. package/dist/types/index.d.ts.map +1 -0
  176. package/dist/types/index.js +3 -0
  177. package/dist/types/index.js.map +1 -0
  178. package/dist/types/network.d.ts +65 -0
  179. package/dist/types/network.d.ts.map +1 -0
  180. package/dist/types/network.js +108 -0
  181. package/dist/types/network.js.map +1 -0
  182. package/dist/types/tokens.d.ts +3 -0
  183. package/dist/types/tokens.d.ts.map +1 -0
  184. package/dist/types/tokens.js +11 -0
  185. package/dist/types/tokens.js.map +1 -0
  186. package/dist/types/transaction.d.ts +13 -0
  187. package/dist/types/transaction.d.ts.map +1 -0
  188. package/dist/types/transaction.js +2 -0
  189. package/dist/types/transaction.js.map +1 -0
  190. package/dist/types/wallet.d.ts +82 -0
  191. package/dist/types/wallet.d.ts.map +1 -0
  192. package/dist/types/wallet.js +2 -0
  193. package/dist/types/wallet.js.map +1 -0
  194. package/dist/wallet/address-validate.d.ts +7 -0
  195. package/dist/wallet/address-validate.d.ts.map +1 -0
  196. package/dist/wallet/address-validate.js +28 -0
  197. package/dist/wallet/address-validate.js.map +1 -0
  198. package/dist/wallet/address.d.ts +32 -0
  199. package/dist/wallet/address.d.ts.map +1 -0
  200. package/dist/wallet/address.js +107 -0
  201. package/dist/wallet/address.js.map +1 -0
  202. package/dist/wallet/app-secret.d.ts +15 -0
  203. package/dist/wallet/app-secret.d.ts.map +1 -0
  204. package/dist/wallet/app-secret.js +45 -0
  205. package/dist/wallet/app-secret.js.map +1 -0
  206. package/dist/wallet/balance-format.d.ts +12 -0
  207. package/dist/wallet/balance-format.d.ts.map +1 -0
  208. package/dist/wallet/balance-format.js +51 -0
  209. package/dist/wallet/balance-format.js.map +1 -0
  210. package/dist/wallet/batch-transfer.d.ts +36 -0
  211. package/dist/wallet/batch-transfer.d.ts.map +1 -0
  212. package/dist/wallet/batch-transfer.js +93 -0
  213. package/dist/wallet/batch-transfer.js.map +1 -0
  214. package/dist/wallet/keystore.d.ts +16 -0
  215. package/dist/wallet/keystore.d.ts.map +1 -0
  216. package/dist/wallet/keystore.js +76 -0
  217. package/dist/wallet/keystore.js.map +1 -0
  218. package/dist/wallet/manager.d.ts +77 -0
  219. package/dist/wallet/manager.d.ts.map +1 -0
  220. package/dist/wallet/manager.js +474 -0
  221. package/dist/wallet/manager.js.map +1 -0
  222. package/dist/wallet/mnemonic.d.ts +5 -0
  223. package/dist/wallet/mnemonic.d.ts.map +1 -0
  224. package/dist/wallet/mnemonic.js +28 -0
  225. package/dist/wallet/mnemonic.js.map +1 -0
  226. package/dist/wallet/sign-message.d.ts +20 -0
  227. package/dist/wallet/sign-message.d.ts.map +1 -0
  228. package/dist/wallet/sign-message.js +58 -0
  229. package/dist/wallet/sign-message.js.map +1 -0
  230. package/package.json +55 -0
@@ -0,0 +1,66 @@
1
+ export interface ApiKeyRecord {
2
+ readonly id: string;
3
+ readonly label: string;
4
+ readonly salt: string;
5
+ readonly hashedSecret: string;
6
+ readonly createdAt: string;
7
+ readonly revokedAt?: string;
8
+ /** Scopes this key holds. Defaults to ['write'] (full daemon access)
9
+ * on records that predate scopes. Enforced by the daemon's
10
+ * per-method gate (server.ts): a read-only key is rejected from
11
+ * every write verb. */
12
+ readonly scopes?: readonly string[];
13
+ }
14
+ export interface ApiKeyGenerated {
15
+ readonly record: ApiKeyRecord;
16
+ /** The plaintext token `<id>.<secret-hex>`. Returned ONCE to the
17
+ * caller (the `key gen` CLI command surfaces it on stdout); the
18
+ * store never persists this value. */
19
+ readonly token: string;
20
+ }
21
+ export interface ApiKeyAuthResult {
22
+ readonly apiKeyId: string;
23
+ readonly label: string;
24
+ readonly scopes: readonly string[];
25
+ }
26
+ export declare class ApiKeyStore {
27
+ private readonly dir;
28
+ constructor(opts?: {
29
+ dir?: string;
30
+ });
31
+ /**
32
+ * Generate, persist, and return a new API key. The plaintext token
33
+ * is surfaced ONCE in the returned `token` field and never
34
+ * persisted — once the CLI has shown it to the operator there is
35
+ * no way to recover it.
36
+ *
37
+ * `scopes` defaults to `['write']` (full daemon access) so
38
+ * existing call sites that don't pass it keep their previous
39
+ * semantics. A read-only key passes `['read']`; a key that needs
40
+ * both passes `['read', 'write']`.
41
+ */
42
+ generate(label: string, scopes?: readonly string[]): ApiKeyGenerated;
43
+ /**
44
+ * Verify a wire token. Returns the auth result on success or null
45
+ * on any failure (unknown id, wrong secret, revoked key). Always
46
+ * runs the hash compare even when the id is unknown so the timing
47
+ * profile doesn't leak id presence.
48
+ */
49
+ verify(token: string): ApiKeyAuthResult | null;
50
+ list(): readonly ApiKeyRecord[];
51
+ /**
52
+ * Mark a key revoked. Returns the updated record, or null if no
53
+ * key with that id exists. Revoked records are kept on disk so
54
+ * audit lines reference a real id.
55
+ */
56
+ revoke(id: string): ApiKeyRecord | null;
57
+ /** Whether the store currently has at least one non-revoked key.
58
+ * Used by `daemon serve` to refuse a TCP bind that would have no
59
+ * way to authenticate any caller. */
60
+ hasActiveKey(): boolean;
61
+ /** Filesystem location — exposed for tests + CLI status output. */
62
+ get directory(): string;
63
+ private read;
64
+ private write;
65
+ }
66
+ //# sourceMappingURL=api-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/daemon/api-keys.ts"],"names":[],"mappings":"AAwCA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;4BAGwB;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;2CAEuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,IAAI,GAAE;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAM;IAKrC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,MAAM,EAAc,GAAG,eAAe;IA4B/E;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IA0C9C,IAAI,IAAI,SAAS,YAAY,EAAE;IAc/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAYvC;;0CAEsC;IACtC,YAAY,IAAI,OAAO;IAIvB,mEAAmE;IACnE,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,CAAC,IAAI;IAeZ,OAAO,CAAC,KAAK;CAUd"}
@@ -0,0 +1,222 @@
1
+ // API-key store for the daemon's stage-2 AuthN. Keys live as
2
+ // individual JSON files under ~/.moth/api-keys/ (mode 0700 dir,
3
+ // 0600 files). Each file is one record:
4
+ //
5
+ // {
6
+ // "id": "<8-char hex prefix>",
7
+ // "label": "ci-bot",
8
+ // "salt": "<hex>",
9
+ // "hashedSecret": "<hex>",
10
+ // "createdAt": "<iso>",
11
+ // "revokedAt": "<iso?>",
12
+ // "scopes": ["read", "write"] // reserved; stage-2.5
13
+ // }
14
+ //
15
+ // The token presented on the wire is `<id>.<secret-hex>`; the
16
+ // server splits on the dot, looks up the record by id, and
17
+ // timing-safe-compares sha256(salt || secret) against
18
+ // `hashedSecret`. Plain SHA-256 is fine because the secret is 32
19
+ // bytes of CSPRNG output — brute force is not viable. No need
20
+ // for Argon2id's CPU/memory cost on a high-entropy secret; the
21
+ // cost there exists to slow human-passphrase guessing.
22
+ import { randomBytes, timingSafeEqual, createHash } from 'node:crypto';
23
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
24
+ import { homedir } from 'node:os';
25
+ import { join, resolve, sep } from 'node:path';
26
+ /**
27
+ * The on-disk id of a key is exactly 8 lowercase hex chars — derived
28
+ * from the first 4 random bytes of the key's secret. Tokens that
29
+ * don't parse to this shape can never have been generated by us, so
30
+ * every read/write path validates against this regex BEFORE touching
31
+ * the filesystem. Without it, a malicious token like
32
+ * `../../etc/passwd.<anything>` would let `read()` attempt to stat
33
+ * arbitrary files inside the user's home — a path-traversal primitive
34
+ * that future bugs could compound into a real exploit even though
35
+ * the JSON.parse + hash check would still reject the contents.
36
+ */
37
+ const ID_RE = /^[0-9a-f]{8}$/;
38
+ export class ApiKeyStore {
39
+ dir;
40
+ constructor(opts = {}) {
41
+ this.dir = opts.dir ?? join(homedir(), '.moth', 'api-keys');
42
+ mkdirSync(this.dir, { recursive: true, mode: 0o700 });
43
+ }
44
+ /**
45
+ * Generate, persist, and return a new API key. The plaintext token
46
+ * is surfaced ONCE in the returned `token` field and never
47
+ * persisted — once the CLI has shown it to the operator there is
48
+ * no way to recover it.
49
+ *
50
+ * `scopes` defaults to `['write']` (full daemon access) so
51
+ * existing call sites that don't pass it keep their previous
52
+ * semantics. A read-only key passes `['read']`; a key that needs
53
+ * both passes `['read', 'write']`.
54
+ */
55
+ generate(label, scopes = ['write']) {
56
+ if (!label.trim())
57
+ throw new Error('API key label is required');
58
+ const VALID = new Set(['read', 'write']);
59
+ const normalised = Array.from(new Set(scopes.map((s) => s.trim()))).filter((s) => s.length > 0);
60
+ if (normalised.length === 0) {
61
+ throw new Error('at least one scope is required (read, write, or both)');
62
+ }
63
+ for (const s of normalised) {
64
+ if (!VALID.has(s)) {
65
+ throw new Error(`unknown scope "${s}"; valid: read, write`);
66
+ }
67
+ }
68
+ const secretBytes = randomBytes(32);
69
+ const id = secretBytes.subarray(0, 4).toString('hex'); // 8-char hex id
70
+ const salt = randomBytes(16).toString('hex');
71
+ const hashedSecret = hashSecret(salt, secretBytes.toString('hex'));
72
+ const record = {
73
+ id,
74
+ label: label.trim(),
75
+ salt,
76
+ hashedSecret,
77
+ createdAt: new Date().toISOString(),
78
+ scopes: normalised,
79
+ };
80
+ this.write(record);
81
+ return { record, token: `${id}.${secretBytes.toString('hex')}` };
82
+ }
83
+ /**
84
+ * Verify a wire token. Returns the auth result on success or null
85
+ * on any failure (unknown id, wrong secret, revoked key). Always
86
+ * runs the hash compare even when the id is unknown so the timing
87
+ * profile doesn't leak id presence.
88
+ */
89
+ verify(token) {
90
+ const dot = token.indexOf('.');
91
+ if (dot <= 0 || dot === token.length - 1) {
92
+ // Still hash a dummy to keep timing stable.
93
+ timingSafeCompareHex(hashSecret('00'.repeat(16), '00'.repeat(32)), hashSecret('00'.repeat(16), '00'.repeat(32)));
94
+ return null;
95
+ }
96
+ const id = token.slice(0, dot);
97
+ const secret = token.slice(dot + 1);
98
+ // Validate the id shape BEFORE any filesystem use. An id that
99
+ // doesn't match the canonical 8-hex format could never have been
100
+ // generated by us, so we reject it as failed-auth without
101
+ // touching the disk. Defense-in-depth against path-traversal
102
+ // tokens like `../../etc/passwd.<anything>`.
103
+ if (!ID_RE.test(id)) {
104
+ timingSafeCompareHex(hashSecret('00'.repeat(16), secret), hashSecret('00'.repeat(16), '00'.repeat(32)));
105
+ return null;
106
+ }
107
+ const record = this.read(id);
108
+ if (!record || record.revokedAt) {
109
+ // Run a dummy hash to keep the timing profile uniform.
110
+ timingSafeCompareHex(hashSecret('00'.repeat(16), secret), hashSecret('00'.repeat(16), '00'.repeat(32)));
111
+ return null;
112
+ }
113
+ const presented = hashSecret(record.salt, secret);
114
+ if (!timingSafeCompareHex(presented, record.hashedSecret))
115
+ return null;
116
+ return {
117
+ apiKeyId: record.id,
118
+ label: record.label,
119
+ scopes: record.scopes ?? ['write'],
120
+ };
121
+ }
122
+ list() {
123
+ if (!existsSync(this.dir))
124
+ return [];
125
+ const out = [];
126
+ for (const name of readdirSync(this.dir)) {
127
+ if (!name.endsWith('.key'))
128
+ continue;
129
+ try {
130
+ out.push(JSON.parse(readFileSync(join(this.dir, name), 'utf-8')));
131
+ }
132
+ catch {
133
+ /* skip unreadable / malformed entries */
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+ /**
139
+ * Mark a key revoked. Returns the updated record, or null if no
140
+ * key with that id exists. Revoked records are kept on disk so
141
+ * audit lines reference a real id.
142
+ */
143
+ revoke(id) {
144
+ const existing = this.read(id);
145
+ if (!existing)
146
+ return null;
147
+ if (existing.revokedAt)
148
+ return existing;
149
+ const updated = {
150
+ ...existing,
151
+ revokedAt: new Date().toISOString(),
152
+ };
153
+ this.write(updated);
154
+ return updated;
155
+ }
156
+ /** Whether the store currently has at least one non-revoked key.
157
+ * Used by `daemon serve` to refuse a TCP bind that would have no
158
+ * way to authenticate any caller. */
159
+ hasActiveKey() {
160
+ return this.list().some((r) => !r.revokedAt);
161
+ }
162
+ /** Filesystem location — exposed for tests + CLI status output. */
163
+ get directory() {
164
+ return this.dir;
165
+ }
166
+ read(id) {
167
+ // Belt-and-suspenders: re-validate at the IO boundary. Callers
168
+ // are supposed to gate on ID_RE before getting here, but a
169
+ // future refactor could miss a path. Cheap to check twice.
170
+ if (!ID_RE.test(id))
171
+ return null;
172
+ const path = join(this.dir, `${id}.key`);
173
+ if (!isInside(this.dir, path))
174
+ return null;
175
+ if (!existsSync(path))
176
+ return null;
177
+ try {
178
+ return JSON.parse(readFileSync(path, 'utf-8'));
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ }
184
+ write(record) {
185
+ if (!ID_RE.test(record.id)) {
186
+ throw new Error(`refusing to write key with malformed id: ${record.id}`);
187
+ }
188
+ const path = join(this.dir, `${record.id}.key`);
189
+ if (!isInside(this.dir, path)) {
190
+ throw new Error(`refusing to write key outside ${this.dir}`);
191
+ }
192
+ writeFileSync(path, JSON.stringify(record, null, 2) + '\n', { mode: 0o600 });
193
+ }
194
+ }
195
+ /**
196
+ * True iff `child`, resolved against the filesystem, is the same as
197
+ * or a descendant of `parent`. Used as a defense-in-depth check
198
+ * after the id regex — even if `..` somehow appeared in a path
199
+ * (e.g. because Node added a quirk), this would catch it before any
200
+ * read/write touched a file outside the api-keys directory.
201
+ */
202
+ function isInside(parent, child) {
203
+ const p = resolve(parent);
204
+ const c = resolve(child);
205
+ return c === p || c.startsWith(p + sep);
206
+ }
207
+ function hashSecret(saltHex, secretHex) {
208
+ const h = createHash('sha256');
209
+ h.update(Buffer.from(saltHex, 'hex'));
210
+ h.update(Buffer.from(secretHex, 'hex'));
211
+ return h.digest('hex');
212
+ }
213
+ function timingSafeCompareHex(a, b) {
214
+ if (a.length !== b.length)
215
+ return false;
216
+ const ab = Buffer.from(a, 'hex');
217
+ const bb = Buffer.from(b, 'hex');
218
+ if (ab.length !== bb.length)
219
+ return false;
220
+ return timingSafeEqual(ab, bb);
221
+ }
222
+ //# sourceMappingURL=api-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/daemon/api-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export type AuditDecision = 'auto-approve' | 'user-approve' | 'user-denied';
2
+ export interface AuditRpcEntry {
3
+ readonly ts: string;
4
+ readonly kind: 'rpc';
5
+ readonly wallet: string;
6
+ readonly network: string;
7
+ readonly verb: string;
8
+ readonly summary: string;
9
+ readonly details?: readonly string[];
10
+ readonly decision: AuditDecision;
11
+ readonly txHash?: string;
12
+ readonly contractAddress?: string;
13
+ readonly status?: string;
14
+ readonly error?: {
15
+ readonly code: string;
16
+ readonly message: string;
17
+ };
18
+ /** Connection transport ('unix' | 'tcp') the RPC arrived on. */
19
+ readonly transport?: 'unix' | 'tcp';
20
+ /** API key id once the connection authenticated; absent on Unix
21
+ * transport without an auth handler. */
22
+ readonly apiKeyId?: string;
23
+ /** Connection-id (per-daemon-process) for correlating multiple
24
+ * RPCs from the same client connection. */
25
+ readonly connId?: number;
26
+ }
27
+ export interface AuditLifecycleEntry {
28
+ readonly ts: string;
29
+ readonly kind: 'lifecycle';
30
+ readonly wallet: string;
31
+ readonly network: string;
32
+ readonly event: 'daemon-start' | 'daemon-stop' | 'socket-bound' | 'sync-complete' | 'shutdown-signal' | 'auth-success' | 'auth-failure';
33
+ readonly message?: string;
34
+ readonly apiKeyId?: string;
35
+ }
36
+ export type AuditEntry = AuditRpcEntry | AuditLifecycleEntry;
37
+ export interface AuditLogOptions {
38
+ /** Override the default `~/.moth/` directory. Used by tests. */
39
+ readonly dir?: string;
40
+ /** Override the file name. Defaults to `daemon-audit.log`. */
41
+ readonly filename?: string;
42
+ }
43
+ export declare class AuditLog {
44
+ private readonly path;
45
+ constructor(opts?: AuditLogOptions);
46
+ /** Append one JSONL entry. Best-effort, silent on error. */
47
+ record(entry: AuditEntry): void;
48
+ /** Convenience: stamp `ts` with the current time and record an
49
+ * RPC entry. Split from the lifecycle variant so TypeScript can
50
+ * narrow on the discriminated union at the call site. */
51
+ recordRpc(entry: Omit<AuditRpcEntry, 'ts' | 'kind'>): void;
52
+ /** Same as `recordRpc` for lifecycle events. */
53
+ recordLifecycle(entry: Omit<AuditLifecycleEntry, 'ts' | 'kind'>): void;
54
+ /** Rotate the live file to `<path>.YYYY-MM-DD` when the file's
55
+ * most-recent mtime is on a different day than today. Called
56
+ * before every append; the per-write cost is one stat() syscall.
57
+ * Idempotent — once today's file exists, the check returns
58
+ * immediately. */
59
+ private maybeRotate;
60
+ /** Test helper: full path to the live log file. */
61
+ get filePath(): string;
62
+ /** Test helper: directory the log lives in. */
63
+ get directory(): string;
64
+ }
65
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../src/daemon/audit-log.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;IACnE,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACpC;6CACyC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;gDAC4C;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EACV,cAAc,GACd,aAAa,GACb,cAAc,GACd,eAAe,GACf,iBAAiB,GACjB,cAAc,GACd,cAAc,CAAC;IACnB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,mBAAmB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,IAAI,GAAE,eAAoB;IAMtC,4DAA4D;IAC5D,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAS/B;;8DAE0D;IAC1D,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI;IAI1D,gDAAgD;IAChD,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI;IAItE;;;;uBAImB;IACnB,OAAO,CAAC,WAAW;IAcnB,mDAAmD;IACnD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+CAA+C;IAC/C,IAAI,SAAS,IAAI,MAAM,CAEtB;CACF"}
@@ -0,0 +1,73 @@
1
+ // Append-only JSONL audit log for the wallet daemon. One JSON object
2
+ // per line. Rotated daily — when the first write of the day sees a
3
+ // log file last modified on an earlier day, it renames the file to
4
+ // `daemon-audit.log.YYYY-MM-DD` and starts a fresh one.
5
+ //
6
+ // Stage-1.5 implementation. Stage 3+ will add hash-chaining for
7
+ // tamper evidence (Trillian-style); for now the file is plain JSONL
8
+ // and trusts the host's filesystem mode (`0600`).
9
+ //
10
+ // All writes are best-effort and silent on error: the audit log
11
+ // must never crash the daemon. Errors during write are dropped on
12
+ // the floor — if they matter, you'll notice the missing entries
13
+ // later.
14
+ import { appendFileSync, existsSync, mkdirSync, renameSync, statSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { dirname, join } from 'node:path';
17
+ export class AuditLog {
18
+ path;
19
+ constructor(opts = {}) {
20
+ const dir = opts.dir ?? join(homedir(), '.moth');
21
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
22
+ this.path = join(dir, opts.filename ?? 'daemon-audit.log');
23
+ }
24
+ /** Append one JSONL entry. Best-effort, silent on error. */
25
+ record(entry) {
26
+ this.maybeRotate();
27
+ try {
28
+ appendFileSync(this.path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
29
+ }
30
+ catch {
31
+ /* audit must never crash the daemon */
32
+ }
33
+ }
34
+ /** Convenience: stamp `ts` with the current time and record an
35
+ * RPC entry. Split from the lifecycle variant so TypeScript can
36
+ * narrow on the discriminated union at the call site. */
37
+ recordRpc(entry) {
38
+ this.record({ ...entry, kind: 'rpc', ts: new Date().toISOString() });
39
+ }
40
+ /** Same as `recordRpc` for lifecycle events. */
41
+ recordLifecycle(entry) {
42
+ this.record({ ...entry, kind: 'lifecycle', ts: new Date().toISOString() });
43
+ }
44
+ /** Rotate the live file to `<path>.YYYY-MM-DD` when the file's
45
+ * most-recent mtime is on a different day than today. Called
46
+ * before every append; the per-write cost is one stat() syscall.
47
+ * Idempotent — once today's file exists, the check returns
48
+ * immediately. */
49
+ maybeRotate() {
50
+ if (!existsSync(this.path))
51
+ return;
52
+ try {
53
+ const stat = statSync(this.path);
54
+ const today = new Date().toISOString().slice(0, 10);
55
+ const fileDay = stat.mtime.toISOString().slice(0, 10);
56
+ if (fileDay !== today) {
57
+ renameSync(this.path, `${this.path}.${fileDay}`);
58
+ }
59
+ }
60
+ catch {
61
+ /* skip rotation on any error; the next write will retry */
62
+ }
63
+ }
64
+ /** Test helper: full path to the live log file. */
65
+ get filePath() {
66
+ return this.path;
67
+ }
68
+ /** Test helper: directory the log lives in. */
69
+ get directory() {
70
+ return dirname(this.path);
71
+ }
72
+ }
73
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../src/daemon/audit-log.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,mEAAmE;AACnE,mEAAmE;AACnE,wDAAwD;AACxD,EAAE;AACF,gEAAgE;AAChE,oEAAoE;AACpE,kDAAkD;AAClD,EAAE;AACF,gEAAgE;AAChE,kEAAkE;AAClE,gEAAgE;AAChE,SAAS;AAET,OAAO,EAAC,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AACpF,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,WAAW,CAAC;AAqDxC,MAAM,OAAO,QAAQ;IACF,IAAI,CAAS;IAE9B,YAAY,OAAwB,EAAE;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;QACjD,SAAS,CAAC,GAAG,EAAE,EAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,IAAI,kBAAkB,CAAC,CAAC;IAC7D,CAAC;IAED,4DAA4D;IAC5D,MAAM,CAAC,KAAiB;QACtB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAC,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAED;;8DAE0D;IAC1D,SAAS,CAAC,KAAyC;QACjD,IAAI,CAAC,MAAM,CAAC,EAAC,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAC,CAAC,CAAC;IACrE,CAAC;IAED,gDAAgD;IAChD,eAAe,CAAC,KAA+C;QAC7D,IAAI,CAAC,MAAM,CAAC,EAAC,GAAG,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAC,CAAC,CAAC;IAC3E,CAAC;IAED;;;;uBAImB;IACX,WAAW;QACjB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBACtB,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;QAC7D,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,+CAA+C;IAC/C,IAAI,SAAS;QACX,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,63 @@
1
+ import type { DaemonBind } from './server.js';
2
+ export interface DaemonClient {
3
+ /** Issue an RPC; resolves with the daemon's result or rejects with a
4
+ * DaemonProtocolError carrying the daemon-reported code+message. */
5
+ call<R = unknown>(method: string, params?: unknown, opts?: {
6
+ timeoutMs?: number;
7
+ }): Promise<R>;
8
+ /** Close the underlying socket. In-flight calls reject with code CLOSED. */
9
+ close(): void;
10
+ readonly closed: boolean;
11
+ /** Daemon-reported build identifier from the version handshake. */
12
+ readonly daemonVersion: string;
13
+ /** API key id this client authenticated as. Present when a `token`
14
+ * was passed to `connectDaemonBind`; absent for Unix-transport
15
+ * clients hitting a daemon with no auth handler. */
16
+ readonly apiKeyId?: string;
17
+ }
18
+ export interface ConnectDaemonOptions {
19
+ /** Override the per-call default. Individual call() invocations can
20
+ * still pass their own timeoutMs. */
21
+ readonly defaultTimeoutMs?: number;
22
+ /** Optional logger hook. Defaults to silent. */
23
+ readonly onLog?: (level: 'info' | 'warn' | 'error', msg: string) => void;
24
+ /**
25
+ * Stage-2 token in `<id>.<secret>` format. When set, the client
26
+ * issues an `auth` RPC after the version handshake and returns
27
+ * null if it fails (treating "bad token" the same as "no daemon"
28
+ * — see the connectDaemon docstring). Omit on Unix-transport
29
+ * connections to a daemon with no auth handler.
30
+ */
31
+ readonly token?: string;
32
+ }
33
+ /**
34
+ * Try to connect to the daemon at the given Unix-socket path. Stage-1
35
+ * entry point — kept for backwards compatibility. For TCP transports
36
+ * use `connectDaemonTcp(host, port)` or the unified
37
+ * `connectDaemonBind(bind)`.
38
+ *
39
+ * Returns:
40
+ * - DaemonClient on successful connect + handshake
41
+ * - null if the file doesn't exist, the connect is refused, or the
42
+ * handshake reports an incompatible protocol version
43
+ *
44
+ * Throws only on genuinely-unexpected conditions (filesystem error,
45
+ * malformed daemon response). Network-style failures are folded into
46
+ * null so the CLI can fall back to standalone without a try/catch.
47
+ */
48
+ export declare function connectDaemon(socketPath: string, opts?: ConnectDaemonOptions): Promise<DaemonClient | null>;
49
+ /**
50
+ * Stage-2 entry point. Connect to a daemon listening on loopback TCP
51
+ * (or, at stage 3, a TLS-terminating reverse proxy in front of the
52
+ * daemon's loopback bind). Same fall-back-to-null semantics as
53
+ * `connectDaemon`.
54
+ */
55
+ export declare function connectDaemonTcp(host: string, port: number, opts?: ConnectDaemonOptions): Promise<DaemonClient | null>;
56
+ /**
57
+ * Transport-agnostic entry point. Both `connectDaemon` and
58
+ * `connectDaemonTcp` route through here. The handshake + dispatcher
59
+ * setup is identical across transports — only the underlying socket
60
+ * factory differs.
61
+ */
62
+ export declare function connectDaemonBind(bind: DaemonBind, opts?: ConnectDaemonOptions): Promise<DaemonClient | null>;
63
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B;yEACqE;IACrE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7F,4EAA4E;IAC5E,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;yDAEqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC;0CACsC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzE;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAE9B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAE9B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA2D9B"}