@bsv/sdk 1.0.33 → 1.0.34

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 (130) hide show
  1. package/README.md +1 -3
  2. package/dist/cjs/mod.js +1 -0
  3. package/dist/cjs/mod.js.map +1 -1
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/src/transaction/Transaction.js +5 -3
  6. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  7. package/dist/cjs/src/transaction/broadcasters/ARC.js +37 -25
  8. package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
  9. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +12 -0
  10. package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
  11. package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +66 -0
  12. package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
  13. package/dist/cjs/src/transaction/broadcasters/index.js +5 -5
  14. package/dist/cjs/src/transaction/broadcasters/index.js.map +1 -1
  15. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js +12 -0
  16. package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
  17. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js +49 -0
  18. package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
  19. package/dist/cjs/src/transaction/chaintrackers/index.js +11 -0
  20. package/dist/cjs/src/transaction/chaintrackers/index.js.map +1 -0
  21. package/dist/cjs/src/transaction/{broadcasters → http}/DefaultHttpClient.js +8 -4
  22. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -0
  23. package/dist/cjs/src/transaction/http/FetchHttpClient.js +29 -0
  24. package/dist/cjs/src/transaction/http/FetchHttpClient.js.map +1 -0
  25. package/dist/cjs/src/transaction/http/HttpClient.js.map +1 -0
  26. package/dist/cjs/src/transaction/{broadcasters → http}/NodejsHttpClient.js +14 -12
  27. package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -0
  28. package/dist/cjs/src/transaction/http/index.js +10 -0
  29. package/dist/cjs/src/transaction/http/index.js.map +1 -0
  30. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  31. package/dist/esm/mod.js +1 -0
  32. package/dist/esm/mod.js.map +1 -1
  33. package/dist/esm/src/transaction/Transaction.js +5 -3
  34. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  35. package/dist/esm/src/transaction/broadcasters/ARC.js +37 -25
  36. package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
  37. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js +5 -0
  38. package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
  39. package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +65 -0
  40. package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
  41. package/dist/esm/src/transaction/broadcasters/index.js +2 -2
  42. package/dist/esm/src/transaction/broadcasters/index.js.map +1 -1
  43. package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js +5 -0
  44. package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
  45. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js +50 -0
  46. package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
  47. package/dist/esm/src/transaction/chaintrackers/index.js +3 -0
  48. package/dist/esm/src/transaction/chaintrackers/index.js.map +1 -0
  49. package/dist/esm/src/transaction/{broadcasters → http}/DefaultHttpClient.js +8 -5
  50. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -0
  51. package/dist/esm/src/transaction/http/FetchHttpClient.js +26 -0
  52. package/dist/esm/src/transaction/http/FetchHttpClient.js.map +1 -0
  53. package/dist/esm/src/transaction/http/HttpClient.js.map +1 -0
  54. package/dist/esm/src/transaction/http/NodejsHttpClient.js +38 -0
  55. package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -0
  56. package/dist/esm/src/transaction/http/index.js +4 -0
  57. package/dist/esm/src/transaction/http/index.js.map +1 -0
  58. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  59. package/dist/types/mod.d.ts +1 -0
  60. package/dist/types/mod.d.ts.map +1 -1
  61. package/dist/types/src/transaction/Transaction.d.ts +3 -3
  62. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  63. package/dist/types/src/transaction/broadcasters/ARC.d.ts +23 -8
  64. package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
  65. package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts +3 -0
  66. package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts.map +1 -0
  67. package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts +26 -0
  68. package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts.map +1 -0
  69. package/dist/types/src/transaction/broadcasters/index.d.ts +3 -4
  70. package/dist/types/src/transaction/broadcasters/index.d.ts.map +1 -1
  71. package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts +3 -0
  72. package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts.map +1 -0
  73. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts +28 -0
  74. package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts.map +1 -0
  75. package/dist/types/src/transaction/chaintrackers/index.d.ts +4 -0
  76. package/dist/types/src/transaction/chaintrackers/index.d.ts.map +1 -0
  77. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +8 -0
  78. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -0
  79. package/dist/types/src/transaction/http/FetchHttpClient.d.ts +31 -0
  80. package/dist/types/src/transaction/http/FetchHttpClient.d.ts.map +1 -0
  81. package/dist/types/src/transaction/http/HttpClient.d.ts +43 -0
  82. package/dist/types/src/transaction/http/HttpClient.d.ts.map +1 -0
  83. package/dist/types/src/transaction/{broadcasters → http}/NodejsHttpClient.d.ts +2 -2
  84. package/dist/types/src/transaction/http/NodejsHttpClient.d.ts.map +1 -0
  85. package/dist/types/src/transaction/http/index.d.ts +7 -0
  86. package/dist/types/src/transaction/http/index.d.ts.map +1 -0
  87. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  88. package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +2 -0
  89. package/docs/examples/EXAMPLE_COMPLEX_TX.md +2 -4
  90. package/docs/examples/EXAMPLE_SIMPLE_TX.md +44 -5
  91. package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +29 -22
  92. package/docs/examples/EXAMPLE_VERIFYING_ROOTS.md +18 -63
  93. package/docs/examples/GETTING_STARTED_NODE_CJS.md +2 -4
  94. package/docs/examples/GETTING_STARTED_REACT.md +2 -4
  95. package/mod.ts +2 -1
  96. package/package.json +21 -1
  97. package/src/transaction/Transaction.ts +5 -3
  98. package/src/transaction/__tests/Transaction.test.ts +62 -0
  99. package/src/transaction/broadcasters/ARC.ts +75 -27
  100. package/src/transaction/broadcasters/DefaultBroadcaster.ts +6 -0
  101. package/src/transaction/broadcasters/WhatsOnChainBroadcaster.ts +70 -0
  102. package/src/transaction/broadcasters/__tests/ARC.test.ts +92 -19
  103. package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +165 -0
  104. package/src/transaction/broadcasters/index.ts +3 -4
  105. package/src/transaction/chaintrackers/DefaultChainTracker.ts +6 -0
  106. package/src/transaction/chaintrackers/WhatsOnChain.ts +70 -0
  107. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +135 -0
  108. package/src/transaction/chaintrackers/index.ts +3 -0
  109. package/src/transaction/http/DefaultHttpClient.ts +32 -0
  110. package/src/transaction/http/FetchHttpClient.ts +50 -0
  111. package/src/transaction/http/HttpClient.ts +45 -0
  112. package/src/transaction/http/NodejsHttpClient.ts +55 -0
  113. package/src/transaction/http/index.ts +6 -0
  114. package/dist/cjs/src/transaction/broadcasters/DefaultHttpClient.js.map +0 -1
  115. package/dist/cjs/src/transaction/broadcasters/HttpClient.js.map +0 -1
  116. package/dist/cjs/src/transaction/broadcasters/NodejsHttpClient.js.map +0 -1
  117. package/dist/esm/src/transaction/broadcasters/DefaultHttpClient.js.map +0 -1
  118. package/dist/esm/src/transaction/broadcasters/HttpClient.js.map +0 -1
  119. package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js +0 -36
  120. package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js.map +0 -1
  121. package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts +0 -6
  122. package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts.map +0 -1
  123. package/dist/types/src/transaction/broadcasters/HttpClient.d.ts +0 -33
  124. package/dist/types/src/transaction/broadcasters/HttpClient.d.ts.map +0 -1
  125. package/dist/types/src/transaction/broadcasters/NodejsHttpClient.d.ts.map +0 -1
  126. package/src/transaction/broadcasters/DefaultHttpClient.ts +0 -29
  127. package/src/transaction/broadcasters/HttpClient.ts +0 -34
  128. package/src/transaction/broadcasters/NodejsHttpClient.ts +0 -55
  129. /package/dist/cjs/src/transaction/{broadcasters → http}/HttpClient.js +0 -0
  130. /package/dist/esm/src/transaction/{broadcasters → http}/HttpClient.js +0 -0
@@ -45,7 +45,7 @@ await tx.sign()
45
45
  // Finally, we broadcast it with ARC.
46
46
  // get your api key from https://console.taal.com
47
47
  const apiKey = 'mainnet_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // replace
48
- await tx.broadcast(new ARC('https://api.taal.com/arc', apiKey))
48
+ await tx.broadcast()
49
49
  ```
50
50
 
51
51
  This code snippet demonstrates creating a transaction, adding an input and an output, setting a change script, configuring the fee, signing the transaction, and broadcasting with the ARC broadcaster. It uses the P2PKH Template, which is a specific type of Bitcoin locking program. To learn more about templates, check out this example (link to be provided once cmpplete).
@@ -63,17 +63,36 @@ transaction.addOutput({
63
63
 
64
64
  The `Transaction` class abstracts the complexity of Bitcoin's transaction structure. It handles inputs, outputs, scripts, and serialization, offering methods to easily modify and interrogate the transaction. Check out the full code-level documentation, refer to other examples, or reach out to the community to learn more.
65
65
 
66
- ## Configuring the ARC with http client
66
+ ## Choosing broadcasting target (ARC or WhatsOnChain)
67
+
68
+ ### ARC
69
+
70
+ You can broadcast via selected ARC by providing ARC instance to the `broadcast` method.
71
+
72
+ ```typescript
73
+ await tx.broadcast(new ARC('https://api.taal.com/arc', apiKey))
74
+ ```
75
+
76
+ ### WhatsOnChain
77
+
78
+ You can broadcast via What's On Chain by providing WhatsOnChainBroadcaster instance to the `broadcast` method.
79
+
80
+ ```typescript
81
+ const network = 'main' // or 'test'
82
+ await tx.broadcast(new WhatsOnChainBroadcaster(network))
83
+ ```
84
+
85
+ ## Use custom http client for broadcasting
67
86
 
68
87
  The ARC broadcaster requires an HTTP client to broadcast transactions. By default, the SDK will try to search for `window.fetch` in browser or `https` module on Node.js.
69
- If you want to use a custom (or preconfigured) HTTP client, you can pass it as an argument to the ARC constructor:
88
+ If you want to use a custom (or preconfigured) HTTP client, you can pass it with use of the adapter the ARC constructor:
70
89
 
71
90
  ### fetch
72
91
 
73
92
  ```typescript
74
93
  // In this example we're assuming you have variable fetch holding the fetch function`
75
94
 
76
- const arc = new ARC('https://api.taal.com/arc', apiKey, {fetch})
95
+ const arc = new ARC('https://api.taal.com/arc', apiKey, new FetchHttpClient(mockFetch))
77
96
  ```
78
97
 
79
98
  ### https
@@ -94,7 +113,27 @@ Although the SDK is not providing adapters for axios, it can be easily used with
94
113
  You can make your own "adapter" for axios as follows:
95
114
 
96
115
  ```typescript
97
- const axiosHttpClient = { fetch: (url, options) => axios(url, {...options, data: options.body})}
116
+ const axiosHttpClient = const httpClient: HttpClient = {
117
+ request: async (...args) => {
118
+ let res;
119
+ try {
120
+ res = await axios(...args);
121
+ } catch (e: unknown) {
122
+ if (axios.isAxiosError(e)) {
123
+ res = e.response;
124
+ } else {
125
+ throw e;
126
+ }
127
+ }
128
+ if (!res) {
129
+ throw new Error('No response');
130
+ }
131
+ return {
132
+ ok: res.status >= 200 && res.status <= 299,
133
+ ...res
134
+ };
135
+ },
136
+ };
98
137
 
99
138
  new ARC('https://api.taal.com/arc', apiKey, axiosHttpClient)
100
139
  ```
@@ -12,26 +12,6 @@ Merkle proofs are simply a way for someone to prove the existence of a given tra
12
12
 
13
13
  The process for SPV is detailed in [BRC-67](https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0067.md), but the main idea is that when a sender sends a transaction, they include merkle proofs on all of the input transactions. This allows anyone with a copy of the Bitcoin block headers to check that the input transactions are included in the blockchain. Verifiers then check that all the input and output scripts correctly transfer value from one party to the next, ensuring an unbroken chain of spends. The [BEEF data structure](https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md) provides a compact and efficient way for people to represent the data required to perform SPV.
14
14
 
15
- ## Block Headers Client
16
-
17
- To verify BEEF structures with the BSV SDK, you'll need to provide a block headers client that, given a merkle root, will indicate to the library whether the merkle root is correct for the block that's in the active chain at the given block height.
18
-
19
- For simplicity in this example, we are going to use a mock headers client that always indicates every merkle root as valid no matter what. However, in any real project, **you MUST always use an actual block headers client or attackers will be able to easily fool you with fraudulent transactions!**
20
-
21
- The TypeScript BSV SDK does not ship with a block headers client, but check out this example (link to be provided once complete) for setting up Pulse.
22
-
23
- Here is the gullible block headers client we will be using:
24
-
25
- ```typescript
26
- const gullibleHeadersClient = {
27
- // DO NOT USE IN A REAL PROJECT due to security risks of accepting any merkle root as valid without verification
28
- isValidRootForHeight: async (merkleRoot, height) => {
29
- console.log({ merkleRoot, height })
30
- return true
31
- }
32
- }
33
- ```
34
-
35
15
  ## Verifying a BEEF Structure
36
16
 
37
17
  Now that you have access to a block headers client (either Pulse on a real project or the above code for a toy example), we can proceed to verifying the BEEF structure with the following code:
@@ -46,10 +26,37 @@ const BEEFHex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f7
46
26
  const tx = Transaction.fromHexBEEF(BEEFHex)
47
27
 
48
28
  // This ensures the BEEF structure is legitimate
49
- const verified = await tx.verify(gullibleHeadersClient)
29
+ const verified = await tx.verify()
50
30
 
51
31
  // Print the results
52
32
  console.log(verified)
53
33
  ```
54
34
 
55
- The above code allows you to ensure that a given BEEF structure is valid according to the rules of SPV.
35
+ The above code allows you to ensure that a given BEEF structure is valid according to the rules of SPV.
36
+
37
+ ## Chain tracker
38
+
39
+ To verify BEEF structures with the BSV SDK, you'll need to provide a block headers client that, given a merkle root, will indicate to the library whether the merkle root is correct for the block that's in the active chain at the given block height.
40
+
41
+ The TypeScript BSV SDK does provides default implementation of the chain tracker that use What's On Chain API.
42
+
43
+ ### What's On Chain configuration
44
+
45
+ #### BSV network
46
+
47
+ The default network for the chain tracker is `main`. You can change it to other network by providing the instance of WhatsOnChain ChainTracker configured for other network to `.verify()` method.
48
+
49
+ ```typescript
50
+ tx.verify(new WhatsOnChain())
51
+ ```
52
+
53
+ #### Api Key
54
+
55
+ It is possible to use WhatsOnChain ChainTracker with obtained API KEY of [WhatsOnChain](https://docs.taal.com/core-products/whatsonchain).
56
+ To do so, you need to provide to `.verify()` method the custom instance of WhatsOnChain ChainTracker with the API KEY.
57
+
58
+ ```typescript
59
+ import { Transaction, WhatsOnChain } from '@bsv/sdk'
60
+
61
+ tx.verify(new WhatsOnChain('main', {apiKey: 'YOUR_API_KEY'}))
62
+ ```
@@ -1,6 +1,6 @@
1
1
  # Example: Building a Pulse Block Headers Client
2
2
 
3
- When [verifying BEEF structures](EXAMPLE_VERIFYING_BEEF.md), it's necessary to ensure that all transactions are well-anchored: this is to say, that they come from inputs in the honest chain. The SDK doesn't ship with a headers client, but this guide shows an example of how to use it with [Pulse](https://github.com/bitcoin-sv/block-headers-service): a popular client suitable for a wide range of use-cases.
3
+ When [verifying BEEF structures](EXAMPLE_VERIFYING_BEEF.md), it's necessary to ensure that all transactions are well-anchored: this is to say, that they come from inputs in the honest chain. The SDK doesn't ship with a headers client, but this guide shows an example of how to use it with [block-headers-service](https://github.com/bitcoin-sv/block-headers-service): a popular client suitable for a wide range of use-cases.
4
4
 
5
5
  ## Pre-requisites
6
6
 
@@ -41,59 +41,13 @@ export default interface ChainTracker {
41
41
 
42
42
  ```
43
43
 
44
- We will hide the complexities of making an https request from browsers/node.js in an separate function, which you could then import into the files you need otherwise.
45
-
46
- ```typescript
47
- // httpsClient.ts
48
- async function nodeFetch (https, url, requestOptions): Promise<any> {
49
- return await new Promise((resolve, reject) => {
50
- const req = https.request(url, requestOptions, res => {
51
- let data = ''
52
- res.on('data', (chunk: string) => {
53
- data += chunk
54
- })
55
- res.on('end', () => {
56
- resolve(data)
57
- })
58
- })
59
-
60
- req.on('error', error => {
61
- reject(error)
62
- })
63
-
64
- if (requestOptions.body as boolean) {
65
- req.write(requestOptions.body)
66
- }
67
- req.end()
68
- })
69
- }
70
- }
71
-
72
- export async function httpsClient (url: string, options: any) : Promise<any> {
73
- let response
74
- let data: any = {}
75
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
76
- // Use fetch in a browser environment
77
- response = await window.fetch(url, options)
78
- data = await response.json()
79
- return data
80
- }
81
- if (typeof require === 'undefined') throw new Error('No method available to perform HTTP request')
82
- // Use Node.js https module
83
- // eslint-disable-next-line
84
- const https = require('https')
85
- response = await nodeFetch(https, url, requestOptions)
86
- data = JSON.parse(response)
87
- return data
88
- }
89
- ```
90
-
91
-
92
44
  Given an array of merkle roots and corresponding block heights, we return a boolean indicating whether they're all valid.
93
45
 
94
46
  We can plug in the Block Header Service API with appropriate HTTP handling logic as follows:
95
47
 
96
48
  ```typescript
49
+ import {defaultHttpClient} from "@bsv/sdk";
50
+
97
51
  /**
98
52
  * Represents a Block Headers Client.
99
53
  */
@@ -107,9 +61,10 @@ export default class BlockHeadersClient implements ChainTracker {
107
61
  * @param {string} URL - The URL endpoint for the Pulse API.
108
62
  * @param {string} apiKey - The API key used for authorization with the Pulse API.
109
63
  */
110
- constructor (URL: string, apiKey: string) {
64
+ constructor(URL: string, apiKey: string) {
111
65
  this.URL = URL
112
66
  this.apiKey = apiKey
67
+ this.httpClient = defaultHttpClient()
113
68
  }
114
69
 
115
70
  /**
@@ -119,19 +74,19 @@ export default class BlockHeadersClient implements ChainTracker {
119
74
  * @param height: number - The corresponding height
120
75
  * @returns {Promise<boolean>} A promise that resolves to either a success or failure response (true or false).
121
76
  */
122
- async isValidRootForHeight (root: string, height: number): Promise<boolean> {
123
- try {
124
- const data = await httpsClient(`${this.URL}/api/v1/chain/merkleroot/verify`, {
125
- method: 'POST',
126
- body: JSON.stringify([{ merkleRoot: root, blockHeight: height }]),
127
- headers: {
128
- 'Content-Type': 'application/json',
129
- Authorization: `Bearer ${this.apiKey}`
130
- }
131
- })
132
- return data?.confirmationState === 'CONFIRMED'
133
- } catch (error) {
134
- return false
77
+ async isValidRootForHeight(root: string, height: number): Promise<boolean> {
78
+ const response = await httpsClient(`${this.URL}/api/v1/chain/merkleroot/verify`, {
79
+ method: 'POST',
80
+ body: [{merkleRoot: root, blockHeight: height}],
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ Authorization: `Bearer ${this.apiKey}`
84
+ }
85
+ })
86
+ if (response.ok) {
87
+ return response.data?.confirmationState === 'CONFIRMED'
88
+ } else {
89
+ throw new Error(`Failed to verify root at height ${height} with response ${response.status}`)
135
90
  }
136
91
  }
137
92
  }
@@ -53,9 +53,7 @@ const tx = new Transaction(version, [input], [output])
53
53
  await tx.fee()
54
54
  await tx.sign()
55
55
 
56
- // get your api key from https://console.taal.com
57
- const apiKey = 'mainnet_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // replace
58
- await tx.broadcast(new ARC('https://api.taal.com/arc', apiKey))
56
+ await tx.broadcast()
59
57
  ```
60
58
 
61
59
  This script demonstrates the entire process of creating a transaction, from initializing keys to signing and broadcast. When you run this script using Node.js (replacing the source transaction, private key, and ARC credentials), the spend will be signed and broadcast to the BSV network.
@@ -70,4 +68,4 @@ node index.js
70
68
 
71
69
  ## Conclusion
72
70
 
73
- Congratulations! You've successfully installed the BSV SDK in your NodeJS project and created a signed transaction. This guide covered the basics to get you started, but the BSV SDK is capable of much more. Explore the SDK documentation for detailed information on all the features and functionalities available to build scalable applications with the BSV blockchain.
71
+ Congratulations! You've successfully installed the BSV SDK in your NodeJS project and created a signed transaction. This guide covered the basics to get you started, but the BSV SDK is capable of much more. Explore the SDK documentation for detailed information on all the features and functionalities available to build scalable applications with the BSV blockchain.
@@ -60,9 +60,7 @@ const BsvButton: React.FC = () => {
60
60
  await tx.fee()
61
61
  await tx.sign()
62
62
 
63
- // grab your api key from https://console.taal.com
64
- const apiKey = 'mainnet_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' // replace
65
- await tx.broadcast(new ARC('https://api.taal.com/arc', apiKey))
63
+ await tx.broadcast()
66
64
  }
67
65
 
68
66
  return (
@@ -118,4 +116,4 @@ Now when you click the button, a transaction will be created, signed, and broadc
118
116
  Conclusion
119
117
  ----------
120
118
 
121
- Congratulations! You've successfully integrated the BSV SDK into your TypeScript & React application and created a button which broadcasts a bitcoin transaction on click. This guide covered the basic steps needed to get you started, but the BSV SDK can do a lot more. Explore the SDK documentation to dive deep into all the features and functionalities available to build scalable applications on the BSV blockchain.
119
+ Congratulations! You've successfully integrated the BSV SDK into your TypeScript & React application and created a button which broadcasts a bitcoin transaction on click. This guide covered the basic steps needed to get you started, but the BSV SDK can do a lot more. Explore the SDK documentation to dive deep into all the features and functionalities available to build scalable applications on the BSV blockchain.
package/mod.ts CHANGED
@@ -4,5 +4,6 @@ export * from "./src/script/templates/index.js"
4
4
  export * from "./src/transaction/index.js"
5
5
  export * from "./src/transaction/fee-models/index.js"
6
6
  export * from "./src/transaction/broadcasters/index.js"
7
+ export * from "./src/transaction/http/index.js"
7
8
  export * from "./src/messages/index.js"
8
- export * from "./src/compat/index.js"
9
+ export * from "./src/compat/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -74,6 +74,26 @@
74
74
  "require": "./dist/cjs/src/transaction/broadcaster/*.js",
75
75
  "types": "./dist/types/src/transaction/broadcaster/*.d.ts"
76
76
  },
77
+ "./transaction/chaintrackers": {
78
+ "import": "./dist/esm/src/transaction/chaintrackers/index.js",
79
+ "require": "./dist/cjs/src/transaction/chaintrackers/index.js",
80
+ "types": "./dist/types/src/transaction/chaintrackers/index.d.ts"
81
+ },
82
+ "./transaction/chaintrackers/*": {
83
+ "import": "./dist/esm/src/transaction/chaintrackers/*.js",
84
+ "require": "./dist/cjs/src/transaction/chaintrackers/*.js",
85
+ "types": "./dist/types/src/transaction/chaintrackers/*.d.ts"
86
+ },
87
+ "./transaction/http": {
88
+ "import": "./dist/esm/src/transaction/http/index.js",
89
+ "require": "./dist/cjs/src/transaction/http/index.js",
90
+ "types": "./dist/types/src/transaction/http/index.d.ts"
91
+ },
92
+ "./transaction/http/*": {
93
+ "import": "./dist/esm/src/transaction/http/*.js",
94
+ "require": "./dist/cjs/src/transaction/http/*.js",
95
+ "types": "./dist/types/src/transaction/http/*.d.ts"
96
+ },
77
97
  "./transaction/fee-model": {
78
98
  "import": "./dist/esm/src/transaction/fee-model/index.js",
79
99
  "require": "./dist/cjs/src/transaction/fee-model/index.js",
@@ -10,6 +10,8 @@ import { Broadcaster, BroadcastResponse, BroadcastFailure } from './Broadcaster.
10
10
  import MerklePath from './MerklePath.js'
11
11
  import Spend from '../script/Spend.js'
12
12
  import ChainTracker from './ChainTracker.js'
13
+ import {defaultBroadcaster} from "./broadcasters/DefaultBroadcaster.js";
14
+ import {defaultChainTracker} from "./chaintrackers/DefaultChainTracker.js";
13
15
 
14
16
  /**
15
17
  * Represents a complete Bitcoin transaction. This class encapsulates all the details
@@ -382,7 +384,7 @@ export default class Transaction {
382
384
  * @param broadcaster The Broadcaster instance wwhere the transaction will be sent
383
385
  * @returns A BroadcastResponse or BroadcastFailure from the Broadcaster
384
386
  */
385
- async broadcast (broadcaster: Broadcaster): Promise<BroadcastResponse | BroadcastFailure> {
387
+ async broadcast (broadcaster: Broadcaster = defaultBroadcaster()): Promise<BroadcastResponse | BroadcastFailure> {
386
388
  return await broadcaster.broadcast(this)
387
389
  }
388
390
 
@@ -533,11 +535,11 @@ export default class Transaction {
533
535
  /**
534
536
  * Verifies the legitimacy of the Bitcoin transaction according to the rules of SPV by ensuring all the input transactions link back to valid block headers, the chain of spends for all inputs are valid, and the sum of inputs is not less than the sum of outputs.
535
537
  *
536
- * @param chainTracker - An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified.
538
+ * @param chainTracker - An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified. If not provided then the default chain tracker will be used.
537
539
  *
538
540
  * @returns Whether the transaction is valid according to the rules of SPV.
539
541
  */
540
- async verify (chainTracker: ChainTracker | 'scripts only'): Promise<boolean> {
542
+ async verify (chainTracker: ChainTracker | 'scripts only' = defaultChainTracker()): Promise<boolean> {
541
543
  // If the transaction has a valid merkle path, verification is complete.
542
544
  if (typeof this.merklePath === 'object' && chainTracker !== 'scripts only') {
543
545
  const proofValid = await this.merklePath.verify(
@@ -16,6 +16,7 @@ import validTransactions from './tx.valid.vectors'
16
16
  import bigTX from './bigtx.vectors'
17
17
 
18
18
  const BRC62Hex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000'
19
+ const MerkleRootFromBEEF = 'bb6f640cc4ee56bf38eb5a1969ac0c16caa2d3d202b22bf3735d10eec0ca6e00'
19
20
 
20
21
  describe('Transaction', () => {
21
22
  const txIn = {
@@ -354,6 +355,40 @@ describe('Transaction', () => {
354
355
  })
355
356
 
356
357
  describe('Broadcast', () => {
358
+ it('Broadcasts with the default Broadcaster instance', async () => {
359
+ const mockedFetch = jest.fn().mockResolvedValue({
360
+ ok: true,
361
+ status: 200,
362
+ statusText: 'OK',
363
+ headers: {
364
+ get(key: string) {
365
+ if (key === 'Content-Type') {
366
+ return 'application/json'
367
+ }
368
+ }
369
+ },
370
+ json: async () => ({
371
+ txid: 'mocked_txid',
372
+ txStatus: 'success',
373
+ extraInfo: 'received'
374
+ })
375
+ });
376
+
377
+ (global as any).window = {fetch: mockedFetch} as any
378
+
379
+ const tx = new Transaction()
380
+ const rv = await tx.broadcast()
381
+
382
+ expect(mockedFetch).toHaveBeenCalled()
383
+ const url = (mockedFetch as jest.Mock).mock.calls[0][0] as string
384
+ expect(url).toEqual('https://arc.taal.com/v1/tx')
385
+ expect(rv).toEqual({
386
+ status: 'success',
387
+ txid: 'mocked_txid',
388
+ message: 'success received'
389
+ })
390
+ })
391
+
357
392
  it('Broadcasts with the provided Broadcaster instance', async () => {
358
393
  const mockBroadcast = jest.fn(() => 'MOCK_RV')
359
394
  const tx = new Transaction()
@@ -391,6 +426,33 @@ describe('Transaction', () => {
391
426
  const verified = await tx.verify(alwaysYesChainTracker)
392
427
  expect(verified).toBe(true)
393
428
  })
429
+
430
+ it('Verifies the transaction from the BEEF spec with a default chain tracker', async () => {
431
+ const mockFetch = jest.fn().mockResolvedValue({
432
+ ok: true,
433
+ status: 200,
434
+ statusText: 'OK',
435
+ headers: {
436
+ get(key: string) {
437
+ if (key === 'Content-Type') {
438
+ return 'application/json'
439
+ }
440
+ }
441
+ },
442
+ json: async () => ({
443
+ merkleroot: MerkleRootFromBEEF,
444
+ })
445
+ });
446
+ (global as any).window = {fetch: mockFetch}
447
+
448
+
449
+ const tx = Transaction.fromHexBEEF(BRC62Hex)
450
+
451
+ const verified = await tx.verify()
452
+
453
+ expect(mockFetch).toHaveBeenCalled()
454
+ expect(verified).toBe(true)
455
+ })
394
456
  })
395
457
 
396
458
  describe('vectors: a 1mb transaction', () => {
@@ -1,38 +1,69 @@
1
- import { BroadcastResponse, BroadcastFailure, Broadcaster } from '../Broadcaster.js'
1
+ import {BroadcastResponse, BroadcastFailure, Broadcaster} from '../Broadcaster.js'
2
2
  import Transaction from '../Transaction.js'
3
- import {HttpClient} from "./HttpClient.js";
4
- import defaultHttpClient from "./DefaultHttpClient.js";
3
+ import {HttpClient, HttpClientRequestOptions} from "../http/HttpClient.js";
4
+ import {defaultHttpClient} from "../http/DefaultHttpClient.js";
5
+ import Random from "../../primitives/Random.js";
6
+ import {toHex} from "../../primitives/utils.js";
7
+
8
+ /** Configuration options for the ARC broadcaster. */
9
+ export interface ArcConfig {
10
+ /** Authentication token for the ARC API */
11
+ apiKey?: string
12
+ /** Deployment id used annotating api calls in XDeployment-ID header - this value will be randomly generated if not set */
13
+ deploymentId?: string
14
+ /** The HTTP client used to make requests to the ARC API. */
15
+ httpClient?: HttpClient
16
+ }
17
+
18
+
19
+ function defaultDeploymentId() {
20
+ return `ts-sdk-${toHex(Random(16))}`;
21
+ }
5
22
 
6
23
  /**
7
24
  * Represents an ARC transaction broadcaster.
8
25
  */
9
26
  export default class ARC implements Broadcaster {
10
- URL: string
11
- apiKey: string
12
- private httpClient: HttpClient;
27
+ readonly URL: string
28
+ readonly apiKey: string | undefined
29
+ readonly deploymentId: string
30
+ private readonly httpClient: HttpClient;
13
31
 
32
+ /**
33
+ * Constructs an instance of the ARC broadcaster.
34
+ *
35
+ * @param {string} URL - The URL endpoint for the ARC API.
36
+ * @param {ArcConfig} config - Configuration options for the ARC broadcaster.
37
+ */
38
+ constructor(URL: string, config?: ArcConfig)
14
39
  /**
15
40
  * Constructs an instance of the ARC broadcaster.
16
41
  *
17
42
  * @param {string} URL - The URL endpoint for the ARC API.
18
43
  * @param {string} apiKey - The API key used for authorization with the ARC API.
19
- * @param {HttpClient} httpClient - The HTTP client used to make requests to the ARC API.
20
44
  */
21
- constructor (URL: string, apiKey: string, httpClient: HttpClient = defaultHttpClient()) {
45
+ constructor(URL: string, apiKey?: string)
46
+
47
+ constructor(URL: string, config?: string | ArcConfig) {
22
48
  this.URL = URL
23
- this.apiKey = apiKey
24
- this.httpClient = httpClient
49
+ if (typeof config === 'string') {
50
+ this.apiKey = config
51
+ this.httpClient = defaultHttpClient()
52
+ } else {
53
+ const {apiKey, deploymentId, httpClient} = config ?? {} as ArcConfig
54
+ this.httpClient = httpClient ?? defaultHttpClient()
55
+ this.deploymentId = deploymentId ?? defaultDeploymentId()
56
+ this.apiKey = apiKey
57
+ }
25
58
  }
26
59
 
27
60
  /**
28
61
  * Broadcasts a transaction via ARC.
29
- * This method will attempt to use `window.fetch` if available (in browser environments).
30
- * If running in a Node.js environment, it falls back to using the Node.js `https` module.
31
62
  *
32
63
  * @param {Transaction} tx - The transaction to be broadcasted.
33
64
  * @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
34
65
  */
35
- async broadcast (tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
66
+ async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
36
67
  let rawTx
37
68
  try {
38
69
  rawTx = tx.toHexEF()
@@ -42,30 +73,28 @@ export default class ARC implements Broadcaster {
42
73
  } else {
43
74
  throw error
44
75
  }
45
- }
46
- const requestOptions = {
76
+ }
77
+
78
+ const requestOptions: HttpClientRequestOptions = {
47
79
  method: 'POST',
48
- headers: {
49
- 'Content-Type': 'application/json',
50
- Authorization: `Bearer ${this.apiKey}`
51
- },
52
- body: JSON.stringify({ rawTx })
80
+ headers: this.requestHeaders(),
81
+ data: {rawTx}
53
82
  }
54
83
 
55
84
  try {
56
- const response = await this.httpClient.fetch(`${this.URL}/v1/tx`, requestOptions)
57
- const data = await response.json()
58
- if (data.txid as boolean || response.ok as boolean || response.statusCode === 200) {
85
+ const response = await this.httpClient.request<ArcResponse>(`${this.URL}/v1/tx`, requestOptions)
86
+ if (response.ok) {
87
+ const {txid, extraInfo, txStatus} = response.data
59
88
  return {
60
89
  status: 'success',
61
- txid: data.txid,
62
- message: data?.txStatus + ' ' + data?.extraInfo
90
+ txid: txid,
91
+ message: `${txStatus} ${extraInfo}`
63
92
  }
64
93
  } else {
65
94
  return {
66
95
  status: 'error',
67
- code: data.status as boolean ? data.status : 'ERR_UNKNOWN',
68
- description: data.detail as boolean ? data.detail : 'Unknown error'
96
+ code: response.status.toString() ?? 'ERR_UNKNOWN',
97
+ description: response.data?.detail ?? 'Unknown error'
69
98
  }
70
99
  }
71
100
  } catch (error) {
@@ -78,4 +107,23 @@ export default class ARC implements Broadcaster {
78
107
  }
79
108
  }
80
109
  }
110
+
111
+ private requestHeaders() {
112
+ const headers: Record<string, string> = {
113
+ 'Content-Type': 'application/json',
114
+ 'XDeployment-ID': this.deploymentId,
115
+ }
116
+
117
+ if (this.apiKey) {
118
+ headers['Authorization'] = `Bearer ${this.apiKey}`
119
+ }
120
+
121
+ return headers
122
+ }
123
+ }
124
+
125
+ interface ArcResponse {
126
+ txid: string
127
+ extraInfo: string
128
+ txStatus: string
81
129
  }
@@ -0,0 +1,6 @@
1
+ import {Broadcaster} from "../Broadcaster.js";
2
+ import ARC from "./ARC.js";
3
+
4
+ export function defaultBroadcaster(): Broadcaster {
5
+ return new ARC('https://arc.taal.com')
6
+ }