@@ -14,6 +14,7 @@ import (
1414 "go.uber.org/zap/zapcore"
1515
1616 "github.com/bsv-blockchain/arcade/config"
17+ "github.com/bsv-blockchain/arcade/events"
1718 "github.com/bsv-blockchain/arcade/kafka"
1819 "github.com/bsv-blockchain/arcade/merkleservice"
1920 "github.com/bsv-blockchain/arcade/services"
@@ -22,6 +23,7 @@ import (
2223 "github.com/bsv-blockchain/arcade/services/p2p_client"
2324 "github.com/bsv-blockchain/arcade/services/propagation"
2425 "github.com/bsv-blockchain/arcade/services/tx_validator"
26+ "github.com/bsv-blockchain/arcade/services/webhook"
2527 "github.com/bsv-blockchain/arcade/store"
2628 storefactory "github.com/bsv-blockchain/arcade/store/factory"
2729 "github.com/bsv-blockchain/arcade/teranode"
@@ -129,7 +131,16 @@ func run(cmd *cobra.Command, _ []string) error {
129131
130132 txVal := validator .NewValidator (nil , nil ) // Default policy, no chain tracker yet
131133
132- svcs := buildServices (cfg , logger , producer , st , leaser , txTracker , teranodeClient , merkleClient , txVal )
134+ // One process-wide events.Publisher routes status updates from every
135+ // service that mutates state (validator, propagation, bump-builder,
136+ // api-server) onto a shared Kafka topic. The api-server SSE handler and
137+ // the webhook delivery service consume from that topic, so the
138+ // transaction-status fan-out works whether the deployment is monolithic
139+ // (mode=all) or split across pods.
140+ publisher := events .NewKafkaPublisher (producer , logger )
141+ defer func () { _ = publisher .Close () }()
142+
143+ svcs := buildServices (cfg , logger , producer , publisher , st , leaser , txTracker , teranodeClient , merkleClient , txVal )
133144
134145 ctx , cancel := context .WithCancel (context .Background ())
135146 defer cancel ()
@@ -188,6 +199,7 @@ func buildServices(
188199 cfg * config.Config ,
189200 logger * zap.Logger ,
190201 producer * kafka.Producer ,
202+ publisher events.Publisher ,
191203 st store.Store ,
192204 leaser store.Leaser ,
193205 txTracker * store.TxTracker ,
@@ -202,16 +214,22 @@ func buildServices(
202214 }
203215
204216 if shouldRun ("api-server" ) {
205- svcs = append (svcs , api_server .New (cfg , logger , producer , st , txTracker , teranodeClient ))
217+ svcs = append (svcs , api_server .New (cfg , logger , producer , publisher , st , txTracker , teranodeClient ))
206218 }
207219 if shouldRun ("bump-builder" ) {
208- svcs = append (svcs , bump_builder .New (cfg , logger , producer , st , teranodeClient ))
220+ svcs = append (svcs , bump_builder .New (cfg , logger , producer , publisher , st , teranodeClient ))
209221 }
210222 if shouldRun ("tx-validator" ) {
211- svcs = append (svcs , tx_validator .New (cfg , logger , producer , st , txTracker , txVal ))
223+ svcs = append (svcs , tx_validator .New (cfg , logger , producer , publisher , st , txTracker , txVal ))
212224 }
213225 if shouldRun ("propagation" ) {
214- svcs = append (svcs , propagation .New (cfg , logger , producer , st , leaser , teranodeClient , merkleClient ))
226+ svcs = append (svcs , propagation .New (cfg , logger , producer , publisher , st , leaser , teranodeClient , merkleClient ))
227+ }
228+ // Webhook delivery runs alongside api-server by default (so a single-binary
229+ // deployment ships callbacks without extra config) but can be split into
230+ // its own pod by setting mode=webhook.
231+ if shouldRun ("api-server" ) || shouldRun ("webhook" ) {
232+ svcs = append (svcs , webhook .New (cfg .Webhook , logger , publisher , st ))
215233 }
216234 // p2p_client is its own service; it's needed both by mode=propagation
217235 // (where it feeds the local teranode.Client directly) and by mode=p2p-client
0 commit comments