|
| 1 | +# Plan: Rework bsv-attest to use wallet create_action (#390) |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +`bsv-attest` v0.1.0 was written before `bsv-wallet` existed. Its `publish` method calls `wallet.fund_and_sign(tx)` (doesn't exist) and requires a separate `broadcaster:`. The x402-rack companion gem demonstrates the correct pattern: delegate to `wallet.create_action`, letting the wallet handle funding, signing, broadcasting, and UTXO state. |
| 6 | + |
| 7 | +This is an integration rework — the core hash/verify logic is sound and unchanged. |
| 8 | + |
| 9 | +The gem's scope is deliberately narrow: OP_RETURN attestation with the simplicity that entails. Hash data, publish the hash, verify it by txid. No BEEF, no merkle proofs, no proof documents. A future archival service can layer proof-based verification on top using the wallet's BEEF output directly — that's not this gem's concern. |
| 10 | + |
| 11 | +## Tasks |
| 12 | + |
| 13 | +### Task 1: Update Configuration — remove `broadcaster:`, keep `wallet:` and `provider:` |
| 14 | + |
| 15 | +**File:** `gem/bsv-attest/lib/bsv/attest/configuration.rb` |
| 16 | + |
| 17 | +Remove `broadcaster` from `attr_accessor`. Configuration becomes: |
| 18 | +```ruby |
| 19 | +attr_accessor :wallet, :provider |
| 20 | +``` |
| 21 | + |
| 22 | +**File:** `gem/bsv-attest/spec/bsv/attest/configuration_spec.rb` |
| 23 | + |
| 24 | +Remove broadcaster-related specs, update the "supports setting all attributes" spec to cover only `wallet` and `provider`. |
| 25 | + |
| 26 | +### Task 2: Update Response — drop `transaction`, keep it simple |
| 27 | + |
| 28 | +**File:** `gem/bsv-attest/lib/bsv/attest/response.rb` |
| 29 | + |
| 30 | +Current: `attr_reader :hash, :transaction, :txid` |
| 31 | + |
| 32 | +New: |
| 33 | +```ruby |
| 34 | +attr_reader :hash, :txid |
| 35 | + |
| 36 | +def initialize(hash:, txid:) |
| 37 | + @hash = hash |
| 38 | + @txid = txid |
| 39 | +end |
| 40 | +``` |
| 41 | + |
| 42 | +- Drop `transaction` — we no longer build the Transaction object ourselves |
| 43 | +- No BEEF, no broadcast_status — the gem returns what the caller needs: the hash and where to find it |
| 44 | +- `hash_hex` stays the same |
| 45 | + |
| 46 | +**File:** `gem/bsv-attest/spec/bsv/attest/response_spec.rb` |
| 47 | + |
| 48 | +Update to test the new attributes (just `hash` and `txid`), remove `transaction` specs. |
| 49 | + |
| 50 | +### Task 3: Add BroadcastError class |
| 51 | + |
| 52 | +**File:** `gem/bsv-attest/lib/bsv/attest/broadcast_error.rb` (new) |
| 53 | + |
| 54 | +```ruby |
| 55 | +class BroadcastError < StandardError; end |
| 56 | +``` |
| 57 | + |
| 58 | +Raised when `create_action` returns a `broadcast_error` in its result. This gives callers a distinct exception type for broadcast failures vs argument errors. |
| 59 | + |
| 60 | +**File:** `gem/bsv-attest/lib/bsv/attest.rb` — add autoload entry. |
| 61 | + |
| 62 | +### Task 4: Rewrite `publish` to use `wallet.create_action` |
| 63 | + |
| 64 | +**File:** `gem/bsv-attest/lib/bsv/attest.rb` |
| 65 | + |
| 66 | +Current signature: `publish(data, wallet: nil, broadcaster: nil)` |
| 67 | +New signature: `publish(data, wallet: nil, description: nil)` |
| 68 | + |
| 69 | +Implementation: |
| 70 | +```ruby |
| 71 | +def publish(data, wallet: nil, description: nil) |
| 72 | + w = wallet || configuration.wallet |
| 73 | + raise ArgumentError, 'wallet is required' unless w |
| 74 | + |
| 75 | + digest = hash(data) |
| 76 | + script = BSV::Script::Script.op_return(digest) |
| 77 | + |
| 78 | + desc = description || 'Attest data hash to chain' |
| 79 | + |
| 80 | + result = w.create_action( |
| 81 | + description: desc, |
| 82 | + outputs: [{ |
| 83 | + locking_script: script.to_hex, |
| 84 | + satoshis: 0, |
| 85 | + output_description: 'Attestation hash' |
| 86 | + }], |
| 87 | + options: { randomize_outputs: false } |
| 88 | + ) |
| 89 | + |
| 90 | + if result[:broadcast_error] |
| 91 | + raise BroadcastError, result[:broadcast_error] |
| 92 | + end |
| 93 | + |
| 94 | + Response.new(hash: digest, txid: result[:txid]) |
| 95 | +end |
| 96 | +``` |
| 97 | + |
| 98 | +Key decisions: |
| 99 | +- **No `broadcaster:` parameter** — wallet owns broadcasting |
| 100 | +- **`description:` parameter** — optional override for the wallet action description (default: 'Attest data hash to chain', 26 chars, within 5-50 limit) |
| 101 | +- **`randomize_outputs: false`** — deterministic output ordering (OP_RETURN first, then change) |
| 102 | +- **BroadcastError on failure** — if the wallet returns broadcast_error, raise rather than return a partial response |
| 103 | +- **Response is just hash + txid** — no BEEF, no broadcast metadata; the gem returns what you need to verify later |
| 104 | +- **`verify` unchanged** — provider-based verification is independent of the publish rework |
| 105 | + |
| 106 | +### Task 5: Update gemspec and version |
| 107 | + |
| 108 | +**File:** `gem/bsv-attest/bsv-attest.gemspec` |
| 109 | + |
| 110 | +```ruby |
| 111 | +spec.add_dependency 'bsv-sdk', '>= 0.11.0', '< 1.0' |
| 112 | +spec.add_dependency 'bsv-wallet', '>= 0.7.0', '< 1.0' |
| 113 | +``` |
| 114 | + |
| 115 | +The wallet is a runtime dependency because `publish` calls `create_action` directly. Version floors match current releases. |
| 116 | + |
| 117 | +**File:** `gem/bsv-attest/lib/bsv/attest/version.rb` |
| 118 | + |
| 119 | +Bump to `0.2.0` — breaking change (dropped broadcaster, changed Response shape). |
| 120 | + |
| 121 | +### Task 6: Rewrite specs |
| 122 | + |
| 123 | +**File:** `gem/bsv-attest/spec/bsv/attest_spec.rb` |
| 124 | + |
| 125 | +**publish specs** — replace mock wallet/broadcaster with a mock that responds to `create_action`: |
| 126 | + |
| 127 | +```ruby |
| 128 | +let(:mock_wallet) do |
| 129 | + Class.new do |
| 130 | + def create_action(args) |
| 131 | + { txid: 'bb' * 32 } |
| 132 | + end |
| 133 | + end.new |
| 134 | +end |
| 135 | +``` |
| 136 | + |
| 137 | +Update specs: |
| 138 | +- "builds an OP_RETURN transaction" → "delegates to create_action and returns Response" |
| 139 | +- "includes the hash in an OP_RETURN output" → verify the create_action args include correct locking_script (spy mock) |
| 140 | +- Remove "raises ArgumentError without broadcaster" |
| 141 | +- Add "raises BroadcastError on broadcast failure" |
| 142 | +- Add "passes custom description to create_action" |
| 143 | +- Update per-call override spec (wallet only, no broadcaster) |
| 144 | +- Update fallback spec (wallet only) |
| 145 | +- Keep verify specs unchanged (they test provider, not wallet) |
| 146 | + |
| 147 | +To verify create_action receives the correct output spec, use a spy-style mock: |
| 148 | + |
| 149 | +```ruby |
| 150 | +let(:spy_wallet) do |
| 151 | + Class.new do |
| 152 | + attr_reader :last_args |
| 153 | + def create_action(args) |
| 154 | + @last_args = args |
| 155 | + { txid: 'bb' * 32 } |
| 156 | + end |
| 157 | + end.new |
| 158 | +end |
| 159 | +``` |
| 160 | + |
| 161 | +Then assert `spy_wallet.last_args[:outputs].first[:locking_script]` contains the expected OP_RETURN hex. |
| 162 | + |
| 163 | +## Files Modified |
| 164 | + |
| 165 | +| File | Action | |
| 166 | +|------|--------| |
| 167 | +| `gem/bsv-attest/lib/bsv/attest.rb` | Edit: rewrite publish, add autoloads | |
| 168 | +| `gem/bsv-attest/lib/bsv/attest/configuration.rb` | Edit: remove broadcaster | |
| 169 | +| `gem/bsv-attest/lib/bsv/attest/response.rb` | Edit: simplify to hash + txid only | |
| 170 | +| `gem/bsv-attest/lib/bsv/attest/broadcast_error.rb` | New: BroadcastError class | |
| 171 | +| `gem/bsv-attest/lib/bsv/attest/version.rb` | Edit: bump to 0.2.0 | |
| 172 | +| `gem/bsv-attest/bsv-attest.gemspec` | Edit: add bsv-wallet dep, pin bsv-sdk | |
| 173 | +| `gem/bsv-attest/spec/bsv/attest_spec.rb` | Edit: rewrite publish specs | |
| 174 | +| `gem/bsv-attest/spec/bsv/attest/configuration_spec.rb` | Edit: remove broadcaster specs | |
| 175 | +| `gem/bsv-attest/spec/bsv/attest/response_spec.rb` | Edit: test simplified attributes | |
| 176 | + |
| 177 | +## Verification |
| 178 | + |
| 179 | +```bash |
| 180 | +cd gem/bsv-attest && bundle exec rspec # all specs pass |
| 181 | +bundle exec rubocop gem/bsv-attest # no offences |
| 182 | +cd gem/bsv-attest && gem build bsv-attest.gemspec # gem builds |
| 183 | +``` |
0 commit comments