Skip to content

Commit 7b8eaf0

Browse files
committed
feat: bulk funding erc20 tokens
Signed-off-by: Ji Hwan <jkim@polygon.technology>
1 parent 163f30e commit 7b8eaf0

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

cmd/fund/cmd.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ 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
3843
}
3944

4045
var (
@@ -80,6 +85,13 @@ func init() {
8085

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

88+
// ERC20 parameters
89+
f.StringVar(&params.TokenAddress, "token-address", "", "address of the ERC20 token contract to mint and fund (if provided, enables ERC20 mode)")
90+
params.TokenAmount = new(big.Int)
91+
params.TokenAmount.SetString("1000000000000000000", 10) // 1 token
92+
f.Var(&flag_loader.BigIntValue{Val: params.TokenAmount}, "token-amount", "amount of ERC20 tokens to mint and transfer to each wallet")
93+
f.StringVar(&params.ERC20BulkMinterAddress, "erc20-bulk-funder-address", "", "address of pre-deployed ERC20BulkFunder contract")
94+
8395
// Marking flags as mutually exclusive
8496
FundCmd.MarkFlagsMutuallyExclusive("addresses", "number")
8597
FundCmd.MarkFlagsMutuallyExclusive("addresses", "hd-derivation")
@@ -148,5 +160,14 @@ func checkFlags() error {
148160
return errors.New("the output file is not specified")
149161
}
150162

163+
// ERC20 specific validations
164+
if params.TokenAddress != "" {
165+
// ERC20 mode - validate token parameters
166+
if params.TokenAmount == nil || params.TokenAmount.Cmp(big.NewInt(0)) <= 0 {
167+
return errors.New("token amount must be greater than 0 when using ERC20 mode")
168+
}
169+
// In ERC20 mode, ETH funding is still supported alongside token minting
170+
}
171+
151172
return nil
152173
}

cmd/fund/fund.go

Lines changed: 57 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); 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,40 @@ 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 to the funding account and transferring.
382+
func fundWalletsWithERC20(ctx context.Context, c *ethclient.Client, tops *bind.TransactOpts, privateKey *ecdsa.PrivateKey, wallets []common.Address) 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 funded all wallets with tokens")
415+
return nil
416+
}

0 commit comments

Comments
 (0)