@drakkar.software/octobot-client 0.4.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 (306) hide show
  1. package/README.md +140 -0
  2. package/dist/client/adapters/accounts.d.ts +66 -0
  3. package/dist/client/adapters/accounts.d.ts.map +1 -0
  4. package/dist/client/adapters/accounts.js +214 -0
  5. package/dist/client/adapters/accounts.js.map +1 -0
  6. package/dist/client/adapters/actionHandle.d.ts +29 -0
  7. package/dist/client/adapters/actionHandle.d.ts.map +1 -0
  8. package/dist/client/adapters/actionHandle.js +34 -0
  9. package/dist/client/adapters/actionHandle.js.map +1 -0
  10. package/dist/client/adapters/automations.d.ts +31 -0
  11. package/dist/client/adapters/automations.d.ts.map +1 -0
  12. package/dist/client/adapters/automations.js +79 -0
  13. package/dist/client/adapters/automations.js.map +1 -0
  14. package/dist/client/adapters/documents.d.ts +55 -0
  15. package/dist/client/adapters/documents.d.ts.map +1 -0
  16. package/dist/client/adapters/documents.js +66 -0
  17. package/dist/client/adapters/documents.js.map +1 -0
  18. package/dist/client/adapters/nodeApi.d.ts +25 -0
  19. package/dist/client/adapters/nodeApi.d.ts.map +1 -0
  20. package/dist/client/adapters/nodeApi.js +81 -0
  21. package/dist/client/adapters/nodeApi.js.map +1 -0
  22. package/dist/client/adapters/settings.d.ts +16 -0
  23. package/dist/client/adapters/settings.d.ts.map +1 -0
  24. package/dist/client/adapters/settings.js +43 -0
  25. package/dist/client/adapters/settings.js.map +1 -0
  26. package/dist/client/adapters/strategies.d.ts +17 -0
  27. package/dist/client/adapters/strategies.d.ts.map +1 -0
  28. package/dist/client/adapters/strategies.js +75 -0
  29. package/dist/client/adapters/strategies.js.map +1 -0
  30. package/dist/client/connect/connect.d.ts +40 -0
  31. package/dist/client/connect/connect.d.ts.map +1 -0
  32. package/dist/client/connect/connect.js +133 -0
  33. package/dist/client/connect/connect.js.map +1 -0
  34. package/dist/client/connect/readOnly.d.ts +97 -0
  35. package/dist/client/connect/readOnly.d.ts.map +1 -0
  36. package/dist/client/connect/readOnly.js +167 -0
  37. package/dist/client/connect/readOnly.js.map +1 -0
  38. package/dist/client/core/errors.d.ts +80 -0
  39. package/dist/client/core/errors.d.ts.map +1 -0
  40. package/dist/client/core/errors.js +154 -0
  41. package/dist/client/core/errors.js.map +1 -0
  42. package/dist/client/core/options.d.ts +44 -0
  43. package/dist/client/core/options.d.ts.map +1 -0
  44. package/dist/client/core/options.js +2 -0
  45. package/dist/client/core/options.js.map +1 -0
  46. package/dist/client/core/session.d.ts +90 -0
  47. package/dist/client/core/session.d.ts.map +1 -0
  48. package/dist/client/core/session.js +72 -0
  49. package/dist/client/core/session.js.map +1 -0
  50. package/dist/client/core/views.d.ts +40 -0
  51. package/dist/client/core/views.d.ts.map +1 -0
  52. package/dist/client/core/views.js +32 -0
  53. package/dist/client/core/views.js.map +1 -0
  54. package/dist/client/index.d.ts +16 -0
  55. package/dist/client/index.d.ts.map +1 -0
  56. package/dist/client/index.js +9 -0
  57. package/dist/client/index.js.map +1 -0
  58. package/dist/client/mirror/collections.d.ts +125 -0
  59. package/dist/client/mirror/collections.d.ts.map +1 -0
  60. package/dist/client/mirror/collections.js +118 -0
  61. package/dist/client/mirror/collections.js.map +1 -0
  62. package/dist/client/mirror/index.d.ts +4 -0
  63. package/dist/client/mirror/index.d.ts.map +1 -0
  64. package/dist/client/mirror/index.js +4 -0
  65. package/dist/client/mirror/index.js.map +1 -0
  66. package/dist/client/mirror/writer.d.ts +39 -0
  67. package/dist/client/mirror/writer.d.ts.map +1 -0
  68. package/dist/client/mirror/writer.js +50 -0
  69. package/dist/client/mirror/writer.js.map +1 -0
  70. package/dist/client/pairing/mirrorGrant.d.ts +91 -0
  71. package/dist/client/pairing/mirrorGrant.d.ts.map +1 -0
  72. package/dist/client/pairing/mirrorGrant.js +99 -0
  73. package/dist/client/pairing/mirrorGrant.js.map +1 -0
  74. package/dist/client/pairing/mirrorReader.d.ts +99 -0
  75. package/dist/client/pairing/mirrorReader.d.ts.map +1 -0
  76. package/dist/client/pairing/mirrorReader.js +117 -0
  77. package/dist/client/pairing/mirrorReader.js.map +1 -0
  78. package/dist/client/pairing/pairingGrantExchange.d.ts +135 -0
  79. package/dist/client/pairing/pairingGrantExchange.d.ts.map +1 -0
  80. package/dist/client/pairing/pairingGrantExchange.js +186 -0
  81. package/dist/client/pairing/pairingGrantExchange.js.map +1 -0
  82. package/dist/client/pairing/pairingRequest.d.ts +82 -0
  83. package/dist/client/pairing/pairingRequest.d.ts.map +1 -0
  84. package/dist/client/pairing/pairingRequest.js +120 -0
  85. package/dist/client/pairing/pairingRequest.js.map +1 -0
  86. package/dist/client/strategy.d.ts +36 -0
  87. package/dist/client/strategy.d.ts.map +1 -0
  88. package/dist/client/strategy.js +26 -0
  89. package/dist/client/strategy.js.map +1 -0
  90. package/dist/collections/index.d.ts +3 -0
  91. package/dist/collections/index.d.ts.map +1 -0
  92. package/dist/collections/index.js +3 -0
  93. package/dist/collections/index.js.map +1 -0
  94. package/dist/collections/nodeCollections.d.ts +57 -0
  95. package/dist/collections/nodeCollections.d.ts.map +1 -0
  96. package/dist/collections/nodeCollections.js +40 -0
  97. package/dist/collections/nodeCollections.js.map +1 -0
  98. package/dist/collections/paths.d.ts +7 -0
  99. package/dist/collections/paths.d.ts.map +1 -0
  100. package/dist/collections/paths.js +13 -0
  101. package/dist/collections/paths.js.map +1 -0
  102. package/dist/crypto/collectionKeys.d.ts +28 -0
  103. package/dist/crypto/collectionKeys.d.ts.map +1 -0
  104. package/dist/crypto/collectionKeys.js +53 -0
  105. package/dist/crypto/collectionKeys.js.map +1 -0
  106. package/dist/crypto/hkdf.d.ts +14 -0
  107. package/dist/crypto/hkdf.d.ts.map +1 -0
  108. package/dist/crypto/hkdf.js +19 -0
  109. package/dist/crypto/hkdf.js.map +1 -0
  110. package/dist/crypto/index.d.ts +5 -0
  111. package/dist/crypto/index.d.ts.map +1 -0
  112. package/dist/crypto/index.js +5 -0
  113. package/dist/crypto/index.js.map +1 -0
  114. package/dist/crypto/secretEncryptor.d.ts +20 -0
  115. package/dist/crypto/secretEncryptor.d.ts.map +1 -0
  116. package/dist/crypto/secretEncryptor.js +82 -0
  117. package/dist/crypto/secretEncryptor.js.map +1 -0
  118. package/dist/crypto/wireConstants.d.ts +14 -0
  119. package/dist/crypto/wireConstants.d.ts.map +1 -0
  120. package/dist/crypto/wireConstants.js +17 -0
  121. package/dist/crypto/wireConstants.js.map +1 -0
  122. package/dist/identity/address.d.ts +3 -0
  123. package/dist/identity/address.d.ts.map +1 -0
  124. package/dist/identity/address.js +17 -0
  125. package/dist/identity/address.js.map +1 -0
  126. package/dist/identity/bip39-wordlist.d.ts +2 -0
  127. package/dist/identity/bip39-wordlist.d.ts.map +1 -0
  128. package/dist/identity/bip39-wordlist.js +2051 -0
  129. package/dist/identity/bip39-wordlist.js.map +1 -0
  130. package/dist/identity/capProvider.d.ts +45 -0
  131. package/dist/identity/capProvider.d.ts.map +1 -0
  132. package/dist/identity/capProvider.js +80 -0
  133. package/dist/identity/capProvider.js.map +1 -0
  134. package/dist/identity/derivationSchemes.d.ts +25 -0
  135. package/dist/identity/derivationSchemes.d.ts.map +1 -0
  136. package/dist/identity/derivationSchemes.js +34 -0
  137. package/dist/identity/derivationSchemes.js.map +1 -0
  138. package/dist/identity/evm.d.ts +8 -0
  139. package/dist/identity/evm.d.ts.map +1 -0
  140. package/dist/identity/evm.js +78 -0
  141. package/dist/identity/evm.js.map +1 -0
  142. package/dist/identity/index.d.ts +10 -0
  143. package/dist/identity/index.d.ts.map +1 -0
  144. package/dist/identity/index.js +10 -0
  145. package/dist/identity/index.js.map +1 -0
  146. package/dist/identity/keys.d.ts +17 -0
  147. package/dist/identity/keys.d.ts.map +1 -0
  148. package/dist/identity/keys.js +36 -0
  149. package/dist/identity/keys.js.map +1 -0
  150. package/dist/identity/mnemonic.d.ts +4 -0
  151. package/dist/identity/mnemonic.d.ts.map +1 -0
  152. package/dist/identity/mnemonic.js +93 -0
  153. package/dist/identity/mnemonic.js.map +1 -0
  154. package/dist/identity/pairing.d.ts +45 -0
  155. package/dist/identity/pairing.d.ts.map +1 -0
  156. package/dist/identity/pairing.js +91 -0
  157. package/dist/identity/pairing.js.map +1 -0
  158. package/dist/identity/pairingRequest.d.ts +99 -0
  159. package/dist/identity/pairingRequest.d.ts.map +1 -0
  160. package/dist/identity/pairingRequest.js +217 -0
  161. package/dist/identity/pairingRequest.js.map +1 -0
  162. package/dist/identity/secret.d.ts +32 -0
  163. package/dist/identity/secret.d.ts.map +1 -0
  164. package/dist/identity/secret.js +54 -0
  165. package/dist/identity/secret.js.map +1 -0
  166. package/dist/index.d.ts +9 -0
  167. package/dist/index.d.ts.map +1 -0
  168. package/dist/index.js +18 -0
  169. package/dist/index.js.map +1 -0
  170. package/dist/internal/bytes.d.ts +11 -0
  171. package/dist/internal/bytes.d.ts.map +1 -0
  172. package/dist/internal/bytes.js +38 -0
  173. package/dist/internal/bytes.js.map +1 -0
  174. package/dist/node-api/dexPair.d.ts +24 -0
  175. package/dist/node-api/dexPair.d.ts.map +1 -0
  176. package/dist/node-api/dexPair.js +39 -0
  177. package/dist/node-api/dexPair.js.map +1 -0
  178. package/dist/node-api/dsl.d.ts +10 -0
  179. package/dist/node-api/dsl.d.ts.map +1 -0
  180. package/dist/node-api/dsl.js +7 -0
  181. package/dist/node-api/dsl.js.map +1 -0
  182. package/dist/node-api/exchanges.d.ts +49 -0
  183. package/dist/node-api/exchanges.d.ts.map +1 -0
  184. package/dist/node-api/exchanges.js +32 -0
  185. package/dist/node-api/exchanges.js.map +1 -0
  186. package/dist/node-api/index.d.ts +12 -0
  187. package/dist/node-api/index.d.ts.map +1 -0
  188. package/dist/node-api/index.js +12 -0
  189. package/dist/node-api/index.js.map +1 -0
  190. package/dist/node-api/marketMaking.d.ts +39 -0
  191. package/dist/node-api/marketMaking.d.ts.map +1 -0
  192. package/dist/node-api/marketMaking.js +44 -0
  193. package/dist/node-api/marketMaking.js.map +1 -0
  194. package/dist/node-api/octobots.d.ts +14 -0
  195. package/dist/node-api/octobots.d.ts.map +1 -0
  196. package/dist/node-api/octobots.js +16 -0
  197. package/dist/node-api/octobots.js.map +1 -0
  198. package/dist/node-api/orderBookPreview.d.ts +11 -0
  199. package/dist/node-api/orderBookPreview.d.ts.map +1 -0
  200. package/dist/node-api/orderBookPreview.js +7 -0
  201. package/dist/node-api/orderBookPreview.js.map +1 -0
  202. package/dist/node-api/pairing.d.ts +39 -0
  203. package/dist/node-api/pairing.d.ts.map +1 -0
  204. package/dist/node-api/pairing.js +61 -0
  205. package/dist/node-api/pairing.js.map +1 -0
  206. package/dist/node-api/pairs.d.ts +32 -0
  207. package/dist/node-api/pairs.d.ts.map +1 -0
  208. package/dist/node-api/pairs.js +72 -0
  209. package/dist/node-api/pairs.js.map +1 -0
  210. package/dist/node-api/requiredFunds.d.ts +11 -0
  211. package/dist/node-api/requiredFunds.d.ts.map +1 -0
  212. package/dist/node-api/requiredFunds.js +7 -0
  213. package/dist/node-api/requiredFunds.js.map +1 -0
  214. package/dist/node-api/setup.d.ts +25 -0
  215. package/dist/node-api/setup.d.ts.map +1 -0
  216. package/dist/node-api/setup.js +13 -0
  217. package/dist/node-api/setup.js.map +1 -0
  218. package/dist/node-api/wallet.d.ts +55 -0
  219. package/dist/node-api/wallet.d.ts.map +1 -0
  220. package/dist/node-api/wallet.js +87 -0
  221. package/dist/node-api/wallet.js.map +1 -0
  222. package/dist/protocol/actions.d.ts +40 -0
  223. package/dist/protocol/actions.d.ts.map +1 -0
  224. package/dist/protocol/actions.js +118 -0
  225. package/dist/protocol/actions.js.map +1 -0
  226. package/dist/protocol/documents.d.ts +25 -0
  227. package/dist/protocol/documents.d.ts.map +1 -0
  228. package/dist/protocol/documents.js +2 -0
  229. package/dist/protocol/documents.js.map +1 -0
  230. package/dist/protocol/index.d.ts +10 -0
  231. package/dist/protocol/index.d.ts.map +1 -0
  232. package/dist/protocol/index.js +12 -0
  233. package/dist/protocol/index.js.map +1 -0
  234. package/dist/protocol/orchestration/createAutomation.d.ts +61 -0
  235. package/dist/protocol/orchestration/createAutomation.d.ts.map +1 -0
  236. package/dist/protocol/orchestration/createAutomation.js +71 -0
  237. package/dist/protocol/orchestration/createAutomation.js.map +1 -0
  238. package/dist/protocol/poll.d.ts +4 -0
  239. package/dist/protocol/poll.d.ts.map +1 -0
  240. package/dist/protocol/poll.js +4 -0
  241. package/dist/protocol/poll.js.map +1 -0
  242. package/dist/protocol/proposal.d.ts +36 -0
  243. package/dist/protocol/proposal.d.ts.map +1 -0
  244. package/dist/protocol/proposal.js +39 -0
  245. package/dist/protocol/proposal.js.map +1 -0
  246. package/dist/protocol/state.d.ts +87 -0
  247. package/dist/protocol/state.d.ts.map +1 -0
  248. package/dist/protocol/state.js +303 -0
  249. package/dist/protocol/state.js.map +1 -0
  250. package/dist/protocol/strategy/build.d.ts +44 -0
  251. package/dist/protocol/strategy/build.d.ts.map +1 -0
  252. package/dist/protocol/strategy/build.js +50 -0
  253. package/dist/protocol/strategy/build.js.map +1 -0
  254. package/dist/protocol/strategy/builders.d.ts +252 -0
  255. package/dist/protocol/strategy/builders.d.ts.map +1 -0
  256. package/dist/protocol/strategy/builders.js +382 -0
  257. package/dist/protocol/strategy/builders.js.map +1 -0
  258. package/dist/protocol/strategy/index.d.ts +5 -0
  259. package/dist/protocol/strategy/index.d.ts.map +1 -0
  260. package/dist/protocol/strategy/index.js +4 -0
  261. package/dist/protocol/strategy/index.js.map +1 -0
  262. package/dist/protocol/strategy/kinds.d.ts +2 -0
  263. package/dist/protocol/strategy/kinds.d.ts.map +1 -0
  264. package/dist/protocol/strategy/kinds.js +2 -0
  265. package/dist/protocol/strategy/kinds.js.map +1 -0
  266. package/dist/protocol/strategy/patch.d.ts +59 -0
  267. package/dist/protocol/strategy/patch.d.ts.map +1 -0
  268. package/dist/protocol/strategy/patch.js +253 -0
  269. package/dist/protocol/strategy/patch.js.map +1 -0
  270. package/dist/protocol/symbols.d.ts +8 -0
  271. package/dist/protocol/symbols.d.ts.map +1 -0
  272. package/dist/protocol/symbols.js +11 -0
  273. package/dist/protocol/symbols.js.map +1 -0
  274. package/dist/transport/constants.d.ts +8 -0
  275. package/dist/transport/constants.d.ts.map +1 -0
  276. package/dist/transport/constants.js +8 -0
  277. package/dist/transport/constants.js.map +1 -0
  278. package/dist/transport/documents.d.ts +27 -0
  279. package/dist/transport/documents.d.ts.map +1 -0
  280. package/dist/transport/documents.js +34 -0
  281. package/dist/transport/documents.js.map +1 -0
  282. package/dist/transport/index.d.ts +8 -0
  283. package/dist/transport/index.d.ts.map +1 -0
  284. package/dist/transport/index.js +8 -0
  285. package/dist/transport/index.js.map +1 -0
  286. package/dist/transport/probe.d.ts +44 -0
  287. package/dist/transport/probe.d.ts.map +1 -0
  288. package/dist/transport/probe.js +121 -0
  289. package/dist/transport/probe.js.map +1 -0
  290. package/dist/transport/rendezvous.d.ts +116 -0
  291. package/dist/transport/rendezvous.d.ts.map +1 -0
  292. package/dist/transport/rendezvous.js +139 -0
  293. package/dist/transport/rendezvous.js.map +1 -0
  294. package/dist/transport/rest.d.ts +35 -0
  295. package/dist/transport/rest.d.ts.map +1 -0
  296. package/dist/transport/rest.js +58 -0
  297. package/dist/transport/rest.js.map +1 -0
  298. package/dist/transport/syncClient.d.ts +14 -0
  299. package/dist/transport/syncClient.d.ts.map +1 -0
  300. package/dist/transport/syncClient.js +30 -0
  301. package/dist/transport/syncClient.js.map +1 -0
  302. package/dist/transport/urls.d.ts +35 -0
  303. package/dist/transport/urls.d.ts.map +1 -0
  304. package/dist/transport/urls.js +126 -0
  305. package/dist/transport/urls.js.map +1 -0
  306. package/package.json +55 -0
@@ -0,0 +1,91 @@
1
+ import { generateDeviceKeys, mintDeviceCap } from '@drakkar.software/starfish-identities';
2
+ import { deriveRoot } from './capProvider.js';
3
+ import { createKeyCache } from './keys.js';
4
+ import { deriveCollectionKeys } from '../crypto/collectionKeys.js';
5
+ import { NODE_COLLECTIONS } from '../collections/nodeCollections.js';
6
+ /** The two node collections a read-only pairing grants by default: enough to
7
+ * reconstruct `accounts.list()`, `automations.list()`, AND `strategies.list()`
8
+ * (the latter is implemented via a `userData` pull, not the legacy
9
+ * `strategies` collection) — with no access to `settings`, `accountTrading`,
10
+ * or the `strategies` collection itself. */
11
+ const DEFAULT_READ_ONLY_COLLECTIONS = ['userData', 'accounts'];
12
+ function assertKnownCollections(collections) {
13
+ const unknown = collections.filter((c) => !(c in NODE_COLLECTIONS));
14
+ if (unknown.length) {
15
+ throw new Error(`createReadOnlyPairing: unknown collection(s): ${unknown.join(', ')}`);
16
+ }
17
+ }
18
+ /** Mint a read-only pairing payload from the wallet's seed. The caller (e.g.
19
+ * mobile2) renders the returned string as a QR code for another device to
20
+ * scan — this package never renders QR images itself. The payload is fully
21
+ * self-contained (it carries the node's endpoint), so
22
+ * `connectReadOnlyDevice()` needs nothing else to connect. */
23
+ export async function createReadOnlyPairing(seed, derivation, node, opts) {
24
+ const root = await deriveRoot(seed, derivation);
25
+ const device = generateDeviceKeys();
26
+ const collections = opts?.collections ?? DEFAULT_READ_ONLY_COLLECTIONS;
27
+ assertKnownCollections(collections);
28
+ const scope = {
29
+ ops: ['read', 'list'],
30
+ collections,
31
+ };
32
+ const cap = await mintDeviceCap(root.keys.edPriv, root.keys.edPub, { edPubHex: device.edPub, kemPubHex: device.kemPub }, scope, opts?.ttlSec != null ? { ttlSec: opts.ttlSec } : undefined);
33
+ const secret = await createKeyCache().getEncryptionKey(seed, derivation);
34
+ const collectionKeys = deriveCollectionKeys(secret, collections);
35
+ const payload = {
36
+ v: 2,
37
+ kind: 'octobot-read-only-pairing',
38
+ node,
39
+ rootEdPub: root.keys.edPub,
40
+ userId: root.userId,
41
+ device,
42
+ cap,
43
+ scope,
44
+ collectionKeys,
45
+ };
46
+ return { payload: JSON.stringify(payload) };
47
+ }
48
+ /** Parse and structurally validate a scanned/pasted read-only pairing
49
+ * payload. Throws on anything that isn't shaped like one — callers doing QR
50
+ * classification (e.g. `classifyScannedCode`) should catch and fall through
51
+ * to the next candidate parser rather than propagate. */
52
+ export function parseReadOnlyPairing(payload) {
53
+ let parsed;
54
+ try {
55
+ parsed = JSON.parse(payload);
56
+ }
57
+ catch {
58
+ throw new Error('not valid JSON');
59
+ }
60
+ if (typeof parsed !== 'object' || parsed === null)
61
+ throw new Error('not an object');
62
+ const p = parsed;
63
+ // A v1 payload (this package's earlier, retired pairing format) is a
64
+ // RECOGNIZED-but-stale payload, not an unrecognized one — a user holding
65
+ // an old QR should be told to re-pair, not that the code is unreadable.
66
+ if (p.v === 1 && p.kind === 'octobot-read-only-pairing') {
67
+ throw new Error('read-only pairing payload is out of date (v1) — re-pair this device to get a current one');
68
+ }
69
+ if (p.v !== 2 || p.kind !== 'octobot-read-only-pairing')
70
+ throw new Error('not a read-only pairing payload');
71
+ if (typeof p.rootEdPub !== 'string' || typeof p.userId !== 'string') {
72
+ throw new Error('malformed read-only pairing payload');
73
+ }
74
+ const node = p.node;
75
+ if (!node || typeof node.host !== 'string' || typeof node.port !== 'number') {
76
+ throw new Error('malformed read-only pairing payload: node');
77
+ }
78
+ const device = p.device;
79
+ if (!device || typeof device.edPriv !== 'string' || typeof device.kemPriv !== 'string') {
80
+ throw new Error('malformed read-only pairing payload: device');
81
+ }
82
+ const cap = p.cap;
83
+ if (!cap || cap.v !== 1 || typeof cap.sig !== 'string') {
84
+ throw new Error('malformed read-only pairing payload: cap');
85
+ }
86
+ if (typeof p.collectionKeys !== 'object' || p.collectionKeys === null) {
87
+ throw new Error('malformed read-only pairing payload: collectionKeys');
88
+ }
89
+ return p;
90
+ }
91
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/identity/pairing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { type GeneratedDeviceKeys } from '@drakkar.software/starfish-identities';
2
+ import type { NodeCollectionKey } from '../collections/nodeCollections.js';
3
+ /** A website's device-code pairing request, published to the `joinsessions`
4
+ * rendezvous collection (`Infra/sync/server/drakkar_sync/apps/dk_spaces/collections.py`,
5
+ * `_pairing/session/{code}`) so the phone app can look it up by the short
6
+ * code the user types in. ONE address serves both phases of the exchange:
7
+ * this "request" doc is later overwritten in place by the phone's sealed
8
+ * "grant" doc (see `client/pairing/pairingGrantExchange.ts`) — the two
9
+ * phases are told apart on the wire by the top-level `kind` field, which
10
+ * the grant phase carries on an UNSEALED outer envelope specifically so a
11
+ * poller can tell "not yet approved" from "approved" without needing to
12
+ * unseal anything. `code` is carried on this payload (not just used as the
13
+ * address) so `popSig` can bind to it — see `popSigningInput` above — and
14
+ * so `parsePairingRequest` can reject a request record that doesn't match
15
+ * the code it was fetched under. */
16
+ export interface PairingRequestPayload {
17
+ v: 1;
18
+ kind: 'octobot-pairing-request';
19
+ code: string;
20
+ /** Who's asking: `'website'` — the original case, a third-party site
21
+ * running this package in a browser — or `'device'` — another OctoBot
22
+ * client (e.g. a second phone) pairing as a read-only viewer of this
23
+ * wallet's cloud mirror. Always present on the wire; there is no
24
+ * absent-means-website fallback (this package predates any published
25
+ * consumer, so there was never a shape to stay compatible with). The
26
+ * approving side (`website-pairing-approve.tsx` in mobile2) branches its
27
+ * copy on this — a `'device'` request has no real "origin" to verify,
28
+ * trust instead comes from the human typing the code themselves. */
29
+ requesterKind: 'website' | 'device';
30
+ devEdPub: string;
31
+ devKemPub: string;
32
+ /** `ed25519Suite.sign(popSigningInput(...), devEdPriv)`, hex. Proves the
33
+ * requester holds `devEdPriv` — not proof of the requester's identity or
34
+ * intent, which is exactly why `origin`/`code` are still what the
35
+ * approving human relies on. */
36
+ popSig: string;
37
+ /** `signKemSig({kemPub: devKemPub, edPriv: devEdPriv})` (`@drakkar.software
38
+ * /starfish-spaces`), hex — a SEPARATE proof-of-possession signature,
39
+ * over just `devKemPub`, matching the exact `{edPub, kemPub, userId,
40
+ * kemSig}` "join request" shape `starfish-spaces`' own
41
+ * `parseJoinRequest`/`inviteToNode` expect. The approving side
42
+ * reconstructs that join request from `devEdPub`/`devKemPub` (already
43
+ * present above) plus this field — see `client/pairing/mirrorGrant.ts` —
44
+ * rather than the website needing to hold a full `starfish-spaces`
45
+ * `Session` just to call `makeJoinRequest()` for an identity it has no
46
+ * wallet to derive. */
47
+ joinRequestKemSig: string;
48
+ /** Attacker-controlled in the sense that anyone can put any string here —
49
+ * the approving side must verify it (e.g. a `.well-known` fetch), not
50
+ * trust it at face value. */
51
+ origin: string;
52
+ label?: string;
53
+ requestedCollections?: NodeCollectionKey[];
54
+ createdAt: string;
55
+ expiresAt: string;
56
+ rendezvous: {
57
+ baseUrl: string;
58
+ namespace: string;
59
+ };
60
+ }
61
+ /** Create a pairing request: fresh ephemeral device keys (never the site's
62
+ * own identity), a proof-of-possession signature, and a short human code.
63
+ * Returns the code separately from `request` too (in addition to it being
64
+ * on the payload) since the caller needs it to display immediately, before
65
+ * `request` is even published. */
66
+ export declare function createPairingRequest(opts: {
67
+ origin: string;
68
+ rendezvous: {
69
+ baseUrl: string;
70
+ namespace: string;
71
+ };
72
+ label?: string;
73
+ requestedCollections?: NodeCollectionKey[];
74
+ ttlSec?: number;
75
+ /** Defaults to `'website'` — the original, still-overwhelmingly-common
76
+ * caller. Only a device-pairing consumer needs to pass `'device'`
77
+ * explicitly. The built payload always carries the field either way. */
78
+ requesterKind?: 'website' | 'device';
79
+ }): Promise<{
80
+ request: PairingRequestPayload;
81
+ device: GeneratedDeviceKeys;
82
+ code: string;
83
+ }>;
84
+ /** Parse and validate a pairing request record fetched by code. `expectedCode`
85
+ * MUST be the code the caller fetched this record under (from the address,
86
+ * not the payload) — rejecting a mismatch stops a request record signed
87
+ * for one code from being accepted after being copied/replayed to a
88
+ * different code's slot. Verifies the proof-of-possession signature and
89
+ * rejects an expired request — both before the approving side does
90
+ * anything else with it. Does NOT verify `origin` resolves to anything
91
+ * real; that needs an actual network check the approving side performs
92
+ * itself (this package stays I/O-free here). It DOES bound and
93
+ * sanity-check `origin`/`label` as strings — a length cap, a URL parse for
94
+ * `origin`, and rejecting control/bidi-override characters in both — since
95
+ * they're the two fields an approving human actually reads and relies on.
96
+ * Full homoglyph/IDNA-confusable host detection is out of scope, the same
97
+ * judgment call already made for origin verification itself. */
98
+ export declare function parsePairingRequest(payload: string, expectedCode: string): PairingRequestPayload;
99
+ //# sourceMappingURL=pairingRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairingRequest.d.ts","sourceRoot":"","sources":["../../src/identity/pairingRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,uCAAuC,CAAA;AAIpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAiE1E;;;;;;;;;;;;qCAYqC;AACrC,MAAM,WAAW,qBAAqB;IACpC,CAAC,EAAE,CAAC,CAAA;IACJ,IAAI,EAAE,yBAAyB,CAAA;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;yEAQqE;IACrE,aAAa,EAAE,SAAS,GAAG,QAAQ,CAAA;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB;;;qCAGiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;;;;4BASwB;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB;;kCAE8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CACnD;AAED;;;;mCAImC;AACnC,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;6EAEyE;IACzE,aAAa,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CACrC,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,qBAAqB,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BzF;AAgCD;;;;;;;;;;;;;iEAaiE;AACjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,qBAAqB,CA2EhG"}
@@ -0,0 +1,217 @@
1
+ import { generateDeviceKeys } from '@drakkar.software/starfish-identities';
2
+ import { ed25519Suite } from '@drakkar.software/starfish-protocol';
3
+ import { signKemSig } from '@drakkar.software/starfish-spaces';
4
+ import { toHex, hexToBytes } from '../internal/bytes.js';
5
+ /** Excludes visually-ambiguous characters (0/O, 1/I/L) — Crockford-style,
6
+ * meant to be read off one screen and typed on another. 31 symbols
7
+ * (23 letters + 8 digits), 8 characters ⇒ 8·log2(31) ≈ 39.63 bits of
8
+ * entropy, well above RFC 8628's device-flow `user_code` minimum, and
9
+ * bounded further by the rendezvous collection's short TTL and per-IP
10
+ * rate limit (not something this package controls). `randomCode()` uses
11
+ * rejection sampling (see `CODE_REJECT_THRESHOLD`) so this figure is the
12
+ * real, uniform entropy — not a biased approximation of it. */
13
+ const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
14
+ const CODE_LENGTH = 8;
15
+ // 256 is not a multiple of CODE_ALPHABET.length (31) — a plain `byte %
16
+ // length` would over-represent the first `256 % 31 = 8` symbols (A-H) by
17
+ // 12.5% in every character position. Reject any byte at or above the
18
+ // largest multiple of the alphabet length that still fits in a byte
19
+ // (Math.floor(256 / 31) * 31 = 248) and draw a replacement — the remaining
20
+ // range [0, 248) maps onto the 31 symbols with exactly uniform probability.
21
+ const CODE_REJECT_THRESHOLD = Math.floor(256 / CODE_ALPHABET.length) * CODE_ALPHABET.length;
22
+ const DEFAULT_REQUEST_TTL_SEC = 5 * 60;
23
+ // `expiresAt`/`createdAt` are NOT covered by popSig — anyone with the code
24
+ // can rewrite them, and the ONLY thing that kept the code's live window
25
+ // actually short was the 5-minute default nobody was forced to respect.
26
+ // This is the real enforcement: a request whose declared window exceeds it
27
+ // is rejected outright by parsePairingRequest, and createPairingRequest
28
+ // clamps its own ttlSec to it too so a well-meaning caller can't
29
+ // accidentally request something this package would reject anyway.
30
+ const MAX_REQUEST_TTL_SEC = 60 * 60;
31
+ function randomBytes(n) {
32
+ const b = new Uint8Array(n);
33
+ globalThis.crypto.getRandomValues(b);
34
+ return b;
35
+ }
36
+ function randomCode() {
37
+ const chars = [];
38
+ // Pull a batch at a time (rather than one byte per iteration) so a run of
39
+ // rejected bytes doesn't turn into a syscall-per-byte loop — rejection
40
+ // hits ~3% of bytes, so a fresh CODE_LENGTH-sized batch almost always
41
+ // finishes the code outright, with a further batch only on the rare tail.
42
+ while (chars.length < CODE_LENGTH) {
43
+ const batch = randomBytes(CODE_LENGTH);
44
+ for (const b of batch) {
45
+ if (b >= CODE_REJECT_THRESHOLD)
46
+ continue;
47
+ chars.push(CODE_ALPHABET[b % CODE_ALPHABET.length]);
48
+ if (chars.length === CODE_LENGTH)
49
+ break;
50
+ }
51
+ }
52
+ return chars.join('');
53
+ }
54
+ /** What binds `code`/`devEdPub`/`devKemPub` together and proves the
55
+ * requester holds `devEdPriv` — without this, a request record's public
56
+ * keys could be swapped in transit with no way to detect it, and binding
57
+ * `code` specifically prevents a validly-signed request minted for one
58
+ * code from being replayed/republished under a DIFFERENT code (the
59
+ * rendezvous collection is public-write, so anyone who captures a signed
60
+ * request record could otherwise re-publish it verbatim elsewhere). Signed
61
+ * over a canonical JSON of the three fields so a signature can't be
62
+ * replayed across a different (key-pair, code) combination. */
63
+ function popSigningInput(code, devEdPub, devKemPub) {
64
+ return new TextEncoder().encode(JSON.stringify({ code, devEdPub, devKemPub }));
65
+ }
66
+ /** Create a pairing request: fresh ephemeral device keys (never the site's
67
+ * own identity), a proof-of-possession signature, and a short human code.
68
+ * Returns the code separately from `request` too (in addition to it being
69
+ * on the payload) since the caller needs it to display immediately, before
70
+ * `request` is even published. */
71
+ export async function createPairingRequest(opts) {
72
+ const device = generateDeviceKeys();
73
+ const code = randomCode();
74
+ // Clamped, not just defaulted — a caller passing an oversized ttlSec
75
+ // would otherwise create a request parsePairingRequest rejects outright
76
+ // (see MAX_REQUEST_TTL_SEC), silently breaking their own integration
77
+ // instead of getting the shortest safe window this package will accept.
78
+ // Does not clamp a NEGATIVE ttlSec (e.g. for testing an already-expired
79
+ // request) — only the upper bound is enforced here.
80
+ const ttlSec = Math.min(opts.ttlSec ?? DEFAULT_REQUEST_TTL_SEC, MAX_REQUEST_TTL_SEC);
81
+ const now = new Date();
82
+ const popSig = toHex(ed25519Suite.sign(popSigningInput(code, device.edPub, device.kemPub), device.edPriv));
83
+ const joinRequestKemSig = signKemSig({ kemPub: device.kemPub, edPriv: device.edPriv });
84
+ const request = {
85
+ v: 1,
86
+ kind: 'octobot-pairing-request',
87
+ code,
88
+ requesterKind: opts.requesterKind ?? 'website',
89
+ devEdPub: device.edPub,
90
+ devKemPub: device.kemPub,
91
+ popSig,
92
+ joinRequestKemSig,
93
+ origin: opts.origin,
94
+ ...(opts.label ? { label: opts.label } : {}),
95
+ ...(opts.requestedCollections ? { requestedCollections: opts.requestedCollections } : {}),
96
+ createdAt: now.toISOString(),
97
+ expiresAt: new Date(now.getTime() + ttlSec * 1000).toISOString(),
98
+ rendezvous: opts.rendezvous,
99
+ };
100
+ return { request, device, code };
101
+ }
102
+ // Ed25519/X25519 public keys are 32 raw bytes; an Ed25519 signature is 64 —
103
+ // hex-encoded, that's exactly these lengths. Checked BEFORE hexToBytes is
104
+ // called on any of them: hexToBytes allocates proportionally to input
105
+ // length with no ceiling of its own, so an unbounded hex string here would
106
+ // allocate before ever reaching signature verification.
107
+ const HEX_KEY_LENGTH = 64;
108
+ const HEX_SIG_LENGTH = 128;
109
+ const MAX_ORIGIN_LENGTH = 2048;
110
+ const MAX_LABEL_LENGTH = 200;
111
+ // C0/C1 control characters (including \n, \r — RN <Text> renders an
112
+ // embedded newline as a real line break, letting attacker text masquerade
113
+ // as extra app chrome) plus the Unicode bidi override/isolate controls
114
+ // (U+202A-E, U+2066-9) that can visually reverse or reorder a rendered
115
+ // string — e.g. making a hostile host read as a different one. Written as
116
+ // explicit \u escapes, never literal control/bidi characters in source.
117
+ const UNSAFE_TEXT_PATTERN = /[\u0000-\u001F\u007F-\u009F\u202A-\u202E\u2066-\u2069]/;
118
+ function assertBoundedSafeText(value, field, maxLength) {
119
+ if (value.length > maxLength)
120
+ throw new Error(`pairing request: ${field} exceeds max length`);
121
+ if (UNSAFE_TEXT_PATTERN.test(value)) {
122
+ throw new Error(`pairing request: ${field} contains a disallowed control or bidi-override character`);
123
+ }
124
+ }
125
+ function assertHexLength(value, field, expectedLength) {
126
+ if (value.length !== expectedLength || !/^[0-9a-fA-F]+$/.test(value)) {
127
+ throw new Error(`pairing request: ${field} is not a valid ${expectedLength}-character hex string`);
128
+ }
129
+ }
130
+ /** Parse and validate a pairing request record fetched by code. `expectedCode`
131
+ * MUST be the code the caller fetched this record under (from the address,
132
+ * not the payload) — rejecting a mismatch stops a request record signed
133
+ * for one code from being accepted after being copied/replayed to a
134
+ * different code's slot. Verifies the proof-of-possession signature and
135
+ * rejects an expired request — both before the approving side does
136
+ * anything else with it. Does NOT verify `origin` resolves to anything
137
+ * real; that needs an actual network check the approving side performs
138
+ * itself (this package stays I/O-free here). It DOES bound and
139
+ * sanity-check `origin`/`label` as strings — a length cap, a URL parse for
140
+ * `origin`, and rejecting control/bidi-override characters in both — since
141
+ * they're the two fields an approving human actually reads and relies on.
142
+ * Full homoglyph/IDNA-confusable host detection is out of scope, the same
143
+ * judgment call already made for origin verification itself. */
144
+ export function parsePairingRequest(payload, expectedCode) {
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(payload);
148
+ }
149
+ catch {
150
+ throw new Error('not valid JSON');
151
+ }
152
+ if (typeof parsed !== 'object' || parsed === null)
153
+ throw new Error('not an object');
154
+ const p = parsed;
155
+ if (p.v !== 1 || p.kind !== 'octobot-pairing-request')
156
+ throw new Error('not a pairing request payload');
157
+ if (typeof p.code !== 'string' || typeof p.devEdPub !== 'string' || typeof p.devKemPub !== 'string'
158
+ || typeof p.popSig !== 'string' || typeof p.joinRequestKemSig !== 'string' || typeof p.origin !== 'string'
159
+ || typeof p.createdAt !== 'string' || typeof p.expiresAt !== 'string') {
160
+ throw new Error('malformed pairing request payload');
161
+ }
162
+ if (p.requesterKind !== 'website' && p.requesterKind !== 'device') {
163
+ throw new Error('malformed pairing request payload: requesterKind');
164
+ }
165
+ if (p.code !== expectedCode) {
166
+ throw new Error('pairing request: code does not match the address it was fetched from');
167
+ }
168
+ const rendezvous = p.rendezvous;
169
+ if (!rendezvous || typeof rendezvous.baseUrl !== 'string' || typeof rendezvous.namespace !== 'string') {
170
+ throw new Error('malformed pairing request payload: rendezvous');
171
+ }
172
+ if (p.label !== undefined && typeof p.label !== 'string') {
173
+ throw new Error('malformed pairing request payload: label');
174
+ }
175
+ assertHexLength(p.devEdPub, 'devEdPub', HEX_KEY_LENGTH);
176
+ assertHexLength(p.devKemPub, 'devKemPub', HEX_KEY_LENGTH);
177
+ assertHexLength(p.popSig, 'popSig', HEX_SIG_LENGTH);
178
+ assertHexLength(p.joinRequestKemSig, 'joinRequestKemSig', HEX_SIG_LENGTH);
179
+ assertBoundedSafeText(p.origin, 'origin', MAX_ORIGIN_LENGTH);
180
+ if (p.label !== undefined)
181
+ assertBoundedSafeText(p.label, 'label', MAX_LABEL_LENGTH);
182
+ try {
183
+ new URL(p.origin);
184
+ }
185
+ catch {
186
+ throw new Error('pairing request: origin is not a valid URL');
187
+ }
188
+ const request = p;
189
+ const verified = ed25519Suite.verify(hexToBytes(request.popSig), popSigningInput(request.code, request.devEdPub, request.devKemPub), request.devEdPub);
190
+ if (!verified)
191
+ throw new Error('pairing request: invalid proof-of-possession signature');
192
+ // Date.parse of a malformed string returns NaN, and every comparison
193
+ // against NaN is false — so a garbage expiresAt would otherwise be
194
+ // silently treated as "not expired" instead of rejected. Fail closed.
195
+ const expiresAtMs = Date.parse(request.expiresAt);
196
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now())
197
+ throw new Error('pairing request: expired');
198
+ // The actual enforcement of "short-lived code" — expiresAt/createdAt are
199
+ // NOT covered by popSig, so a party with the code can rewrite either to
200
+ // anything. An earlier version of this check compared expiresAt against
201
+ // the request's OWN createdAt — trivially bypassable, since an attacker
202
+ // controlling both fields can place them arbitrarily far in the future
203
+ // while keeping their difference inside the cap, making the code "look"
204
+ // freshly issued no matter when it's actually redeemed (e.g.
205
+ // createdAt = now+364d, expiresAt = now+364d+1h — passes a
206
+ // createdAt-relative check, but the code stays "valid" for the next
207
+ // year). Anchoring to THIS CALL's real wall clock instead closes that:
208
+ // a request cannot claim to remain valid more than MAX_REQUEST_TTL_SEC
209
+ // from right now, independent of what it claims createdAt to be.
210
+ // createdAt itself is otherwise purely informational — never used for
211
+ // any security decision here.
212
+ if (expiresAtMs - Date.now() > MAX_REQUEST_TTL_SEC * 1000) {
213
+ throw new Error('pairing request: expiry window exceeds the maximum this package allows');
214
+ }
215
+ return request;
216
+ }
217
+ //# sourceMappingURL=pairingRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairingRequest.js","sourceRoot":"","sources":["../../src/identity/pairingRequest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /** What a string of key material turned out to be. */
2
+ export type ClassifiedSecret = {
3
+ kind: 'privateKey';
4
+ value: string;
5
+ } | {
6
+ kind: 'seed';
7
+ value: string;
8
+ } | {
9
+ kind: 'unknown';
10
+ };
11
+ /** Classify key material that arrived somewhere already known to hold it — a
12
+ * pairing payload's `password`, a standalone `{"password": "…"}` code, or a
13
+ * phrase typed by hand.
14
+ *
15
+ * The `0x` prefix picks the branch, and the branch still validates: a value
16
+ * that is neither a real key nor a real phrase has to be rejected rather than
17
+ * quietly produce a wallet nobody owns.
18
+ *
19
+ * Note what this deliberately does **not** decide: which derivation SCHEME
20
+ * a `seed` needs, if a caller has registered more than the built-in
21
+ * `'bip44'` (see `derivationSchemes.ts`) — that depends on where the phrase
22
+ * came from, not on its shape. */
23
+ export declare function classifySecret(raw: string): Promise<ClassifiedSecret>;
24
+ /** Classify a bare scanned/typed value, where no field name says which shape to
25
+ * expect, so both are tried. Unlike `classifySecret` an unprefixed 64-hex still
26
+ * reads as a key here — there is no envelope to say otherwise. */
27
+ export declare function classifyBareSecret(raw: string): Promise<ClassifiedSecret>;
28
+ /** The wallet inside a standalone `{"password": "…"}` code — JSON carrying key
29
+ * material directly, with no node attached to fetch it from. Null when the
30
+ * value is not that shape. */
31
+ export declare function readSecretEnvelope(data: string): string | null;
32
+ //# sourceMappingURL=secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.d.ts","sourceRoot":"","sources":["../../src/identity/secret.ts"],"names":[],"mappings":"AAGA,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAEvB;;;;;;;;;;;mCAWmC;AACnC,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAW3E;AAED;;mEAEmE;AACnE,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAS/E;AAED;;+BAE+B;AAC/B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU9D"}
@@ -0,0 +1,54 @@
1
+ import { isEvmPrivateKey, normalizeEvmPrivateKey } from './evm.js';
2
+ import { validateSeedPhrase } from './mnemonic.js';
3
+ /** Classify key material that arrived somewhere already known to hold it — a
4
+ * pairing payload's `password`, a standalone `{"password": "…"}` code, or a
5
+ * phrase typed by hand.
6
+ *
7
+ * The `0x` prefix picks the branch, and the branch still validates: a value
8
+ * that is neither a real key nor a real phrase has to be rejected rather than
9
+ * quietly produce a wallet nobody owns.
10
+ *
11
+ * Note what this deliberately does **not** decide: which derivation SCHEME
12
+ * a `seed` needs, if a caller has registered more than the built-in
13
+ * `'bip44'` (see `derivationSchemes.ts`) — that depends on where the phrase
14
+ * came from, not on its shape. */
15
+ export async function classifySecret(raw) {
16
+ const trimmed = raw.trim();
17
+ if (trimmed.toLowerCase().startsWith('0x')) {
18
+ const key = normalizeEvmPrivateKey(trimmed);
19
+ return isEvmPrivateKey(key) ? { kind: 'privateKey', value: key } : { kind: 'unknown' };
20
+ }
21
+ const phrase = trimmed.split(/\s+/).map((w) => w.toLowerCase()).filter(Boolean).join(' ');
22
+ if (phrase && (await validateSeedPhrase(phrase)))
23
+ return { kind: 'seed', value: phrase };
24
+ return { kind: 'unknown' };
25
+ }
26
+ /** Classify a bare scanned/typed value, where no field name says which shape to
27
+ * expect, so both are tried. Unlike `classifySecret` an unprefixed 64-hex still
28
+ * reads as a key here — there is no envelope to say otherwise. */
29
+ export async function classifyBareSecret(raw) {
30
+ const trimmed = raw.trim();
31
+ const key = normalizeEvmPrivateKey(trimmed);
32
+ if (isEvmPrivateKey(key))
33
+ return { kind: 'privateKey', value: key };
34
+ const phrase = trimmed.split(/\s+/).map((w) => w.toLowerCase()).filter(Boolean).join(' ');
35
+ if (phrase && (await validateSeedPhrase(phrase)))
36
+ return { kind: 'seed', value: phrase };
37
+ return { kind: 'unknown' };
38
+ }
39
+ /** The wallet inside a standalone `{"password": "…"}` code — JSON carrying key
40
+ * material directly, with no node attached to fetch it from. Null when the
41
+ * value is not that shape. */
42
+ export function readSecretEnvelope(data) {
43
+ try {
44
+ const parsed = JSON.parse(data.trim());
45
+ if (parsed && typeof parsed === 'object' && typeof parsed.password === 'string') {
46
+ return parsed.password;
47
+ }
48
+ }
49
+ catch {
50
+ // not JSON — not an envelope
51
+ }
52
+ return null;
53
+ }
54
+ //# sourceMappingURL=secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.js","sourceRoot":"","sources":["../../src/identity/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAQlD;;;;;;;;;;;mCAWmC;AACnC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IAE1B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAA;QAC3C,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzF,IAAI,MAAM,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IACxF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AAC5B,CAAC;AAED;;mEAEmE;AACnE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IAE1B,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,eAAe,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;IAEnE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzF,IAAI,MAAM,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IACxF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AAC5B,CAAC;AAED;;+BAE+B;AAC/B,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QACtC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChF,OAAO,MAAM,CAAC,QAAQ,CAAA;QACxB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { connectOctoBot, type OctoBotClient, type ConnectOptions, type SeedDerivation, type CallOptions, type AccountsApi, type AccountInput, type AutomationsApi, type CreateAutomationInput, type CreateAutomationProgress, type StrategiesApi, type SettingsApi, type NodeApi, type DocumentsApi, type ReadOnlyDocumentsApi, type ActionHandle, type AccountView, type AccountKind, type AutomationView, type Holding, OctoBotError, OctoBotConfigError, OctoBotConnectionError, OctoBotAuthError, OctoBotHttpError, OctoBotConflictError, OctoBotActionError, OctoBotTimeoutError, OctoBotScopeError, isOctoBotError, type OctoBotErrorCode, connectReadOnlyDevice, type ConnectReadOnlyOptions, type ReadOnlyOctoBotClient, type ReadOnlyAccountsApi, type ReadOnlyAutomationsApi, type ReadOnlyStrategiesApi, type ProposedAction, accountViewOf, automationViewOf, startPairingRequest, fetchPairingRequestByCode, type StartPairingRequestOptions, type PairingRequestSession, type PairingRequestLookup, buildJoinRequestJson, mintPairingGrant, revokePairingGrant, NothingToShareError, type MintedPairingGrant, parseMirrorGrantBundle, readMirrorCollections, attemptDirectMirrorWrite, type MirrorGrantBundle, type MirrorGrantNodeRef, type ReadMirrorCollectionsOptions, type AttemptDirectMirrorWriteOptions, publishPairingGrant, clearPairingGrant, fetchPairingGrant, awaitPairingGrant, type UnsealedPairingGrant, } from './client/index.js';
2
+ export { MIRROR_COLLECTIONS, DEFAULT_MIRROR_COLLECTIONS, THIRD_PARTY_ELIGIBLE_MIRROR_COLLECTIONS, MIRROR_SPACE_NAME, isIsolatedMirrorCollection, isKnownMirrorCollection, isPublicMirrorCollection, isThirdPartyEligible, mirrorVisibilityFor, type MirrorCollection, type MirrorCollectionId, type MirrorVisibility, syncCloudMirror, type SyncCloudMirrorOptions, type SyncCloudMirrorResult, } from './client/mirror/index.js';
3
+ export { strategy, type StrategyBuilders } from './client/strategy.js';
4
+ export { createReadOnlyPairing, parseReadOnlyPairing, type ReadOnlyPairingPayload } from './identity/pairing.js';
5
+ export { createPairingRequest, parsePairingRequest, type PairingRequestPayload } from './identity/pairingRequest.js';
6
+ export { createRendezvousClient, pullRendezvousDoc, pushRendezvousDoc, clearRendezvousDoc, joinSessionPath, type RendezvousDoc, } from './transport/rendezvous.js';
7
+ export { encodeActionProposal, decodeActionProposal, type ActionProposal, type ProposedActionEntry } from './protocol/proposal.js';
8
+ export type { Strategy as ProtocolStrategy, Account as ProtocolAccount, AccountState, AccountAuthentication, ExchangeConfig, AutomationState, WorkflowStatus, DetailedAsset, UserAction, UserActionConfiguration, DslKeywordsState, MarketMakingConfiguration, AccountTrading, AccountTradingWithAccountId, DetailedAssetsForTradingType, CreateAutomationConfiguration, } from '@drakkar.software/octobot-protocol';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,uCAAuC,EACvC,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAChH,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACpH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAOlI,YAAY,EACV,QAAQ,IAAI,gBAAgB,EAC5B,OAAO,IAAI,eAAe,EAC1B,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,uBAAuB,EACvB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,oCAAoC,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ // @drakkar.software/octobot-client — a TypeScript client for the OctoBot node
2
+ // protocol: wallet identity, accounts, automations, strategies, and user
3
+ // actions over the Starfish sync transport.
4
+ //
5
+ // This is the facade tier. It keeps no document cache and no offline
6
+ // queue — every read is a fresh pull, every write is a fresh push/append.
7
+ // An app that needs persistence, an offline queue, or CRDT merge across
8
+ // devices builds that layer ON TOP of this package (see
9
+ // https://docs.octobot.cloud/client-sdk/advanced-primitives for the subpath
10
+ // exports this is built from).
11
+ export { connectOctoBot, OctoBotError, OctoBotConfigError, OctoBotConnectionError, OctoBotAuthError, OctoBotHttpError, OctoBotConflictError, OctoBotActionError, OctoBotTimeoutError, OctoBotScopeError, isOctoBotError, connectReadOnlyDevice, accountViewOf, automationViewOf, startPairingRequest, fetchPairingRequestByCode, buildJoinRequestJson, mintPairingGrant, revokePairingGrant, NothingToShareError, parseMirrorGrantBundle, readMirrorCollections, attemptDirectMirrorWrite, publishPairingGrant, clearPairingGrant, fetchPairingGrant, awaitPairingGrant, } from './client/index.js';
12
+ export { MIRROR_COLLECTIONS, DEFAULT_MIRROR_COLLECTIONS, THIRD_PARTY_ELIGIBLE_MIRROR_COLLECTIONS, MIRROR_SPACE_NAME, isIsolatedMirrorCollection, isKnownMirrorCollection, isPublicMirrorCollection, isThirdPartyEligible, mirrorVisibilityFor, syncCloudMirror, } from './client/mirror/index.js';
13
+ export { strategy } from './client/strategy.js';
14
+ export { createReadOnlyPairing, parseReadOnlyPairing } from './identity/pairing.js';
15
+ export { createPairingRequest, parsePairingRequest } from './identity/pairingRequest.js';
16
+ export { createRendezvousClient, pullRendezvousDoc, pushRendezvousDoc, clearRendezvousDoc, joinSessionPath, } from './transport/rendezvous.js';
17
+ export { encodeActionProposal, decodeActionProposal } from './protocol/proposal.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yEAAyE;AACzE,4CAA4C;AAC5C,EAAE;AACF,qEAAqE;AACrE,0EAA0E;AAC1E,wEAAwE;AACxE,wDAAwD;AACxD,4EAA4E;AAC5E,+BAA+B;AAE/B,OAAO,EACL,cAAc,EAoBd,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EAEd,qBAAqB,EAOrB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EAIzB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EAEnB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EAKxB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,uCAAuC,EACvC,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EAInB,eAAe,GAGhB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAyB,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAA+B,MAAM,uBAAuB,CAAA;AAChH,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAA8B,MAAM,8BAA8B,CAAA;AACpH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAEhB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAiD,MAAM,wBAAwB,CAAA"}
@@ -0,0 +1,11 @@
1
+ export declare function toHex(b: Uint8Array): string;
2
+ export declare function hexToBytes(hex: string): Uint8Array;
3
+ export declare function toBase64(bytes: Uint8Array): string;
4
+ export declare function fromBase64(b64: string): Uint8Array<ArrayBuffer>;
5
+ /** URL-safe base64 (`+/` → `-_`, no `=` padding) of raw bytes. Distinct from
6
+ * `starfish-protocol`'s `toBase64Url`, which encodes a UTF-8 *string* — not
7
+ * usable here since not every byte sequence (e.g. random session-id bytes)
8
+ * round-trips through UTF-8. */
9
+ export declare function toBase64Url(bytes: Uint8Array): string;
10
+ export declare function fromBase64Url(b64url: string): Uint8Array<ArrayBuffer>;
11
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../../src/internal/bytes.ts"],"names":[],"mappings":"AAKA,wBAAgB,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAE3C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAKlD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAElD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAM/D;AAED;;;iCAGiC;AACjC,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAIrE"}