@did-btcr2/api 0.2.2 → 0.3.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 (73) hide show
  1. package/dist/browser.js +57253 -55798
  2. package/dist/browser.mjs +57252 -55797
  3. package/dist/cjs/api.js +31 -934
  4. package/dist/cjs/api.js.map +1 -1
  5. package/dist/cjs/bitcoin.js +110 -0
  6. package/dist/cjs/bitcoin.js.map +1 -0
  7. package/dist/cjs/cas.js +90 -0
  8. package/dist/cjs/cas.js.map +1 -0
  9. package/dist/cjs/crypto.js +425 -0
  10. package/dist/cjs/crypto.js.map +1 -0
  11. package/dist/cjs/did.js +70 -0
  12. package/dist/cjs/did.js.map +1 -0
  13. package/dist/cjs/helpers.js +28 -0
  14. package/dist/cjs/helpers.js.map +1 -0
  15. package/dist/cjs/index.js +12 -0
  16. package/dist/cjs/index.js.map +1 -1
  17. package/dist/cjs/kms.js +73 -0
  18. package/dist/cjs/kms.js.map +1 -0
  19. package/dist/cjs/method.js +262 -0
  20. package/dist/cjs/method.js.map +1 -0
  21. package/dist/cjs/types.js +2 -0
  22. package/dist/cjs/types.js.map +1 -0
  23. package/dist/esm/api.js +31 -934
  24. package/dist/esm/api.js.map +1 -1
  25. package/dist/esm/bitcoin.js +110 -0
  26. package/dist/esm/bitcoin.js.map +1 -0
  27. package/dist/esm/cas.js +90 -0
  28. package/dist/esm/cas.js.map +1 -0
  29. package/dist/esm/crypto.js +425 -0
  30. package/dist/esm/crypto.js.map +1 -0
  31. package/dist/esm/did.js +70 -0
  32. package/dist/esm/did.js.map +1 -0
  33. package/dist/esm/helpers.js +28 -0
  34. package/dist/esm/helpers.js.map +1 -0
  35. package/dist/esm/index.js +12 -0
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/esm/kms.js +73 -0
  38. package/dist/esm/kms.js.map +1 -0
  39. package/dist/esm/method.js +262 -0
  40. package/dist/esm/method.js.map +1 -0
  41. package/dist/esm/types.js +2 -0
  42. package/dist/esm/types.js.map +1 -0
  43. package/dist/types/api.d.ts +19 -693
  44. package/dist/types/api.d.ts.map +1 -1
  45. package/dist/types/bitcoin.d.ts +64 -0
  46. package/dist/types/bitcoin.d.ts.map +1 -0
  47. package/dist/types/cas.d.ts +70 -0
  48. package/dist/types/cas.d.ts.map +1 -0
  49. package/dist/types/crypto.d.ts +310 -0
  50. package/dist/types/crypto.d.ts.map +1 -0
  51. package/dist/types/did.d.ts +51 -0
  52. package/dist/types/did.d.ts.map +1 -0
  53. package/dist/types/helpers.d.ts +10 -0
  54. package/dist/types/helpers.d.ts.map +1 -0
  55. package/dist/types/index.d.ts +14 -0
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/kms.d.ts +49 -0
  58. package/dist/types/kms.d.ts.map +1 -0
  59. package/dist/types/method.d.ts +117 -0
  60. package/dist/types/method.d.ts.map +1 -0
  61. package/dist/types/types.d.ts +128 -0
  62. package/dist/types/types.d.ts.map +1 -0
  63. package/package.json +5 -5
  64. package/src/api.ts +40 -1317
  65. package/src/bitcoin.ts +129 -0
  66. package/src/cas.ts +121 -0
  67. package/src/crypto.ts +525 -0
  68. package/src/did.ts +75 -0
  69. package/src/helpers.ts +35 -0
  70. package/src/index.ts +37 -1
  71. package/src/kms.ts +95 -0
  72. package/src/method.ts +331 -0
  73. package/src/types.ts +122 -0
package/src/bitcoin.ts ADDED
@@ -0,0 +1,129 @@
1
+ import {
2
+ BitcoinConnection,
3
+ BitcoinCoreRpcClient,
4
+ BitcoinRestClient,
5
+ type RawTransactionRest
6
+ } from '@did-btcr2/bitcoin';
7
+ import { assertString } from './helpers.js';
8
+ import type { BitcoinApiConfig } from './types.js';
9
+
10
+ /**
11
+ * Bitcoin network operations sub-facade.
12
+ * Always backed by a {@link BitcoinConnection} so it can be passed to
13
+ * resolve/update without extra configuration.
14
+ *
15
+ * Lazily initialized by {@link DidBtcr2Api} to avoid connection overhead
16
+ * when Bitcoin features are not used.
17
+ * @public
18
+ */
19
+ export class BitcoinApi {
20
+ /** The underlying BitcoinConnection used for all operations. */
21
+ readonly connection: BitcoinConnection;
22
+
23
+ /** REST client for the active network. */
24
+ get rest(): BitcoinRestClient {
25
+ return this.connection.rest;
26
+ }
27
+
28
+ /**
29
+ * RPC client for the active network, or `undefined` if not configured.
30
+ * Use {@link requireRpc} when RPC is expected to be available.
31
+ */
32
+ get rpc(): BitcoinCoreRpcClient | undefined {
33
+ return this.connection.rpc;
34
+ }
35
+
36
+ /** Whether an RPC client is available for this network. */
37
+ get hasRpc(): boolean {
38
+ return this.connection.rpc !== undefined;
39
+ }
40
+
41
+ /**
42
+ * RPC client for the active network.
43
+ * @throws {Error} If RPC was not configured for this network.
44
+ */
45
+ requireRpc(): BitcoinCoreRpcClient {
46
+ const client = this.connection.rpc;
47
+ if (!client) {
48
+ throw new Error(
49
+ 'RPC client not configured. Pass an rpc config when creating the BitcoinApi, e.g.: '
50
+ + '{ network: \'regtest\', rpc: { host: \'http://localhost:18443\', username: \'u\', password: \'p\' } }'
51
+ );
52
+ }
53
+ return client;
54
+ }
55
+
56
+ /**
57
+ * Create a BitcoinApi for a specific network with optional endpoint overrides.
58
+ * Uses BitcoinConnection.forNetwork() — no env vars consulted.
59
+ * @param cfg The network and optional REST/RPC overrides.
60
+ */
61
+ constructor(cfg: BitcoinApiConfig) {
62
+ let executor = cfg.executor;
63
+ // Wrap the default fetch with a timeout if configured and no custom
64
+ // executor was provided.
65
+ if (!executor && cfg.timeoutMs !== undefined) {
66
+ const ms = cfg.timeoutMs;
67
+ executor = (req) => fetch(req.url, {
68
+ method : req.method,
69
+ headers : req.headers,
70
+ body : req.body,
71
+ signal : AbortSignal.timeout(ms),
72
+ });
73
+ }
74
+ this.connection = BitcoinConnection.forNetwork(cfg.network, {
75
+ rest : cfg.rest,
76
+ rpc : cfg.rpc,
77
+ executor,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Fetch a transaction by txid via REST.
83
+ * @param txid The transaction ID (64-character hex string).
84
+ * @returns The fetched transaction.
85
+ */
86
+ async getTransaction(txid: string): Promise<RawTransactionRest> {
87
+ assertString(txid, 'txid');
88
+ return await this.rest.transaction.get(txid);
89
+ }
90
+
91
+ /**
92
+ * Broadcast a raw tx (hex) via REST.
93
+ * @param rawTxHex The raw transaction hex string.
94
+ */
95
+ async send(rawTxHex: string) {
96
+ assertString(rawTxHex, 'rawTxHex');
97
+ return await this.rest.transaction.send(rawTxHex);
98
+ }
99
+
100
+ /**
101
+ * Get UTXOs for an address via REST.
102
+ * @param address The Bitcoin address.
103
+ */
104
+ async getUtxos(address: string) {
105
+ assertString(address, 'address');
106
+ return await this.rest.address.getUtxos(address);
107
+ }
108
+
109
+ /**
110
+ * Get a block by hash or height via REST.
111
+ * @param params Block identifier — at least one of `hash` or `height` is required.
112
+ */
113
+ async getBlock(params: { hash?: string; height?: number }) {
114
+ if (!params.hash && params.height === undefined) {
115
+ throw new Error('getBlock requires at least one of hash or height.');
116
+ }
117
+ return await this.rest.block.get({ blockhash: params.hash, height: params.height });
118
+ }
119
+
120
+ /** Convert BTC to satoshis (integer-safe string-split arithmetic). */
121
+ static btcToSats(btc: number): number {
122
+ return BitcoinConnection.btcToSats(btc);
123
+ }
124
+
125
+ /** Convert satoshis to BTC (integer-safe string-split arithmetic). */
126
+ static satsToBtc(sats: number): number {
127
+ return BitcoinConnection.satsToBtc(sats);
128
+ }
129
+ }
package/src/cas.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { canonicalize, decode as decodeHash } from '@did-btcr2/common';
2
+ import type { Helia } from 'helia';
3
+ import { CID } from 'multiformats/cid';
4
+ import * as raw from 'multiformats/codecs/raw';
5
+ import { create as createDigest } from 'multiformats/hashes/digest';
6
+ import { sha256 } from 'multiformats/hashes/sha2';
7
+ import { assertString } from './helpers.js';
8
+
9
+ /**
10
+ * Executor interface for content-addressed storage.
11
+ *
12
+ * Implementations handle the actual I/O (IPFS, HTTP gateway, local store, etc.).
13
+ * All hashes are base64url-encoded SHA-256 digests (no padding).
14
+ * @public
15
+ */
16
+ export interface CasExecutor {
17
+ /** Retrieve raw bytes by base64url SHA-256 hash. Returns null if not found. */
18
+ retrieve(hash: string): Promise<Uint8Array | null>;
19
+ /** Publish raw bytes and return the base64url SHA-256 hash. */
20
+ publish(data: Uint8Array): Promise<string>;
21
+ }
22
+
23
+ /**
24
+ * Default {@link CasExecutor} backed by IPFS via Helia.
25
+ *
26
+ * Stores/retrieves data as raw blocks (`0x55` codec) with SHA-256 hashing.
27
+ * The CID is deterministically derived from the content hash, so lookups
28
+ * by base64url SHA-256 hash translate directly to CID lookups.
29
+ * @public
30
+ */
31
+ export class IpfsCasExecutor implements CasExecutor {
32
+ readonly #helia: Helia;
33
+
34
+ constructor(helia: Helia) {
35
+ this.#helia = helia;
36
+ }
37
+
38
+ async retrieve(hash: string): Promise<Uint8Array | null> {
39
+ const hashBytes = decodeHash(hash, 'base64url');
40
+ const cid = CID.create(1, raw.code, createDigest(sha256.code, hashBytes));
41
+ try {
42
+ return await this.#helia.blockstore.get(cid);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ async publish(data: Uint8Array): Promise<string> {
49
+ const digest = await sha256.digest(data);
50
+ const cid = CID.createV1(raw.code, digest);
51
+ await this.#helia.blockstore.put(cid, data);
52
+ // Return base64url-encoded hash (no padding)
53
+ return btoa(String.fromCharCode(...digest.bytes.slice(2)))
54
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Configuration for the CAS (Content-Addressed Storage) driver.
60
+ * @public
61
+ */
62
+ export type CasConfig = {
63
+ /** Custom executor implementation (overrides the default IPFS executor). */
64
+ executor?: CasExecutor;
65
+ /** Pre-existing Helia instance for the default IPFS executor. */
66
+ helia?: Helia;
67
+ };
68
+
69
+ /**
70
+ * Content-Addressed Storage API sub-facade.
71
+ *
72
+ * Provides `publish` and `retrieve` for JSON objects using their
73
+ * JCS-canonicalized SHA-256 hash as the content address.
74
+ *
75
+ * By default uses IPFS (via Helia). Inject a custom {@link CasExecutor}
76
+ * to use a different CAS backend.
77
+ *
78
+ * Lazily initialized by {@link DidBtcr2Api} to avoid startup overhead
79
+ * when CAS features are not used.
80
+ * @public
81
+ */
82
+ export class CasApi {
83
+ readonly #executor: CasExecutor;
84
+
85
+ constructor(config: CasConfig) {
86
+ if (config.executor) {
87
+ this.#executor = config.executor;
88
+ } else if (config.helia) {
89
+ this.#executor = new IpfsCasExecutor(config.helia);
90
+ } else {
91
+ throw new Error(
92
+ 'CAS configuration requires either an executor or a Helia instance. '
93
+ + 'Example: createApi({ cas: { helia: await createHelia() } })'
94
+ );
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Retrieve a JSON object from the CAS by its base64url SHA-256 hash.
100
+ * @param hash Base64url-encoded SHA-256 hash of the JCS-canonicalized object.
101
+ * @returns The parsed JSON object, or `null` if not found.
102
+ */
103
+ async retrieve(hash: string): Promise<object | null> {
104
+ assertString(hash, 'hash');
105
+ const bytes = await this.#executor.retrieve(hash);
106
+ if (!bytes) return null;
107
+ return JSON.parse(new TextDecoder().decode(bytes)) as object;
108
+ }
109
+
110
+ /**
111
+ * Publish a JSON object to the CAS.
112
+ * The object is JCS-canonicalized before storage; the returned hash
113
+ * matches what {@link canonicalHash} would produce.
114
+ * @param object The JSON object to publish.
115
+ * @returns The base64url-encoded SHA-256 hash (content address).
116
+ */
117
+ async publish(object: object): Promise<string> {
118
+ const bytes = new TextEncoder().encode(canonicalize(object as Record<string, any>));
119
+ return await this.#executor.publish(bytes);
120
+ }
121
+ }