Skip to content

Commit f563d04

Browse files
authored
Merge branch 'main' into minhd-vu/datastore-improvements
2 parents 2113bcc + bd9923c commit f563d04

File tree

12 files changed

+333
-94
lines changed

12 files changed

+333
-94
lines changed

cmd/fixnoncegap/fixnoncegap.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,12 @@ func fixNonceGap(cmd *cobra.Command, args []string) error {
115115
var maxNonce uint64
116116
if inputFixNonceGapArgs.maxNonce != 0 {
117117
maxNonce = inputFixNonceGapArgs.maxNonce
118+
log.Info().Uint64("maxNonce", maxNonce).Msg("maxNonce loaded from --max-nonce flag")
118119
} else {
119-
maxNonce, err = getMaxNonceFromTxPool(addr)
120+
log.Info().Msg("--max-nonce flag not set")
121+
maxNonce, err = getMaxNonce(addr)
120122
if err != nil {
121-
if strings.Contains(err.Error(), "the method txpool_content does not exist/is not available") {
122-
log.Error().Err(err).Msg("The RPC doesn't provide access to txpool_content, please check --help for more information about --max-nonce")
123-
return nil
124-
}
125-
log.Error().Err(err).Msg("Unable to get max nonce from txpool")
123+
log.Error().Err(err).Msg("Unable to get max nonce, please check --help for more information about --max-nonce")
126124
return err
127125
}
128126
}
@@ -132,7 +130,11 @@ func fixNonceGap(cmd *cobra.Command, args []string) error {
132130
log.Info().Stringer("addr", addr).Msg("There is no nonce gap.")
133131
return nil
134132
}
135-
log.Info().Stringer("addr", addr).Msgf("Nonce gap found. Max nonce: %d", maxNonce)
133+
log.Info().
134+
Stringer("addr", addr).
135+
Uint64("currentNonce", currentNonce).
136+
Uint64("maxNonce", maxNonce).
137+
Msg("Nonce gap found")
136138

137139
gasPrice, err := rpcClient.SuggestGasPrice(cmd.Context())
138140
if err != nil {
@@ -273,6 +275,26 @@ func WaitMineTransaction(ctx context.Context, client *ethclient.Client, tx *type
273275
}
274276
}
275277

278+
func getMaxNonce(addr common.Address) (uint64, error) {
279+
log.Info().Msg("getting max nonce from txpool_content")
280+
maxNonce, err := getMaxNonceFromTxPool(addr)
281+
if err == nil {
282+
log.Info().Uint64("maxNonce", maxNonce).Msg("maxNonce loaded from txpool_content")
283+
return maxNonce, nil
284+
}
285+
log.Warn().Err(err).Msg("unable to get max nonce from txpool_content")
286+
287+
log.Info().Msg("getting max nonce from pending nonce")
288+
// if the error is not about txpool_content, falls back to PendingNonceAt
289+
maxNonce, err = rpcClient.PendingNonceAt(context.Background(), addr)
290+
if err != nil {
291+
return 0, err
292+
}
293+
294+
log.Info().Uint64("maxNonce", maxNonce).Msg("maxNonce loaded from pending nonce")
295+
return maxNonce, nil
296+
}
297+
276298
func getMaxNonceFromTxPool(addr common.Address) (uint64, error) {
277299
var result PoolContent
278300
err := rpcClient.Client().Call(&result, "txpool_content")
@@ -299,6 +321,7 @@ func getMaxNonceFromTxPool(addr common.Address) (uint64, error) {
299321
nonceInt, ok := new(big.Int).SetString(nonce, 10)
300322
if !ok {
301323
err = fmt.Errorf("invalid nonce found: %s", nonce)
324+
log.Warn().Err(err).Msg("unable to get txpool_content")
302325
return 0, err
303326
}
304327

cmd/fund/cmd.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ type cmdFundParams struct {
3535
Seed string
3636

3737
FunderAddress string
38+
39+
// ERC20 specific parameters
40+
TokenAddress string
41+
TokenAmount *big.Int
42+
ERC20BulkMinterAddress string
43+
ApproveSpender string
44+
ApproveAmount *big.Int
3845
}
3946

4047
var (
@@ -80,6 +87,17 @@ func init() {
8087

8188
f.StringVarP(&params.OutputFile, "file", "f", "wallets.json", "output JSON file path for storing addresses and private keys of funded wallets")
8289

90+
// ERC20 parameters
91+
f.StringVar(&params.TokenAddress, "token-address", "", "address of the ERC20 token contract to mint and fund (if provided, enables ERC20 mode)")
92+
params.TokenAmount = new(big.Int)
93+
params.TokenAmount.SetString("1000000000000000000", 10) // 1 token
94+
f.Var(&flag_loader.BigIntValue{Val: params.TokenAmount}, "token-amount", "amount of ERC20 tokens to mint and transfer to each wallet")
95+
f.StringVar(&params.ERC20BulkMinterAddress, "erc20-bulk-funder-address", "", "address of pre-deployed ERC20BulkFunder contract")
96+
f.StringVar(&params.ApproveSpender, "approve-spender", "", "address to approve for spending tokens from each funded wallet")
97+
params.ApproveAmount = new(big.Int)
98+
params.ApproveAmount.SetString("1000000000000000000000", 10) // 1000 tokens default
99+
f.Var(&flag_loader.BigIntValue{Val: params.ApproveAmount}, "approve-amount", "amount of ERC20 tokens to approve for the spender")
100+
83101
// Marking flags as mutually exclusive
84102
FundCmd.MarkFlagsMutuallyExclusive("addresses", "number")
85103
FundCmd.MarkFlagsMutuallyExclusive("addresses", "hd-derivation")
@@ -148,5 +166,20 @@ func checkFlags() error {
148166
return errors.New("the output file is not specified")
149167
}
150168

169+
// ERC20 specific validations
170+
if params.TokenAddress != "" {
171+
// ERC20 mode - validate token parameters
172+
if params.TokenAmount == nil || params.TokenAmount.Cmp(big.NewInt(0)) <= 0 {
173+
return errors.New("token amount must be greater than 0 when using ERC20 mode")
174+
}
175+
// Validate approve parameters if provided
176+
if params.ApproveSpender != "" {
177+
if params.ApproveAmount == nil || params.ApproveAmount.Cmp(big.NewInt(0)) <= 0 {
178+
return errors.New("approve amount must be greater than 0 when approve spender is specified")
179+
}
180+
}
181+
// In ERC20 mode, ETH funding is still supported alongside token minting
182+
}
183+
151184
return nil
152185
}

cmd/fund/fund.go

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/0xPolygon/polygon-cli/bindings/funder"
1717
"github.com/0xPolygon/polygon-cli/hdwallet"
1818
"github.com/0xPolygon/polygon-cli/util"
19+
"github.com/ethereum/go-ethereum/accounts/abi"
1920
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2021
"github.com/ethereum/go-ethereum/common"
2122
"github.com/ethereum/go-ethereum/crypto"
@@ -77,18 +78,27 @@ func runFunding(ctx context.Context) error {
7778
}()
7879
}
7980

80-
// Deploy or instantiate the Funder contract.
81-
var contract *funder.Funder
82-
contract, err = deployOrInstantiateFunderContract(ctx, c, tops, privateKey, len(addresses))
83-
if err != nil {
84-
return err
85-
}
81+
// If ERC20 mode is enabled, fund with tokens instead of ETH
82+
if params.TokenAddress != "" {
83+
log.Info().Str("tokenAddress", params.TokenAddress).Msg("Starting ERC20 token funding (ETH funding disabled)")
84+
if err = fundWalletsWithERC20(ctx, c, tops, privateKey, addresses, privateKeys); err != nil {
85+
return err
86+
}
87+
log.Info().Msg("Wallet(s) funded with ERC20 tokens! 🪙")
88+
} else {
89+
// Deploy or instantiate the Funder contract.
90+
var contract *funder.Funder
91+
contract, err = deployOrInstantiateFunderContract(ctx, c, tops, privateKey, len(addresses))
92+
if err != nil {
93+
return err
94+
}
8695

87-
// Fund wallets.
88-
if err = fundWallets(ctx, c, tops, contract, addresses); err != nil {
89-
return err
96+
// Fund wallets with ETH.
97+
if err = fundWallets(ctx, c, tops, contract, addresses); err != nil {
98+
return err
99+
}
100+
log.Info().Msg("Wallet(s) funded with ETH! 💸")
90101
}
91-
log.Info().Msg("Wallet(s) funded! 💸")
92102

93103
log.Info().Msgf("Total execution time: %s", time.Since(startTime))
94104
return nil
@@ -367,3 +377,90 @@ func getAddressesAndKeysFromSeed(seed string, numWallets int) ([]common.Address,
367377
log.Info().Int("count", numWallets).Msg("Wallet(s) generated from seed")
368378
return addresses, privateKeys, nil
369379
}
380+
381+
// fundWalletsWithERC20 funds multiple wallets with ERC20 tokens by minting directly to each wallet and optionally approving a spender.
382+
func fundWalletsWithERC20(ctx context.Context, c *ethclient.Client, tops *bind.TransactOpts, privateKey *ecdsa.PrivateKey, wallets []common.Address, walletsPrivateKeys []*ecdsa.PrivateKey) error {
383+
if len(wallets) == 0 {
384+
return errors.New("no wallet to fund with ERC20 tokens")
385+
}
386+
387+
// Get the token contract instance
388+
tokenAddress := common.HexToAddress(params.TokenAddress)
389+
390+
log.Info().Int("wallets", len(wallets)).Str("amountPerWallet", params.TokenAmount.String()).Msg("Minting tokens directly to each wallet")
391+
392+
// Create ABI for mint(address, uint256) function since the generated binding has wrong signature
393+
mintABI, err := abi.JSON(strings.NewReader(`[{"type":"function","name":"mint","inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"outputs":[],"stateMutability":"nonpayable"}]`))
394+
if err != nil {
395+
log.Error().Err(err).Msg("Unable to parse mint ABI")
396+
return err
397+
}
398+
399+
// Create bound contract with the correct ABI
400+
mintContract := bind.NewBoundContract(tokenAddress, mintABI, c, c, c)
401+
402+
// Mint tokens directly to each wallet
403+
for i, wallet := range wallets {
404+
log.Debug().Int("wallet", i+1).Int("total", len(wallets)).Str("address", wallet.String()).Str("amount", params.TokenAmount.String()).Msg("Minting tokens directly to wallet")
405+
406+
// Call mint(address, uint256) function directly
407+
_, err = mintContract.Transact(tops, "mint", wallet, params.TokenAmount)
408+
if err != nil {
409+
log.Error().Err(err).Str("wallet", wallet.String()).Msg("Unable to mint ERC20 tokens directly to wallet")
410+
return err
411+
}
412+
}
413+
414+
log.Info().Int("count", len(wallets)).Str("amount", params.TokenAmount.String()).Msg("Successfully minted tokens to all wallets")
415+
416+
// If approve spender is specified, approve tokens from each wallet
417+
if params.ApproveSpender != "" && len(walletsPrivateKeys) > 0 {
418+
spenderAddress := common.HexToAddress(params.ApproveSpender)
419+
log.Info().Str("spender", spenderAddress.String()).Str("amount", params.ApproveAmount.String()).Msg("Starting bulk approve for all wallets")
420+
421+
// Create ABI for approve(address, uint256) function
422+
approveABI, err := abi.JSON(strings.NewReader(`[{"type":"function","name":"approve","inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"outputs":[{"name":"","type":"bool"}],"stateMutability":"nonpayable"}]`))
423+
if err != nil {
424+
log.Error().Err(err).Msg("Unable to parse approve ABI")
425+
return err
426+
}
427+
428+
// Get chain ID for signing transactions
429+
chainID, err := c.ChainID(ctx)
430+
if err != nil {
431+
log.Error().Err(err).Msg("Unable to get chain ID for approve transactions")
432+
return err
433+
}
434+
435+
// Approve from each wallet
436+
for i, walletPrivateKey := range walletsPrivateKeys {
437+
if i >= len(wallets) {
438+
break // Safety check
439+
}
440+
441+
wallet := wallets[i]
442+
log.Debug().Int("wallet", i+1).Int("total", len(wallets)).Str("address", wallet.String()).Str("spender", spenderAddress.String()).Str("amount", params.ApproveAmount.String()).Msg("Approving spender from wallet")
443+
444+
// Create transaction options for this wallet
445+
walletTops, err := bind.NewKeyedTransactorWithChainID(walletPrivateKey, chainID)
446+
if err != nil {
447+
log.Error().Err(err).Str("wallet", wallet.String()).Msg("Unable to create transaction signer for wallet")
448+
return err
449+
}
450+
451+
// Create bound contract for approve call
452+
approveContract := bind.NewBoundContract(tokenAddress, approveABI, c, c, c)
453+
454+
// Call approve(address, uint256) function from this wallet
455+
_, err = approveContract.Transact(walletTops, "approve", spenderAddress, params.ApproveAmount)
456+
if err != nil {
457+
log.Error().Err(err).Str("wallet", wallet.String()).Str("spender", spenderAddress.String()).Msg("Unable to approve spender from wallet")
458+
return err
459+
}
460+
}
461+
462+
log.Info().Int("count", len(wallets)).Str("spender", spenderAddress.String()).Str("amount", params.ApproveAmount.String()).Msg("Successfully approved spender for all wallets")
463+
}
464+
465+
return nil
466+
}

cmd/ulxly/bridge_service/aggkit/service.go

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,43 @@ func (s *BridgeService) GetDeposits(destinationAddress string, offset, limit int
5757
return nil, 0, fmt.Errorf("GetDeposits is not supported by aggkit bridge service yet")
5858
}
5959

60-
func (s *BridgeService) GetProof(depositNetwork, depositCount uint32, ger *common.Hash) (*bridge_service.Proof, error) {
61-
var l1InfoTreeIndex uint32
60+
func (s *BridgeService) GetProof(depositNetwork, depositCount uint32) (*bridge_service.Proof, error) {
61+
return s.getProof(depositNetwork, depositCount, nil)
62+
}
6263

63-
if ger != nil {
64-
return nil, errors.New("getting proof by ger is not supported yet by Aggkit bridge service")
65-
}
64+
func (s *BridgeService) GetProofByGer(depositNetwork, depositCount uint32, ger common.Hash) (*bridge_service.Proof, error) {
65+
return nil, errors.New("getting proof by ger is not supported yet by Aggkit bridge service")
66+
}
6667

67-
timeout := time.After(time.Minute)
68-
out:
69-
for {
70-
idx, err := s.getL1InfoTreeIndex(depositNetwork, depositCount)
71-
if err != nil && !errors.Is(err, bridge_service.ErrNotFound) {
72-
return nil, err
73-
} else if err == nil {
74-
l1InfoTreeIndex = *idx
75-
break out
76-
}
77-
select {
78-
case <-timeout:
79-
return nil, fmt.Errorf("timeout waiting for l1 info tree index")
80-
default:
81-
time.Sleep(time.Second)
68+
func (s *BridgeService) GetProofByL1InfoTreeIndex(depositNetwork, depositCount uint32, l1InfoTreeIndex uint32) (*bridge_service.Proof, error) {
69+
return s.getProof(depositNetwork, depositCount, &l1InfoTreeIndex)
70+
}
71+
72+
func (s *BridgeService) getProof(depositNetwork, depositCount uint32, l1InfoTreeIndex *uint32) (*bridge_service.Proof, error) {
73+
var l1InfoTreeIndexValue uint32
74+
if l1InfoTreeIndex != nil {
75+
l1InfoTreeIndexValue = *l1InfoTreeIndex
76+
} else {
77+
78+
timeout := time.After(time.Minute)
79+
out:
80+
for {
81+
idx, err := s.getL1InfoTreeIndex(depositNetwork, depositCount)
82+
if err != nil && !errors.Is(err, bridge_service.ErrNotFound) {
83+
return nil, err
84+
} else if err == nil {
85+
l1InfoTreeIndexValue = *idx
86+
break out
87+
}
88+
select {
89+
case <-timeout:
90+
return nil, fmt.Errorf("timeout waiting for l1 info tree index")
91+
default:
92+
time.Sleep(time.Second)
93+
}
8294
}
8395
}
84-
85-
endpoint := fmt.Sprintf("%s/%s/claim-proof?network_id=%d&leaf_index=%d&deposit_count=%d", s.BridgeServiceBase.Url(), urlPath, depositNetwork, l1InfoTreeIndex, depositCount)
96+
endpoint := fmt.Sprintf("%s/%s/claim-proof?network_id=%d&leaf_index=%d&deposit_count=%d", s.BridgeServiceBase.Url(), urlPath, depositNetwork, l1InfoTreeIndexValue, depositCount)
8697
resp, respError, statusCode, err := httpjson.HTTPGetWithError[getClaimProofResponse, errorResponse](s.httpClient, endpoint)
8798
if err != nil {
8899
return nil, err

cmd/ulxly/bridge_service/interface.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ var (
1515
type BridgeService interface {
1616
GetDeposit(depositNetwork, depositCount uint32) (*Deposit, error)
1717
GetDeposits(destinationAddress string, offset, limit int) (deposits []Deposit, total int, err error)
18-
GetProof(depositNetwork, depositCount uint32, ger *common.Hash) (*Proof, error)
18+
GetProof(depositNetwork, depositCount uint32) (*Proof, error)
19+
GetProofByGer(depositNetwork, depositCount uint32, ger common.Hash) (*Proof, error)
20+
GetProofByL1InfoTreeIndex(depositNetwork, depositCount uint32, l1InfoTreeIndex uint32) (*Proof, error)
1921
Url() string
2022
}
2123

cmd/ulxly/bridge_service/legacy/service.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,21 @@ func (s *BridgeService) GetDeposits(destinationAddress string, offset, limit int
6666

6767
}
6868

69-
func (s *BridgeService) GetProof(depositNetwork, depositCount uint32, ger *common.Hash) (*bridge_service.Proof, error) {
69+
func (s *BridgeService) GetProof(depositNetwork, depositCount uint32) (*bridge_service.Proof, error) {
7070
endpoint := fmt.Sprintf("%s/merkle-proof?net_id=%d&deposit_cnt=%d", s.BridgeServiceBase.Url(), depositNetwork, depositCount)
71-
if ger != nil {
72-
endpoint = fmt.Sprintf("%s/merkle-proof-by-ger?net_id=%d&deposit_cnt=%d&ger=%s", s.BridgeServiceBase.Url(), depositNetwork, depositCount, ger.String())
71+
72+
resp, _, err := httpjson.HTTPGet[GetProofResponse](s.httpClient, endpoint)
73+
if err != nil {
74+
return nil, err
7375
}
7476

77+
proof := resp.Proof.ToProof()
78+
return proof, nil
79+
}
80+
81+
func (s *BridgeService) GetProofByGer(depositNetwork, depositCount uint32, ger common.Hash) (*bridge_service.Proof, error) {
82+
endpoint := fmt.Sprintf("%s/merkle-proof-by-ger?net_id=%d&deposit_cnt=%d&ger=%s", s.BridgeServiceBase.Url(), depositNetwork, depositCount, ger.String())
83+
7584
resp, _, err := httpjson.HTTPGet[GetProofResponse](s.httpClient, endpoint)
7685
if err != nil {
7786
return nil, err
@@ -80,3 +89,7 @@ func (s *BridgeService) GetProof(depositNetwork, depositCount uint32, ger *commo
8089
proof := resp.Proof.ToProof()
8190
return proof, nil
8291
}
92+
93+
func (s *BridgeService) GetProofByL1InfoTreeIndex(depositNetwork, depositCount uint32, l1InfoTreeIndex uint32) (*bridge_service.Proof, error) {
94+
return nil, fmt.Errorf("GetProofByL1InfoTreeIndex is not supported in the legacy bridge service")
95+
}

0 commit comments

Comments
 (0)