bsv-sdk 0.1.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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +58 -0
  3. data/LICENCE +86 -0
  4. data/README.md +155 -0
  5. data/lib/bsv/attest/configuration.rb +9 -0
  6. data/lib/bsv/attest/response.rb +19 -0
  7. data/lib/bsv/attest/verification_error.rb +7 -0
  8. data/lib/bsv/attest/version.rb +7 -0
  9. data/lib/bsv/attest.rb +71 -0
  10. data/lib/bsv/network/arc.rb +113 -0
  11. data/lib/bsv/network/broadcast_error.rb +15 -0
  12. data/lib/bsv/network/broadcast_response.rb +29 -0
  13. data/lib/bsv/network/chain_provider_error.rb +14 -0
  14. data/lib/bsv/network/utxo.rb +28 -0
  15. data/lib/bsv/network/whats_on_chain.rb +82 -0
  16. data/lib/bsv/network.rb +12 -0
  17. data/lib/bsv/primitives/base58.rb +117 -0
  18. data/lib/bsv/primitives/bsm.rb +131 -0
  19. data/lib/bsv/primitives/curve.rb +115 -0
  20. data/lib/bsv/primitives/digest.rb +99 -0
  21. data/lib/bsv/primitives/ecdsa.rb +224 -0
  22. data/lib/bsv/primitives/ecies.rb +128 -0
  23. data/lib/bsv/primitives/extended_key.rb +315 -0
  24. data/lib/bsv/primitives/mnemonic/wordlist.rb +270 -0
  25. data/lib/bsv/primitives/mnemonic.rb +192 -0
  26. data/lib/bsv/primitives/private_key.rb +139 -0
  27. data/lib/bsv/primitives/public_key.rb +118 -0
  28. data/lib/bsv/primitives/schnorr.rb +108 -0
  29. data/lib/bsv/primitives/signature.rb +136 -0
  30. data/lib/bsv/primitives.rb +23 -0
  31. data/lib/bsv/script/builder.rb +73 -0
  32. data/lib/bsv/script/chunk.rb +77 -0
  33. data/lib/bsv/script/interpreter/error.rb +54 -0
  34. data/lib/bsv/script/interpreter/interpreter.rb +281 -0
  35. data/lib/bsv/script/interpreter/operations/arithmetic.rb +243 -0
  36. data/lib/bsv/script/interpreter/operations/bitwise.rb +68 -0
  37. data/lib/bsv/script/interpreter/operations/crypto.rb +209 -0
  38. data/lib/bsv/script/interpreter/operations/data_push.rb +34 -0
  39. data/lib/bsv/script/interpreter/operations/flow_control.rb +94 -0
  40. data/lib/bsv/script/interpreter/operations/splice.rb +89 -0
  41. data/lib/bsv/script/interpreter/operations/stack_ops.rb +112 -0
  42. data/lib/bsv/script/interpreter/script_number.rb +218 -0
  43. data/lib/bsv/script/interpreter/stack.rb +203 -0
  44. data/lib/bsv/script/opcodes.rb +165 -0
  45. data/lib/bsv/script/script.rb +424 -0
  46. data/lib/bsv/script.rb +20 -0
  47. data/lib/bsv/transaction/beef.rb +323 -0
  48. data/lib/bsv/transaction/merkle_path.rb +250 -0
  49. data/lib/bsv/transaction/p2pkh.rb +44 -0
  50. data/lib/bsv/transaction/sighash.rb +48 -0
  51. data/lib/bsv/transaction/transaction.rb +380 -0
  52. data/lib/bsv/transaction/transaction_input.rb +109 -0
  53. data/lib/bsv/transaction/transaction_output.rb +51 -0
  54. data/lib/bsv/transaction/unlocking_script_template.rb +36 -0
  55. data/lib/bsv/transaction/var_int.rb +50 -0
  56. data/lib/bsv/transaction.rb +21 -0
  57. data/lib/bsv/version.rb +5 -0
  58. data/lib/bsv/wallet/insufficient_funds_error.rb +15 -0
  59. data/lib/bsv/wallet/wallet.rb +119 -0
  60. data/lib/bsv/wallet.rb +8 -0
  61. data/lib/bsv-attest.rb +4 -0
  62. data/lib/bsv-sdk.rb +11 -0
  63. metadata +104 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ed81ad480647a52136c32f6bd786fd8a0a590aa6d2805fb02151b583c6b8664b
4
+ data.tar.gz: c6eba8e1020913d59958d68b028b66ef7035d494f3c5d34dbd98c401ab30ec35
5
+ SHA512:
6
+ metadata.gz: aa4673f314ac7dddcb94d04d8e9c6db75a2c635c298e46d9412ee65babb491fd514f1bc37e94996206a41ad7d35ff1c2f57554913201be67e532700793b187da
7
+ data.tar.gz: ca38730828db7c78299a8eee34a682c5781f8cdae44186d7762b12030158f0f696d78204819f832e8f2bb8f913298100f9c55fb7a6f1f89cce4453c759d5e2c1
data/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-02-14
9
+
10
+ Initial release of the BSV Ruby SDK.
11
+
12
+ ### Added
13
+
14
+ #### Primitives
15
+
16
+ - secp256k1 elliptic curve operations (point arithmetic, scalar multiplication)
17
+ - ECDSA signing and verification with RFC 6979 deterministic nonces
18
+ - Public and private key handling (WIF import/export, compressed/uncompressed formats)
19
+ - Base58Check encoding and decoding
20
+ - Hash functions: SHA-256, RIPEMD-160, Hash160 (SHA-256 + RIPEMD-160), SHA-512, HMAC
21
+ - BIP-32 hierarchical deterministic key derivation (extended keys, hardened/normal child paths)
22
+ - BIP-39 mnemonic phrase generation and seed derivation
23
+ - ECIES encryption and decryption (BIE1 format)
24
+ - Bitcoin Signed Message (BSM) signing and verification
25
+
26
+ #### Script
27
+
28
+ - Opcode constants (full set)
29
+ - Script chunk representation and parsing
30
+ - Script serialisation and deserialisation
31
+ - Script templates: P2PKH, P2PK, P2MS (multisig), OP_RETURN data
32
+ - Script type detection (including read-only recognition of P2SH and other legacy types)
33
+ - Script builder API for programmatic construction
34
+ - Script interpreter with stack operations, arithmetic, crypto, flow control, splice, and bitwise ops
35
+
36
+ #### Transaction
37
+
38
+ - Transaction construction and serialisation (raw format)
39
+ - BIP-143 sighash computation (all hash types with FORKID)
40
+ - Transaction signing with configurable sighash flags
41
+ - BEEF serialisation (BRC-95/BRC-96)
42
+ - Merkle path representation and verification
43
+ - Fee estimation
44
+ - Script verification during signing
45
+ - Unlocking script templates for common script types
46
+
47
+ #### Network
48
+
49
+ - ARC broadcaster for transaction submission
50
+ - WhatsOnChain chain data provider
51
+ - Basic wallet functionality
52
+
53
+ #### Testing
54
+
55
+ - Cross-SDK test vectors from Go, TypeScript, and Python reference implementations
56
+ - NIST and RFC hash function test vectors
57
+ - Bitcoin Core script interpreter test suite
58
+ - Protocol conformance specs for opcodes, sighash flags, and transaction templates
data/LICENCE ADDED
@@ -0,0 +1,86 @@
1
+ Open BSV License Version 5 – granted by BSV Association, Grafenauweg 6, 6300
2
+ Zug, Switzerland (CHE-427.008.338) ("Licensor"), to you as a user (henceforth
3
+ "You", "User" or "Licensee").
4
+
5
+ For the purposes of this license, the definitions below have the following
6
+ meanings:
7
+
8
+ "Bitcoin Protocol" means the protocol implementation, cryptographic rules,
9
+ network protocols, and consensus mechanisms in the Bitcoin White Paper as
10
+ described here https://protocol.bsvblockchain.org.
11
+
12
+ "Bitcoin White Paper" means the paper entitled 'Bitcoin: A Peer-to-Peer
13
+ Electronic Cash System' published by 'Satoshi Nakamoto' in October 2008.
14
+
15
+ "BSV Blockchains" means:
16
+ (a) the Bitcoin blockchain containing block height #556767 with the hash
17
+ "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and
18
+ that contains the longest honest persistent chain of blocks which has been
19
+ produced in a manner which is consistent with the rules set forth in the
20
+ Network Access Rules; and
21
+ (b) the test blockchains that contain the longest honest persistent chains of
22
+ blocks which has been produced in a manner which is consistent with the
23
+ rules set forth in the Network Access Rules.
24
+
25
+ "Network Access Rules" or "Rules" means the set of rules regulating the
26
+ relationship between BSV Association and the nodes on BSV based on the Bitcoin
27
+ Protocol rules and those set out in the Bitcoin White Paper, and available here
28
+ https://bsvblockchain.org/network-access-rules.
29
+
30
+ "Software" means the software the subject of this licence, including any/all
31
+ intellectual property rights therein and associated documentation files.
32
+
33
+ BSV Association grants permission, free of charge and on a non-exclusive and
34
+ revocable basis, to any person obtaining a copy of the Software to deal in the
35
+ Software without restriction, including without limitation the rights to use,
36
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
37
+ Software, and to permit persons to whom the Software is furnished to do so,
38
+ subject to and conditioned upon the following conditions:
39
+
40
+ 1 - The text "© BSV Association," and this license shall be included in all
41
+ copies or substantial portions of the Software.
42
+ 2 - The Software, and any software that is derived from the Software or parts
43
+ thereof, must only be used on the BSV Blockchains.
44
+
45
+ For the avoidance of doubt, this license is granted subject to and conditioned
46
+ upon your compliance with these terms only. In the event of non-compliance, the
47
+ license shall extinguish and you can be enjoined from violating BSV's
48
+ intellectual property rights (incl. damages and similar related claims).
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES REGARDING ENTITLEMENT,
52
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
53
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS THEREOF BE LIABLE FOR ANY CLAIM,
54
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
55
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
56
+ DEALINGS IN THE SOFTWARE.
57
+
58
+
59
+ Version 0.1.1 of the Bitcoin SV software, and prior versions of software upon
60
+ which it was based, were licensed under the MIT License, which is included below.
61
+
62
+ The MIT License (MIT)
63
+
64
+ Copyright (c) 2009-2010 Satoshi Nakamoto
65
+ Copyright (c) 2009-2015 Bitcoin Developers
66
+ Copyright (c) 2009-2017 The Bitcoin Core developers
67
+ Copyright (c) 2017 The Bitcoin ABC developers
68
+ Copyright (c) 2018 Bitcoin Association for BSV
69
+ Copyright (c) 2023 BSV Association
70
+
71
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
72
+ this software and associated documentation files (the "Software"), to deal in
73
+ the Software without restriction, including without limitation the rights to
74
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
75
+ the Software, and to permit persons to whom the Software is furnished to do so,
76
+ subject to the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be included in all
79
+ copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
83
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
84
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
85
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
86
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # BSV Ruby SDK
2
+
3
+ [![CI](https://github.com/sgbett/bsv-ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/sgbett/bsv-ruby-sdk/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/sgbett/bsv-ruby-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/sgbett/bsv-ruby-sdk)
5
+ [![Gem Version](https://img.shields.io/gem/v/bsv-sdk)](https://rubygems.org/gems/bsv-sdk)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-red)](https://rubygems.org/gems/bsv-sdk)
7
+
8
+ Welcome to the BSV Blockchain Libraries Project, the comprehensive Ruby SDK designed to provide an updated and unified layer for developing scalable applications on the BSV Blockchain. This SDK addresses the limitations of previous tools by offering a fresh, peer-to-peer approach, adhering to SPV, and ensuring privacy and scalability.
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Acknowledgements](#acknowledgements)
13
+ 2. [Objective](#objective)
14
+ 3. [Getting Started](#getting-started)
15
+ 4. [Features & Deliverables](#features--deliverables)
16
+ 5. [Documentation](#documentation)
17
+ 6. [Contribution Guidelines](#contribution-guidelines)
18
+ 7. [Support & Contacts](#support--contacts)
19
+ 8. [Licence](#licence)
20
+
21
+ ## Acknowledgements
22
+
23
+ This Ruby SDK is a port of the official BSV Blockchain SDKs, which serve as its reference implementations. Primitives, script handling, and transaction logic are directly translated from them, adapted for Ruby idioms and conventions.
24
+
25
+ The reference SDKs:
26
+
27
+ - [TypeScript SDK](https://github.com/bsv-blockchain/ts-sdk)
28
+ - [Go SDK](https://github.com/bsv-blockchain/go-sdk)
29
+ - [Python SDK](https://github.com/bsv-blockchain/py-sdk)
30
+
31
+ These are maintained under the BSV Blockchain organisation and backed by the Bitcoin Association. The debt to their contributors is substantial — their clear, robust code made this port both feasible and consistent.
32
+
33
+ ## Objective
34
+
35
+ The BSV Blockchain Libraries Project aims to structure and maintain a middleware layer of the BSV Blockchain technology stack. By facilitating the development and maintenance of core libraries, it serves as an essential toolkit for developers looking to build on the BSV Blockchain.
36
+
37
+ This Ruby SDK brings maximum compatibility with the official SDK family to the Ruby ecosystem. It was born from a practical need: building an attestation gem ([bsv-attest](https://rubygems.org/gems/bsv-attest)) required a complete, idiomatic Ruby implementation of BSV primitives, script handling, and transaction construction. Rather than wrapping FFI bindings or shelling out to other languages, the SDK implements everything in pure Ruby using only the standard library's OpenSSL bindings.
38
+
39
+ <!-- TODO: Update bsv-attest link once gem documentation is published (see #48) -->
40
+
41
+ ## Getting Started
42
+
43
+ ### Requirements
44
+
45
+ - Ruby >= 2.7
46
+ - No external dependencies beyond Ruby's standard library (`openssl`)
47
+
48
+ ### Installation
49
+
50
+ Add to your Gemfile:
51
+
52
+ ```ruby
53
+ gem 'bsv-sdk'
54
+ ```
55
+
56
+ Or install directly:
57
+
58
+ ```bash
59
+ gem install bsv-sdk
60
+ ```
61
+
62
+ ### Basic Usage
63
+
64
+ Create and sign a P2PKH transaction:
65
+
66
+ ```ruby
67
+ require 'bsv-sdk'
68
+
69
+ # Generate a new private key (or load from WIF)
70
+ priv_key = BSV::Primitives::PrivateKey.generate
71
+
72
+ # Derive the public key hash for locking scripts
73
+ pubkey_hash = priv_key.public_key.hash160
74
+ locking_script = BSV::Script::Script.p2pkh_lock(pubkey_hash)
75
+
76
+ # Create a transaction spending a UTXO
77
+ tx = BSV::Transaction::Transaction.new
78
+
79
+ # Add an input referencing a previous transaction output
80
+ input = BSV::Transaction::TransactionInput.new(
81
+ prev_tx_id: source_txid_bytes, # 32-byte binary txid of the UTXO
82
+ prev_tx_out_index: 0
83
+ )
84
+ input.source_satoshis = 100_000
85
+ input.source_locking_script = locking_script
86
+ tx.add_input(input)
87
+
88
+ # Add an output sending to the same address (for demonstration)
89
+ tx.add_output(BSV::Transaction::TransactionOutput.new(
90
+ satoshis: 90_000,
91
+ locking_script: locking_script
92
+ ))
93
+
94
+ # Sign the input using the P2PKH template
95
+ template = BSV::Transaction::P2PKH.new(priv_key)
96
+ tx.inputs[0].unlocking_script = template.sign(tx, 0)
97
+
98
+ # The signed transaction is ready to broadcast
99
+ puts tx.to_hex
100
+ ```
101
+
102
+ ## Features & Deliverables
103
+
104
+ - **Cryptographic Primitives** — ECDSA signing with RFC 6979 deterministic nonces, Schnorr signatures, ECIES encryption/decryption, Bitcoin Signed Messages. All built on Ruby's stdlib OpenSSL.
105
+ - **Key Management** — BIP-32 HD key derivation, BIP-39 mnemonic generation (12/24-word phrases), WIF import/export, Base58Check encoding/decoding.
106
+ - **Script Layer** — Complete opcode set, script parsing and serialisation, type detection and predicates (`p2pkh?`, `p2pk?`, `p2sh?`, `multisig?`, `op_return?`), data extraction (pubkey hashes, script hashes, addresses), and a fluent builder API.
107
+ - **Script Templates** — Ready-made locking and unlocking script generators for P2PKH, P2PK, P2MS (multisig), and OP_RETURN.
108
+ - **Transaction Construction** — Input/output building, BIP-143 sighash computation (all SIGHASH types with FORKID), P2PKH signing, fee estimation.
109
+ - **SPV Structures** — Merkle path construction and verification, BEEF (Background Evaluation Extended Format) serialisation and deserialisation.
110
+ - **Network Integration** — ARC broadcaster for transaction submission, WhatsOnChain chain provider for UTXO queries and fee rates.
111
+ - **Wallet** — Simple wallet that sources UTXOs, estimates fees, funds and signs transactions.
112
+
113
+ ## Documentation
114
+
115
+ Full documentation is available at **[sgbett.github.io/bsv-ruby-sdk](https://sgbett.github.io/bsv-ruby-sdk/)**.
116
+
117
+ **Guides:**
118
+
119
+ - [Getting Started](https://sgbett.github.io/bsv-ruby-sdk/guides/getting-started/) — installation, first transaction
120
+ - [Primitives](https://sgbett.github.io/bsv-ruby-sdk/guides/primitives/) — keys, signing, encryption, HD keys
121
+ - [Script](https://sgbett.github.io/bsv-ruby-sdk/guides/script/) — construction, templates, detection
122
+ - [Transaction](https://sgbett.github.io/bsv-ruby-sdk/guides/transaction/) — building, signing, BEEF
123
+
124
+ **Additional resources:**
125
+
126
+ - [API Reference](https://sgbett.github.io/bsv-ruby-sdk/reference/) — auto-generated from YARD annotations
127
+ - [spec/ directory](https://github.com/sgbett/bsv-ruby-sdk/tree/master/spec) — runnable usage examples
128
+ - [CHANGELOG](CHANGELOG.md) — release history
129
+
130
+ **Protocol reference:**
131
+
132
+ The [BSV Protocol Documentation](https://hub.bsvblockchain.org/bitcoin-protocol-documentation) on the BSV Hub is the canonical protocol reference — covering transaction format, script opcodes, sighash flags, BEEF/SPV structures, and BRC specifications. The project includes an [MCP](https://modelcontextprotocol.io/) configuration (`.mcp.json`) that connects [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to the hub's search endpoint, giving AI-assisted development sessions direct access to protocol specs during implementation.
133
+
134
+ ## Contribution Guidelines
135
+
136
+ Contributions are welcome — bug reports, feature requests, and pull requests.
137
+
138
+ 1. **Fork & Clone** — Fork this repository and clone it locally.
139
+ 2. **Set Up** — Run `bundle install` to install dependencies.
140
+ 3. **Branch** — Create a new branch for your changes.
141
+ 4. **Test** — Ensure all specs pass with `bundle exec rake` and lint passes with `bundle exec rubocop`.
142
+ 5. **Commit** — Follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages.
143
+ 6. **Pull Request** — Open a pull request against `master`.
144
+
145
+ ## Support & Contacts
146
+
147
+ Maintainer: Simon Bettison
148
+
149
+ For questions, bug reports, or feature requests, please [open an issue](https://github.com/sgbett/bsv-ruby-sdk/issues) on GitHub.
150
+
151
+ ## Licence
152
+
153
+ [Open BSV Licence Version 5](LICENCE)
154
+
155
+ Thank you for being a part of the BSV Blockchain Libraries Project. Let's build the future of BSV Blockchain together!
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Attest
5
+ class Configuration
6
+ attr_accessor :wallet, :broadcaster, :provider
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Attest
5
+ class Response
6
+ attr_reader :hash, :transaction, :txid
7
+
8
+ def initialize(hash:, transaction:, txid:)
9
+ @hash = hash
10
+ @transaction = transaction
11
+ @txid = txid
12
+ end
13
+
14
+ def hash_hex
15
+ @hash.unpack1('H*')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Attest
5
+ class VerificationError < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Attest
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
data/lib/bsv/attest.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Attest
5
+ autoload :Configuration, 'bsv/attest/configuration'
6
+ autoload :Response, 'bsv/attest/response'
7
+ autoload :VerificationError, 'bsv/attest/verification_error'
8
+ autoload :VERSION, 'bsv/attest/version'
9
+
10
+ class << self
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield(configuration)
17
+ end
18
+
19
+ def reset_configuration!
20
+ @configuration = Configuration.new
21
+ end
22
+
23
+ def hash(data)
24
+ BSV::Primitives::Digest.sha256(data)
25
+ end
26
+
27
+ def publish(data, wallet: nil, broadcaster: nil)
28
+ w = wallet || configuration.wallet
29
+ b = broadcaster || configuration.broadcaster
30
+ raise ArgumentError, 'wallet is required' unless w
31
+ raise ArgumentError, 'broadcaster is required' unless b
32
+
33
+ digest = hash(data)
34
+
35
+ script = BSV::Script::Script.op_return(digest)
36
+ output = BSV::Transaction::TransactionOutput.new(satoshis: 0, locking_script: script)
37
+
38
+ tx = BSV::Transaction::Transaction.new
39
+ tx.add_output(output)
40
+
41
+ w.fund_and_sign(tx)
42
+
43
+ broadcast_response = b.broadcast(tx)
44
+
45
+ Response.new(hash: digest, transaction: tx, txid: broadcast_response.txid)
46
+ end
47
+
48
+ def verify(data, txid, provider: nil)
49
+ p = provider || configuration.provider
50
+ raise ArgumentError, 'provider is required' unless p
51
+
52
+ digest = hash(data)
53
+
54
+ tx = p.fetch_transaction(txid)
55
+
56
+ tx.outputs.each do |output|
57
+ chunks = output.locking_script.chunks
58
+ next if chunks.length < 3
59
+ next unless chunks[0].opcode == BSV::Script::Opcodes::OP_FALSE
60
+ next unless chunks[1].opcode == BSV::Script::Opcodes::OP_RETURN
61
+
62
+ chunks[2..].each do |chunk|
63
+ return true if chunk.data == digest
64
+ end
65
+ end
66
+
67
+ raise VerificationError, 'hash not found in transaction outputs'
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module BSV
8
+ module Network
9
+ # ARC broadcaster for submitting transactions to the BSV network.
10
+ #
11
+ # Any object responding to #broadcast(tx) can serve as a broadcaster;
12
+ # this class implements that contract using the ARC API.
13
+ #
14
+ # The HTTP client is injectable for testability. It must respond to
15
+ # #request(uri, request) and return an object with #code and #body.
16
+ class ARC
17
+ REJECTED_STATUSES = %w[REJECTED DOUBLE_SPEND_ATTEMPTED].freeze
18
+
19
+ def initialize(url, api_key: nil, http_client: nil)
20
+ @url = url.chomp('/')
21
+ @api_key = api_key
22
+ @http_client = http_client
23
+ end
24
+
25
+ # Submit a transaction to ARC.
26
+ # Returns BroadcastResponse on success, raises BroadcastError on failure.
27
+ def broadcast(tx)
28
+ uri = URI("#{@url}/v1/tx")
29
+ request = Net::HTTP::Post.new(uri)
30
+ request['Content-Type'] = 'application/octet-stream'
31
+ apply_auth_header(request)
32
+ request.body = tx.to_binary
33
+
34
+ response = execute(uri, request)
35
+ handle_broadcast_response(response)
36
+ end
37
+
38
+ # Query the status of a previously submitted transaction.
39
+ # Returns BroadcastResponse on success, raises BroadcastError on failure.
40
+ def status(txid)
41
+ uri = URI("#{@url}/v1/tx/#{txid}")
42
+ request = Net::HTTP::Get.new(uri)
43
+ apply_auth_header(request)
44
+
45
+ response = execute(uri, request)
46
+ handle_broadcast_response(response)
47
+ end
48
+
49
+ private
50
+
51
+ def apply_auth_header(request)
52
+ request['Authorization'] = "Bearer #{@api_key}" if @api_key
53
+ end
54
+
55
+ def execute(uri, request)
56
+ if @http_client
57
+ @http_client.request(uri, request)
58
+ else
59
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
60
+ http.request(request)
61
+ end
62
+ end
63
+ end
64
+
65
+ def handle_broadcast_response(response)
66
+ body = parse_json(response.body)
67
+ code = response.code.to_i
68
+
69
+ unless (200..299).cover?(code)
70
+ raise BroadcastError.new(
71
+ body['detail'] || body['title'] || "HTTP #{code}",
72
+ status_code: code,
73
+ txid: body['txid']
74
+ )
75
+ end
76
+
77
+ tx_status = body['txStatus']
78
+ if rejected_status?(tx_status)
79
+ raise BroadcastError.new(
80
+ body['detail'] || body['title'] || tx_status,
81
+ status_code: code,
82
+ txid: body['txid']
83
+ )
84
+ end
85
+
86
+ build_response(body)
87
+ end
88
+
89
+ def rejected_status?(tx_status)
90
+ REJECTED_STATUSES.include?(tx_status)
91
+ end
92
+
93
+ def parse_json(raw)
94
+ JSON.parse(raw)
95
+ rescue JSON::ParserError
96
+ { 'detail' => raw }
97
+ end
98
+
99
+ def build_response(body)
100
+ BroadcastResponse.new(
101
+ txid: body['txid'],
102
+ tx_status: body['txStatus'],
103
+ message: body['title'],
104
+ extra_info: body['extraInfo'],
105
+ block_hash: body['blockHash'],
106
+ block_height: body['blockHeight'],
107
+ timestamp: body['timestamp'],
108
+ competing_txs: body['competingTxs']
109
+ )
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ class BroadcastError < StandardError
6
+ attr_reader :status_code, :txid
7
+
8
+ def initialize(message, status_code: nil, txid: nil)
9
+ @status_code = status_code
10
+ @txid = txid
11
+ super(message)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ class BroadcastResponse
6
+ attr_reader :txid, :tx_status, :message, :extra_info,
7
+ :block_hash, :block_height, :timestamp, :competing_txs
8
+
9
+ def initialize(attrs = {})
10
+ @txid = attrs[:txid]
11
+ @tx_status = attrs[:tx_status]
12
+ @message = attrs[:message]
13
+ @extra_info = attrs[:extra_info]
14
+ @block_hash = attrs[:block_hash]
15
+ @block_height = attrs[:block_height]
16
+ @timestamp = attrs[:timestamp]
17
+ @competing_txs = attrs[:competing_txs]
18
+ end
19
+
20
+ def success?
21
+ true
22
+ end
23
+
24
+ def mined?
25
+ tx_status == 'MINED'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ class ChainProviderError < StandardError
6
+ attr_reader :status_code
7
+
8
+ def initialize(message, status_code: nil)
9
+ @status_code = status_code
10
+ super(message)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Network
5
+ class UTXO
6
+ attr_reader :tx_hash, :tx_pos, :satoshis, :height
7
+
8
+ def initialize(tx_hash:, tx_pos:, satoshis:, height: nil)
9
+ @tx_hash = tx_hash
10
+ @tx_pos = tx_pos
11
+ @satoshis = satoshis
12
+ @height = height
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(self.class) &&
17
+ tx_hash == other.tx_hash &&
18
+ tx_pos == other.tx_pos
19
+ end
20
+
21
+ alias eql? ==
22
+
23
+ def hash
24
+ [tx_hash, tx_pos].hash
25
+ end
26
+ end
27
+ end
28
+ end