Skip to content

Commit d40061f

Browse files
committed
feat: polycli fund support for bulk token approval
Signed-off-by: Ji Hwan <[email protected]>
1 parent 7b8eaf0 commit d40061f

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

cmd/fund/cmd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type cmdFundParams struct {
4040
TokenAddress string
4141
TokenAmount *big.Int
4242
ERC20BulkMinterAddress string
43+
ApproveSpender string
44+
ApproveAmount *big.Int
4345
}
4446

4547
var (
@@ -91,6 +93,10 @@ func init() {
9193
params.TokenAmount.SetString("1000000000000000000", 10) // 1 token
9294
f.Var(&flag_loader.BigIntValue{Val: params.TokenAmount}, "token-amount", "amount of ERC20 tokens to mint and transfer to each wallet")
9395
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")
94100

95101
// Marking flags as mutually exclusive
96102
FundCmd.MarkFlagsMutuallyExclusive("addresses", "number")
@@ -166,6 +172,12 @@ func checkFlags() error {
166172
if params.TokenAmount == nil || params.TokenAmount.Cmp(big.NewInt(0)) <= 0 {
167173
return errors.New("token amount must be greater than 0 when using ERC20 mode")
168174
}
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+
}
169181
// In ERC20 mode, ETH funding is still supported alongside token minting
170182
}
171183

cmd/fund/fund.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func runFunding(ctx context.Context) error {
8181
// If ERC20 mode is enabled, fund with tokens instead of ETH
8282
if params.TokenAddress != "" {
8383
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 {
84+
if err = fundWalletsWithERC20(ctx, c, tops, privateKey, addresses, privateKeys); err != nil {
8585
return err
8686
}
8787
log.Info().Msg("Wallet(s) funded with ERC20 tokens! 🪙")
@@ -378,8 +378,8 @@ func getAddressesAndKeysFromSeed(seed string, numWallets int) ([]common.Address,
378378
return addresses, privateKeys, nil
379379
}
380380

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 {
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 {
383383
if len(wallets) == 0 {
384384
return errors.New("no wallet to fund with ERC20 tokens")
385385
}
@@ -411,6 +411,56 @@ func fundWalletsWithERC20(ctx context.Context, c *ethclient.Client, tops *bind.T
411411
}
412412
}
413413

414-
log.Info().Int("count", len(wallets)).Str("amount", params.TokenAmount.String()).Msg("Successfully funded all wallets with tokens")
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+
415465
return nil
416466
}

0 commit comments

Comments
 (0)