@bsv/sdk 1.0.33 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/cjs/mod.js +2 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/transaction/Transaction.js +5 -3
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/src/transaction/broadcasters/ARC.js +37 -25
- package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
- package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js +12 -0
- package/dist/cjs/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
- package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +66 -0
- package/dist/cjs/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
- package/dist/cjs/src/transaction/broadcasters/index.js +5 -5
- package/dist/cjs/src/transaction/broadcasters/index.js.map +1 -1
- package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js +12 -0
- package/dist/cjs/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
- package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js +49 -0
- package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
- package/dist/cjs/src/transaction/chaintrackers/index.js +11 -0
- package/dist/cjs/src/transaction/chaintrackers/index.js.map +1 -0
- package/dist/cjs/src/transaction/{broadcasters → http}/DefaultHttpClient.js +8 -4
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -0
- package/dist/cjs/src/transaction/http/FetchHttpClient.js +29 -0
- package/dist/cjs/src/transaction/http/FetchHttpClient.js.map +1 -0
- package/dist/cjs/src/transaction/http/HttpClient.js.map +1 -0
- package/dist/cjs/src/transaction/{broadcasters → http}/NodejsHttpClient.js +14 -12
- package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -0
- package/dist/cjs/src/transaction/http/index.js +10 -0
- package/dist/cjs/src/transaction/http/index.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +2 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +5 -3
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/src/transaction/broadcasters/ARC.js +37 -25
- package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
- package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js +5 -0
- package/dist/esm/src/transaction/broadcasters/DefaultBroadcaster.js.map +1 -0
- package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js +65 -0
- package/dist/esm/src/transaction/broadcasters/WhatsOnChainBroadcaster.js.map +1 -0
- package/dist/esm/src/transaction/broadcasters/index.js +2 -2
- package/dist/esm/src/transaction/broadcasters/index.js.map +1 -1
- package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js +5 -0
- package/dist/esm/src/transaction/chaintrackers/DefaultChainTracker.js.map +1 -0
- package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js +50 -0
- package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -0
- package/dist/esm/src/transaction/chaintrackers/index.js +3 -0
- package/dist/esm/src/transaction/chaintrackers/index.js.map +1 -0
- package/dist/esm/src/transaction/{broadcasters → http}/DefaultHttpClient.js +8 -5
- package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -0
- package/dist/esm/src/transaction/http/FetchHttpClient.js +26 -0
- package/dist/esm/src/transaction/http/FetchHttpClient.js.map +1 -0
- package/dist/esm/src/transaction/http/HttpClient.js.map +1 -0
- package/dist/esm/src/transaction/http/NodejsHttpClient.js +38 -0
- package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -0
- package/dist/esm/src/transaction/http/index.js +4 -0
- package/dist/esm/src/transaction/http/index.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +2 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +3 -3
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/broadcasters/ARC.d.ts +23 -8
- package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
- package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts +3 -0
- package/dist/types/src/transaction/broadcasters/DefaultBroadcaster.d.ts.map +1 -0
- package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts +26 -0
- package/dist/types/src/transaction/broadcasters/WhatsOnChainBroadcaster.d.ts.map +1 -0
- package/dist/types/src/transaction/broadcasters/index.d.ts +3 -4
- package/dist/types/src/transaction/broadcasters/index.d.ts.map +1 -1
- package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts +3 -0
- package/dist/types/src/transaction/chaintrackers/DefaultChainTracker.d.ts.map +1 -0
- package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts +28 -0
- package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts.map +1 -0
- package/dist/types/src/transaction/chaintrackers/index.d.ts +4 -0
- package/dist/types/src/transaction/chaintrackers/index.d.ts.map +1 -0
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +8 -0
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -0
- package/dist/types/src/transaction/http/FetchHttpClient.d.ts +31 -0
- package/dist/types/src/transaction/http/FetchHttpClient.d.ts.map +1 -0
- package/dist/types/src/transaction/http/HttpClient.d.ts +43 -0
- package/dist/types/src/transaction/http/HttpClient.d.ts.map +1 -0
- package/dist/types/src/transaction/{broadcasters → http}/NodejsHttpClient.d.ts +2 -2
- package/dist/types/src/transaction/http/NodejsHttpClient.d.ts.map +1 -0
- package/dist/types/src/transaction/http/index.d.ts +7 -0
- package/dist/types/src/transaction/http/index.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +2 -0
- package/docs/examples/EXAMPLE_COMPLEX_TX.md +2 -4
- package/docs/examples/EXAMPLE_SIMPLE_TX.md +44 -5
- package/docs/examples/EXAMPLE_VERIFYING_BEEF.md +29 -22
- package/docs/examples/EXAMPLE_VERIFYING_ROOTS.md +18 -63
- package/docs/examples/GETTING_STARTED_NODE_CJS.md +2 -4
- package/docs/examples/GETTING_STARTED_REACT.md +2 -4
- package/docs/primitives.md +61 -59
- package/docs/script.md +13 -11
- package/docs/transaction.md +489 -35
- package/mod.ts +3 -1
- package/package.json +21 -1
- package/src/transaction/Transaction.ts +5 -3
- package/src/transaction/__tests/Transaction.test.ts +62 -0
- package/src/transaction/broadcasters/ARC.ts +75 -27
- package/src/transaction/broadcasters/DefaultBroadcaster.ts +6 -0
- package/src/transaction/broadcasters/WhatsOnChainBroadcaster.ts +70 -0
- package/src/transaction/broadcasters/__tests/ARC.test.ts +92 -19
- package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +165 -0
- package/src/transaction/broadcasters/index.ts +3 -4
- package/src/transaction/chaintrackers/DefaultChainTracker.ts +6 -0
- package/src/transaction/chaintrackers/WhatsOnChain.ts +70 -0
- package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +135 -0
- package/src/transaction/chaintrackers/index.ts +3 -0
- package/src/transaction/http/DefaultHttpClient.ts +32 -0
- package/src/transaction/http/FetchHttpClient.ts +50 -0
- package/src/transaction/http/HttpClient.ts +45 -0
- package/src/transaction/http/NodejsHttpClient.ts +55 -0
- package/src/transaction/http/index.ts +6 -0
- package/dist/cjs/src/transaction/broadcasters/DefaultHttpClient.js.map +0 -1
- package/dist/cjs/src/transaction/broadcasters/HttpClient.js.map +0 -1
- package/dist/cjs/src/transaction/broadcasters/NodejsHttpClient.js.map +0 -1
- package/dist/esm/src/transaction/broadcasters/DefaultHttpClient.js.map +0 -1
- package/dist/esm/src/transaction/broadcasters/HttpClient.js.map +0 -1
- package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js +0 -36
- package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js.map +0 -1
- package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts +0 -6
- package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts.map +0 -1
- package/dist/types/src/transaction/broadcasters/HttpClient.d.ts +0 -33
- package/dist/types/src/transaction/broadcasters/HttpClient.d.ts.map +0 -1
- package/dist/types/src/transaction/broadcasters/NodejsHttpClient.d.ts.map +0 -1
- package/src/transaction/broadcasters/DefaultHttpClient.ts +0 -29
- package/src/transaction/broadcasters/HttpClient.ts +0 -34
- package/src/transaction/broadcasters/NodejsHttpClient.ts +0 -55
- /package/dist/cjs/src/transaction/{broadcasters → http}/HttpClient.js +0 -0
- /package/dist/esm/src/transaction/{broadcasters → http}/HttpClient.js +0 -0
|
@@ -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 {
|
|
1
|
+
import {BroadcastResponse, BroadcastFailure, Broadcaster} from '../Broadcaster.js'
|
|
2
2
|
import Transaction from '../Transaction.js'
|
|
3
|
-
import {HttpClient} from "
|
|
4
|
-
import defaultHttpClient from "
|
|
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
|
-
|
|
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
|
|
45
|
+
constructor(URL: string, apiKey?: string)
|
|
46
|
+
|
|
47
|
+
constructor(URL: string, config?: string | ArcConfig) {
|
|
22
48
|
this.URL = URL
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const requestOptions: HttpClientRequestOptions = {
|
|
47
79
|
method: 'POST',
|
|
48
|
-
headers:
|
|
49
|
-
|
|
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.
|
|
57
|
-
|
|
58
|
-
|
|
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:
|
|
62
|
-
message:
|
|
90
|
+
txid: txid,
|
|
91
|
+
message: `${txStatus} ${extraInfo}`
|
|
63
92
|
}
|
|
64
93
|
} else {
|
|
65
94
|
return {
|
|
66
95
|
status: 'error',
|
|
67
|
-
code:
|
|
68
|
-
description:
|
|
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,70 @@
|
|
|
1
|
+
import {BroadcastResponse, BroadcastFailure, Broadcaster} from '../Broadcaster.js'
|
|
2
|
+
import Transaction from '../Transaction.js'
|
|
3
|
+
import {HttpClient} from "../http/HttpClient.js";
|
|
4
|
+
import {defaultHttpClient} from "../http/DefaultHttpClient.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an WhatsOnChain transaction broadcaster.
|
|
8
|
+
*/
|
|
9
|
+
export default class WhatsOnChainBroadcaster implements Broadcaster {
|
|
10
|
+
readonly network: string
|
|
11
|
+
private readonly URL: string
|
|
12
|
+
private readonly httpClient: HttpClient;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Constructs an instance of the WhatsOnChain broadcaster.
|
|
16
|
+
*
|
|
17
|
+
* @param {'main' | 'test' | 'stn'} network - The BSV network to use when calling the WhatsOnChain API.
|
|
18
|
+
* @param {HttpClient} httpClient - The HTTP client used to make requests to the API.
|
|
19
|
+
*/
|
|
20
|
+
constructor(network: 'main' | 'test' | 'stn' = 'main', httpClient: HttpClient = defaultHttpClient()) {
|
|
21
|
+
this.network = network
|
|
22
|
+
this.URL = `https://api.whatsonchain.com/v1/bsv/${network}/tx/raw`
|
|
23
|
+
this.httpClient = httpClient
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Broadcasts a transaction via WhatsOnChain.
|
|
28
|
+
*
|
|
29
|
+
* @param {Transaction} tx - The transaction to be broadcasted.
|
|
30
|
+
* @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
|
|
31
|
+
*/
|
|
32
|
+
async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
|
|
33
|
+
let rawTx = tx.toHex()
|
|
34
|
+
|
|
35
|
+
const requestOptions = {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'Accept': 'text/plain'
|
|
40
|
+
},
|
|
41
|
+
data: {txhex: rawTx}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await this.httpClient.request<string>(this.URL, requestOptions)
|
|
46
|
+
if (response.ok) {
|
|
47
|
+
const txid = response.data
|
|
48
|
+
return {
|
|
49
|
+
status: 'success',
|
|
50
|
+
txid: txid,
|
|
51
|
+
message: 'broadcast successful'
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
return {
|
|
55
|
+
status: 'error',
|
|
56
|
+
code: response.status.toString() ?? 'ERR_UNKNOWN',
|
|
57
|
+
description: response.data ?? 'Unknown error'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
status: 'error',
|
|
63
|
+
code: '500',
|
|
64
|
+
description: typeof error.message === 'string'
|
|
65
|
+
? error.message
|
|
66
|
+
: 'Internal Server Error'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import ARC from '../../../../dist/cjs/src/transaction/broadcasters/ARC.js'
|
|
2
2
|
import Transaction from '../../../../dist/cjs/src/transaction/Transaction.js'
|
|
3
|
-
import {NodejsHttpClient} from "
|
|
3
|
+
import {NodejsHttpClient} from "../../../../dist/cjs/src/transaction/http/NodejsHttpClient.js";
|
|
4
|
+
import {FetchHttpClient} from "../../../../dist/cjs/src/transaction/http/FetchHttpClient.js";
|
|
5
|
+
import {HttpClientRequestOptions} from "../../http";
|
|
4
6
|
|
|
5
7
|
// Mock Transaction
|
|
6
8
|
jest.mock('../../Transaction', () => {
|
|
@@ -15,11 +17,13 @@ jest.mock('../../Transaction', () => {
|
|
|
15
17
|
|
|
16
18
|
describe('ARC Broadcaster', () => {
|
|
17
19
|
const URL = 'https://example.com'
|
|
18
|
-
const apiKey = 'test_api_key'
|
|
19
20
|
const successResponse = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
status: 200,
|
|
22
|
+
data: {
|
|
23
|
+
txid: 'mocked_txid',
|
|
24
|
+
txStatus: 'success',
|
|
25
|
+
extraInfo: 'received'
|
|
26
|
+
}
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
let transaction: Transaction
|
|
@@ -31,9 +35,9 @@ describe('ARC Broadcaster', () => {
|
|
|
31
35
|
it('should broadcast successfully using window.fetch', async () => {
|
|
32
36
|
// Mocking window.fetch
|
|
33
37
|
const mockFetch = mockedFetch(successResponse)
|
|
34
|
-
global.window = {
|
|
38
|
+
global.window = {fetch: mockFetch} as any
|
|
35
39
|
|
|
36
|
-
const broadcaster = new ARC(URL
|
|
40
|
+
const broadcaster = new ARC(URL)
|
|
37
41
|
const response = await broadcaster.broadcast(transaction)
|
|
38
42
|
|
|
39
43
|
expect(mockFetch).toHaveBeenCalled()
|
|
@@ -49,7 +53,7 @@ describe('ARC Broadcaster', () => {
|
|
|
49
53
|
mockedHttps(successResponse)
|
|
50
54
|
delete global.window
|
|
51
55
|
|
|
52
|
-
const broadcaster = new ARC(URL
|
|
56
|
+
const broadcaster = new ARC(URL)
|
|
53
57
|
const response = await broadcaster.broadcast(transaction)
|
|
54
58
|
|
|
55
59
|
expect(response).toEqual({
|
|
@@ -63,7 +67,7 @@ describe('ARC Broadcaster', () => {
|
|
|
63
67
|
|
|
64
68
|
const mockFetch = mockedFetch(successResponse)
|
|
65
69
|
|
|
66
|
-
const broadcaster = new ARC(URL,
|
|
70
|
+
const broadcaster = new ARC(URL, {httpClient: new FetchHttpClient(mockFetch)})
|
|
67
71
|
const response = await broadcaster.broadcast(transaction)
|
|
68
72
|
|
|
69
73
|
expect(mockFetch).toHaveBeenCalled()
|
|
@@ -77,7 +81,7 @@ describe('ARC Broadcaster', () => {
|
|
|
77
81
|
it('should broadcast successfully using provided https', async () => {
|
|
78
82
|
|
|
79
83
|
const mockHttps = mockedHttps(successResponse)
|
|
80
|
-
const broadcaster = new ARC(URL,
|
|
84
|
+
const broadcaster = new ARC(URL, {httpClient: new NodejsHttpClient(mockHttps)})
|
|
81
85
|
|
|
82
86
|
const response = await broadcaster.broadcast(transaction)
|
|
83
87
|
|
|
@@ -88,11 +92,66 @@ describe('ARC Broadcaster', () => {
|
|
|
88
92
|
})
|
|
89
93
|
})
|
|
90
94
|
|
|
95
|
+
it('should send default request headers when broadcasting', async () => {
|
|
96
|
+
const mockFetch = mockedFetch(successResponse)
|
|
97
|
+
|
|
98
|
+
const broadcaster = new ARC(URL, {httpClient: new FetchHttpClient(mockFetch)})
|
|
99
|
+
await broadcaster.broadcast(transaction)
|
|
100
|
+
|
|
101
|
+
const {headers} = (mockFetch as jest.Mock).mock.calls[0][1] as HttpClientRequestOptions
|
|
102
|
+
|
|
103
|
+
expect(headers['Content-Type']).toEqual('application/json')
|
|
104
|
+
expect(headers['XDeployment-ID']).toBeDefined()
|
|
105
|
+
expect(headers['XDeployment-ID']).toMatch(/ts-sdk-.*/)
|
|
106
|
+
expect(headers['Authorization']).toBeUndefined()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should send authorization header when api key is provided', async () => {
|
|
110
|
+
const mockFetch = mockedFetch(successResponse)
|
|
111
|
+
const apiKey = 'mainnet_1234567890'
|
|
112
|
+
|
|
113
|
+
const broadcaster = new ARC(URL, {apiKey, httpClient: new FetchHttpClient(mockFetch)})
|
|
114
|
+
await broadcaster.broadcast(transaction)
|
|
115
|
+
|
|
116
|
+
const {headers} = (mockFetch as jest.Mock).mock.calls[0][1] as HttpClientRequestOptions
|
|
117
|
+
|
|
118
|
+
expect(headers['XDeployment-ID']).toBeDefined()
|
|
119
|
+
expect(headers['XDeployment-ID']).toMatch(/ts-sdk-.*/)
|
|
120
|
+
expect(headers['Authorization']).toEqual(`Bearer ${apiKey}`)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('should handle api key as second argument', async () => {
|
|
124
|
+
const mockFetch = mockedFetch(successResponse)
|
|
125
|
+
global.window = {fetch: mockFetch} as any
|
|
126
|
+
|
|
127
|
+
const apiKey = 'mainnet_1234567890'
|
|
128
|
+
|
|
129
|
+
const broadcaster = new ARC(URL, apiKey)
|
|
130
|
+
await broadcaster.broadcast(transaction)
|
|
131
|
+
|
|
132
|
+
const {headers} = (mockFetch as jest.Mock).mock.calls[0][1] as HttpClientRequestOptions
|
|
133
|
+
|
|
134
|
+
expect(headers['Authorization']).toEqual(`Bearer ${apiKey}`)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
it('should send provided deployment id', async () => {
|
|
139
|
+
const mockFetch = mockedFetch(successResponse)
|
|
140
|
+
const deploymentId = 'custom_deployment_id'
|
|
141
|
+
|
|
142
|
+
const broadcaster = new ARC(URL, {deploymentId, httpClient: new FetchHttpClient(mockFetch)})
|
|
143
|
+
await broadcaster.broadcast(transaction)
|
|
144
|
+
|
|
145
|
+
const {headers} = (mockFetch as jest.Mock).mock.calls[0][1] as HttpClientRequestOptions
|
|
146
|
+
|
|
147
|
+
expect(headers['XDeployment-ID']).toEqual(deploymentId)
|
|
148
|
+
})
|
|
149
|
+
|
|
91
150
|
it('should handle network errors', async () => {
|
|
92
151
|
const mockFetch = jest.fn().mockRejectedValue(new Error('Network error'))
|
|
93
|
-
global.window = {
|
|
152
|
+
global.window = {fetch: mockFetch} as any
|
|
94
153
|
|
|
95
|
-
const broadcaster = new ARC(URL,
|
|
154
|
+
const broadcaster = new ARC(URL, {httpClient: new FetchHttpClient(mockFetch)})
|
|
96
155
|
const response = await broadcaster.broadcast(transaction)
|
|
97
156
|
|
|
98
157
|
expect(mockFetch).toHaveBeenCalled()
|
|
@@ -106,11 +165,12 @@ describe('ARC Broadcaster', () => {
|
|
|
106
165
|
it('should handle non-200 responses', async () => {
|
|
107
166
|
const mockFetch = mockedFetch({
|
|
108
167
|
status: '400',
|
|
109
|
-
|
|
168
|
+
data: {
|
|
169
|
+
detail: 'Bad request'
|
|
170
|
+
}
|
|
110
171
|
})
|
|
111
|
-
global.window = { fetch: mockFetch } as any
|
|
112
172
|
|
|
113
|
-
const broadcaster = new ARC(URL,
|
|
173
|
+
const broadcaster = new ARC(URL, {httpClient: new FetchHttpClient(mockFetch)})
|
|
114
174
|
const response = await broadcaster.broadcast(transaction)
|
|
115
175
|
|
|
116
176
|
expect(mockFetch).toHaveBeenCalled()
|
|
@@ -123,8 +183,17 @@ describe('ARC Broadcaster', () => {
|
|
|
123
183
|
|
|
124
184
|
function mockedFetch(response) {
|
|
125
185
|
return jest.fn().mockResolvedValue({
|
|
126
|
-
ok: response.status ===
|
|
127
|
-
|
|
186
|
+
ok: response.status === 200,
|
|
187
|
+
status: response.status,
|
|
188
|
+
statusText: response.status === 200 ? 'OK' : 'Bad request',
|
|
189
|
+
headers: {
|
|
190
|
+
get(key: string) {
|
|
191
|
+
if (key === 'Content-Type') {
|
|
192
|
+
return 'application/json'
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
json: async () => response.data
|
|
128
197
|
});
|
|
129
198
|
}
|
|
130
199
|
|
|
@@ -133,9 +202,13 @@ describe('ARC Broadcaster', () => {
|
|
|
133
202
|
request: (url, options, callback) => {
|
|
134
203
|
// eslint-disable-next-line
|
|
135
204
|
callback({
|
|
136
|
-
statusCode:
|
|
205
|
+
statusCode: response.status,
|
|
206
|
+
statusMessage: response.status == 200 ? 'OK' : 'Bad request',
|
|
207
|
+
headers: {
|
|
208
|
+
'content-type': 'application/json'
|
|
209
|
+
},
|
|
137
210
|
on: (event, handler) => {
|
|
138
|
-
if (event === 'data') handler(JSON.stringify(response))
|
|
211
|
+
if (event === 'data') handler(JSON.stringify(response.data))
|
|
139
212
|
if (event === 'end') handler()
|
|
140
213
|
}
|
|
141
214
|
})
|