Skip to content
Open
2 changes: 2 additions & 0 deletions cmd/bee/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
optionNamePaymentEarly = "payment-early-percent"
optionNameResolverEndpoints = "resolver-options"
optionNameBootnodeMode = "bootnode-mode"
optionNameBzzTokenAddress = "bzz-token-address"
optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
Expand Down Expand Up @@ -299,6 +300,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
cmd.Flags().Duration(optionNameBlockchainRpcIdleTimeout, 90*time.Second, "blockchain rpc idle connection timeout")
cmd.Flags().Duration(optionNameBlockchainRpcKeepalive, 30*time.Second, "blockchain rpc TCP keepalive interval")
cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory addresses")
cmd.Flags().String(optionNameBzzTokenAddress, "", "bzz token contract address")
cmd.Flags().String(optionNameSwapInitialDeposit, "0", "initial deposit if deploying a new chequebook")
cmd.Flags().Bool(optionNameSwapEnable, false, "enable swap")
cmd.Flags().Bool(optionNameChequebookEnable, true, "enable chequebook")
Expand Down
1 change: 1 addition & 0 deletions cmd/bee/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo
BlockchainRpcTLSTimeout: c.config.GetDuration(configKeyBlockchainRpcTLSTimeout),
BlockchainRpcIdleTimeout: c.config.GetDuration(configKeyBlockchainRpcIdleTimeout),
BlockchainRpcKeepalive: c.config.GetDuration(configKeyBlockchainRpcKeepalive),
BzzTokenAddress: c.config.GetString(optionNameBzzTokenAddress),
BlockProfile: c.config.GetBool(optionNamePProfBlock),
BlockTime: networkConfig.blockTime,
BlockSyncInterval: c.config.GetUint64(optionNameBlockSyncInterval),
Expand Down
2 changes: 2 additions & 0 deletions packaging/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# bootnode: ["/dnsaddr/mainnet.ethswarm.org"]
## cause the node to always accept incoming connections
# bootnode-mode: false
## bzz token contract address
# bzz-token-address: ""
## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes
# cache-capacity: "1000000"
## enable forwarded content caching
Expand Down
2 changes: 2 additions & 0 deletions packaging/homebrew-amd64/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# bootnode: ["/dnsaddr/mainnet.ethswarm.org"]
## cause the node to always accept incoming connections
# bootnode-mode: false
## bzz token contract address
# bzz-token-address: ""
## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes
# cache-capacity: "1000000"
## enable forwarded content caching
Expand Down
2 changes: 2 additions & 0 deletions packaging/homebrew-arm64/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# bootnode: ["/dnsaddr/mainnet.ethswarm.org"]
## cause the node to always accept incoming connections
# bootnode-mode: false
## bzz token contract address
# bzz-token-address: ""
## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes
# cache-capacity: "1000000"
## enable forwarded content caching
Expand Down
2 changes: 2 additions & 0 deletions packaging/scoop/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# bootnode: ["/dnsaddr/mainnet.ethswarm.org"]
## cause the node to always accept incoming connections
# bootnode-mode: false
## bzz token contract address
# bzz-token-address: ""
## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes
# cache-capacity: "1000000"
## enable forwarded content caching
Expand Down
5 changes: 3 additions & 2 deletions pkg/config/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (
"github.com/ethersphere/go-storage-incentives-abi/abi"
)

// TODO: consider adding BzzAddress (also as a cmd param) to the ChainConfig and remove the postagecontract.LookupERC20Address function.

type ChainConfig struct {
// General.
ChainID int64
Expand All @@ -27,6 +25,7 @@ type ChainConfig struct {
RedistributionAddress common.Address
SwapPriceOracleAddress common.Address // Swap swear and swindle (S3) Contracts
CurrentFactoryAddress common.Address
BzzAddress common.Address

// ABIs.
StakingABI string
Expand All @@ -47,6 +46,7 @@ var (
RedistributionAddress: common.HexToAddress(abi.TestnetRedistributionAddress),
SwapPriceOracleAddress: common.HexToAddress("0x1814e9b3951Df0CB8e12b2bB99c5594514588936"),
CurrentFactoryAddress: common.HexToAddress("0x0fF044F6bB4F684a5A149B46D7eC03ea659F98A1"),
BzzAddress: common.HexToAddress(abi.TestnetBzzTokenAddress),

StakingABI: abi.TestnetStakingABI,
PostageStampABI: abi.TestnetPostageStampABI,
Expand All @@ -65,6 +65,7 @@ var (
RedistributionAddress: common.HexToAddress(abi.MainnetRedistributionAddress),
SwapPriceOracleAddress: common.HexToAddress("0xA57A50a831B31c904A770edBCb706E03afCdbd94"),
CurrentFactoryAddress: common.HexToAddress("0xc2d5a532cf69aa9a1378737d8ccdef884b6e7420"),
BzzAddress: common.HexToAddress(abi.MainnetBzzTokenAddress),

StakingABI: abi.MainnetStakingABI,
PostageStampABI: abi.MainnetPostageStampABI,
Expand Down
76 changes: 76 additions & 0 deletions pkg/config/chain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2026 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package config_test

import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/go-storage-incentives-abi/abi"

"github.com/ethersphere/bee/v2/pkg/config"
)

func TestChainConfigBzzAddress(t *testing.T) {
t.Parallel()

tests := []struct {
name string
cfg config.ChainConfig
want common.Address
}{
{
name: "mainnet",
cfg: config.Mainnet,
want: common.HexToAddress(abi.MainnetBzzTokenAddress),
},
{
name: "testnet",
cfg: config.Testnet,
want: common.HexToAddress(abi.TestnetBzzTokenAddress),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

if (tc.want == common.Address{}) {
t.Fatal("expected a non-zero bzz token address")
}
if tc.cfg.BzzAddress != tc.want {
t.Fatalf("got bzz address %s, want %s", tc.cfg.BzzAddress, tc.want)
}
})
}
}

func TestGetByChainIDBzzAddress(t *testing.T) {
t.Parallel()

t.Run("known chain", func(t *testing.T) {
t.Parallel()

cfg, found := config.GetByChainID(config.Mainnet.ChainID)
if !found {
t.Fatal("expected mainnet to be a known chain")
}
if cfg.BzzAddress != common.HexToAddress(abi.MainnetBzzTokenAddress) {
t.Fatalf("got bzz address %s, want %s", cfg.BzzAddress, abi.MainnetBzzTokenAddress)
}
})

t.Run("unknown chain", func(t *testing.T) {
t.Parallel()

cfg, found := config.GetByChainID(-1)
if found {
t.Fatal("expected unknown chain to be reported as not found")
}
if (cfg.BzzAddress != common.Address{}) {
t.Fatalf("expected zero bzz address for unknown chain, got %s", cfg.BzzAddress)
}
})
}
86 changes: 40 additions & 46 deletions pkg/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type Options struct {
BlockchainRpcTLSTimeout time.Duration
BlockchainRpcIdleTimeout time.Duration
BlockchainRpcKeepalive time.Duration
BzzTokenAddress string
BlockProfile bool
BlockTime time.Duration
BlockSyncInterval uint64
Expand Down Expand Up @@ -539,56 +540,55 @@ func NewBee(
}
}

if chainEnabled {
chequebookFactory, ferr := InitChequebookFactory(logger, chainBackend, chainID, transactionService, o.SwapFactoryAddress)
if o.SwapEnable && ferr != nil {
return nil, fmt.Errorf("init chequebook factory: %w", ferr)
}
chainCfg, found := config.GetByChainID(chainID)
Comment thread
martinconic marked this conversation as resolved.
Outdated

if ferr == nil {
erc20Address, err := chequebookFactory.ERC20Address(ctx)
if err != nil {
if o.SwapEnable {
return nil, fmt.Errorf("factory fail: %w", err)
}
logger.Warning("unable to resolve ERC20 token address; BZZ balance will be unavailable via /wallet", "error", err)
} else {
erc20Service = erc20.New(transactionService, erc20Address)
}
} else {
logger.Warning("unable to init chequebook factory; BZZ balance will be unavailable via /wallet", "error", ferr)
bzzTokenAddress := chainCfg.BzzAddress
if o.BzzTokenAddress != "" {
Comment thread
martinconic marked this conversation as resolved.
Outdated
if !common.IsHexAddress(o.BzzTokenAddress) {
return nil, errors.New("malformed bzz token address")
}
bzzTokenAddress = common.HexToAddress(o.BzzTokenAddress)
} else if chainEnabled && bzzTokenAddress == (common.Address{}) {
Comment thread
martinconic marked this conversation as resolved.
Outdated
return nil, errors.New("no known bzz token address for this network; provide --bzz-token-address")
}

if o.SwapEnable {
if o.ChequebookEnable {
var err error
chequebookService, err = InitChequebookService(
ctx,
logger,
stateStore,
signer,
chainID,
chainBackend,
overlayEthAddress,
transactionService,
chequebookFactory,
o.SwapInitialDeposit,
erc20Service,
)
if err != nil {
return nil, fmt.Errorf("init chequebook service: %w", err)
}
}
if chainEnabled {
erc20Service = erc20.New(transactionService, bzzTokenAddress)
}
Comment on lines 556 to +561

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full bzz token address verification can be moved here, not to have it partially before calling constructor and inside of it. But o.BzzTokenAddress need to be changed to string type. And it can be done only if chainEnabled. No need to error out if disabled.

	if chainEnabled {
		if o.BzzTokenAddress != "" {
			if !common.IsHexAddress(o.BzzTokenAddress) {
				return nil, errors.New("malformed bzz token address")
			}
			bzzTokenAddress = common.HexToAddress(o.BzzTokenAddress)
		}
		if bzzTokenAddress == (common.Address{}) {
			return nil, errors.New("no known bzz token address for this network; provide --bzz-token-address")
		}
		erc20Service = erc20.New(transactionService, bzzTokenAddress)
	}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous review round Elad asked specifically for the opposite — sanitize the address in cmd and pass a typed common.Address to NewBee rather than a raw string (done in 599f33b). That aligns it with how the other contract-address flags are validated and keeps the constructor free of string parsing. I'd prefer no to flip it back unless we settle on one convention — @acud, any objection to consolidating validation in node.go as @gacevicljubisa suggests, or keep it cmd-side?

@gacevicljubisa gacevicljubisa Jun 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have missed that comment. Most of verification is done inside the constructor, and my idea was to have it in one place and all can be inside this one if statement when chain is enabled. In any case no need to return error if chain is disabled.


chequeStore, cashoutService = initChequeStoreCashout(
if o.SwapEnable {
chequebookFactory, err := InitChequebookFactory(logger, chainBackend, chainID, transactionService, o.SwapFactoryAddress)
if err != nil {
return nil, fmt.Errorf("init chequebook factory: %w", err)
}

if o.ChequebookEnable && chainEnabled {
Comment thread
martinconic marked this conversation as resolved.
Outdated
chequebookService, err = InitChequebookService(
ctx,
logger,
stateStore,
chainBackend,
chequebookFactory,
signer,
chainID,
chainBackend,
overlayEthAddress,
transactionService,
chequebookFactory,
o.SwapInitialDeposit,
erc20Service,
)
if err != nil {
return nil, fmt.Errorf("init chequebook service: %w", err)
}
}

chequeStore, cashoutService = initChequeStoreCashout(
stateStore,
chainBackend,
chequebookFactory,
chainID,
overlayEthAddress,
transactionService,
)
}

lightNodes := lightnode.NewContainer(swarmAddress)
Expand Down Expand Up @@ -706,7 +706,6 @@ func NewBee(
eventListener postage.Listener
)

chainCfg, found := config.GetByChainID(chainID)
postageStampContractAddress, postageSyncStart := chainCfg.PostageStampAddress, chainCfg.PostageStampStartBlock
if o.PostageContractAddress != "" {
if !common.IsHexAddress(o.PostageContractAddress) {
Expand All @@ -723,11 +722,6 @@ func NewBee(

postageStampContractABI := abiutil.MustParseABI(chainCfg.PostageStampABI)

bzzTokenAddress, err := postagecontract.LookupERC20Address(ctx, transactionService, postageStampContractAddress, postageStampContractABI, chainEnabled)
if err != nil {
return nil, fmt.Errorf("lookup erc20 postage address: %w", err)
}

// Compute gas limit for contract transactions: when TrxDebugMode is enabled,
// gas estimation is skipped and DefaultGasLimit is used for all contract calls.
var contractGasLimit uint64
Expand Down
26 changes: 0 additions & 26 deletions pkg/postage/postagecontract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,29 +552,3 @@ func (m *noOpPostageContract) MinimumValidityBlocks(context.Context) (uint64, er
func (m *noOpPostageContract) ExpireBatches(context.Context) error {
return ErrChainDisabled
}

func LookupERC20Address(ctx context.Context, transactionService transaction.Service, postageStampContractAddress common.Address, postageStampContractABI abi.ABI, chainEnabled bool) (common.Address, error) {
if !chainEnabled {
return common.Address{}, nil
}

callData, err := postageStampContractABI.Pack("bzzToken")
if err != nil {
return common.Address{}, err
}

request := &transaction.TxRequest{
To: &postageStampContractAddress,
Data: callData,
GasPrice: nil,
GasLimit: 0,
Value: big.NewInt(0),
}

data, err := transactionService.Call(ctx, request)
if err != nil {
return common.Address{}, err
}

return common.BytesToAddress(data), nil
}
27 changes: 0 additions & 27 deletions pkg/postage/postagecontract/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,30 +948,3 @@ func TestBatchExpirer(t *testing.T) {
}
})
}

func TestLookupERC20Address(t *testing.T) {
postageStampContractAddress := common.HexToAddress("ffff")
erc20Address := common.HexToAddress("ffff")

addr, err := postagecontract.LookupERC20Address(
context.Background(),
transactionMock.New(
transactionMock.WithCallFunc(func(ctx context.Context, request *transaction.TxRequest) (result []byte, err error) {
if *request.To != postageStampContractAddress {
return nil, fmt.Errorf("called wrong contract. wanted %v, got %v", postageStampContractAddress, request.To)
}
return common.BytesToHash(erc20Address.Bytes()).Bytes(), nil
}),
),
postageStampContractAddress,
postageStampContractABI,
true,
)
if err != nil {
t.Fatal(err)
}

if addr != postageStampContractAddress {
t.Fatalf("got wrong erc20 address. wanted %v, got %v", erc20Address, addr)
}
}
Loading