diff --git a/client/client.go b/client/client.go index 4b04d1d..67102ac 100644 --- a/client/client.go +++ b/client/client.go @@ -77,6 +77,18 @@ func NewBlockchainWithCustomNonceTracker(ethClient EthClientGetter, timeout time } } +func (bc *Blockchain) makeTransactOpts(ctx context.Context, rr *WriteRequest) (*bind.TransactOpts, error) { + if rr.Nonce == nil { + nonceUint, err := bc.getNonce(rr.Identity) + if err != nil { + return nil, errors.Wrap(err, "could not get nonce") + } + rr.Nonce = big.NewInt(0).SetUint64(nonceUint) + } + + return rr.toTransactOpts(ctx), nil +} + // GetHermesFee fetches the hermes fee from blockchain func (bc *Blockchain) GetHermesFee(hermesAddress common.Address) (uint16, error) { caller, err := bindings.NewHermesImplementationCaller(hermesAddress, bc.ethClient.Client()) @@ -356,11 +368,45 @@ func (r RegistrationRequest) toEstimateOps() *bindings.EstimateOpts { // WriteRequest contains the required params for a write request type WriteRequest struct { + Nonce *big.Int Identity common.Address Signer bind.SignerFn + + // GasTip is the amount of tokens to tip for miners. Required for EIP-1559. + GasTip *big.Int + // BaseFee is the amount which has and will be paid for the transaction. Required for EIP-1559. + BaseFee *big.Int GasLimit uint64 + + // GasPrice is the legacy gas price pre london hardfork. GasPrice *big.Int - Nonce *big.Int +} + +func (wr *WriteRequest) toTransactOpts(ctx context.Context) *bind.TransactOpts { + if wr.GasPrice != nil && wr.GasTip != nil { + panic("could not convert write request to transact opts: can't set both GasTip and GasPrice") + } + + to := &bind.TransactOpts{ + Context: ctx, + From: wr.Identity, + Signer: wr.Signer, + GasLimit: wr.GasLimit, + Nonce: wr.Nonce, + } + + // Support pre EIP-1559 transactions + if wr.GasPrice != nil && wr.GasPrice.Cmp(big.NewInt(0)) > 0 { + to.GasPrice = wr.GasPrice + return to + } + + if wr.GasTip != nil && wr.BaseFee != nil { + to.GasFeeCap = new(big.Int).Add(wr.GasTip, wr.BaseFee) + } + to.GasTipCap = wr.GasTip + + return to } // getGasLimit returns the gas limit @@ -374,26 +420,17 @@ func (bc *Blockchain) RegisterIdentity(rr RegistrationRequest) (*types.Transacti if err != nil { return nil, err } - parent := context.Background() - ctx, cancel := context.WithTimeout(parent, bc.bcTimeout) + + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - nonce := rr.Nonce - if nonce == nil { - nonceUint, err := bc.getNonce(rr.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - nonce = big.NewInt(0).SetUint64(nonceUint) + to, err := bc.makeTransactOpts(ctx, &rr.WriteRequest) + if err != nil { + return nil, err } - tx, err := transactor.RegisterIdentity(&bind.TransactOpts{ - From: rr.Identity, - Signer: rr.Signer, - Context: ctx, - GasLimit: rr.GasLimit, - GasPrice: rr.GasPrice, - Nonce: nonce, - }, + + tx, err := transactor.RegisterIdentity( + to, rr.HermesID, rr.Stake, rr.TransactorFee, @@ -435,22 +472,13 @@ func (bc *Blockchain) PayAndSettle(psr PayAndSettleRequest) (*types.Transaction, ctx, cancel := context.WithTimeout(parent, bc.bcTimeout) defer cancel() - nonce := psr.Nonce - if nonce == nil { - nonceUint, err := bc.getNonce(psr.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - nonce = big.NewInt(0).SetUint64(nonceUint) + to, err := bc.makeTransactOpts(ctx, &psr.WriteRequest) + if err != nil { + return nil, err } - tx, err := transactor.PayAndSettle(&bind.TransactOpts{ - From: psr.Identity, - Signer: psr.Signer, - Context: ctx, - GasLimit: psr.GasLimit, - GasPrice: psr.GasPrice, - Nonce: nonce, - }, + + tx, err := transactor.PayAndSettle( + to, psr.ProviderID, psr.Promise.Amount, psr.Promise.Fee, @@ -489,21 +517,15 @@ func (bc *Blockchain) TransferMyst(req TransferRequest) (tx *types.Transaction, return tx, err } - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) + defer cancel() + + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return transactor.Transfer(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - GasPrice: req.GasPrice, - GasLimit: req.GasLimit, - Nonce: req.Nonce, - }, req.Recipient, req.Amount) + return transactor.Transfer(to, req.Recipient, req.Amount) } // IsHermesRegistered checks if given hermes is registered and returns true or false. @@ -548,13 +570,15 @@ func (bc *Blockchain) IncreaseProviderStake(req ProviderStakeIncreaseRequest) (* return nil, err } - transactor, cancel, err := bc.getTransactorFromRequest(req.WriteRequest) + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() + + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) if err != nil { - return nil, fmt.Errorf("could not get transactor: %w", err) + return nil, err } - return t.IncreaseStake(transactor, req.ChannelID, req.Amount) + return t.IncreaseStake(to, req.ChannelID, req.Amount) } // SettleIntoStakeRequest represents all the parameters required for settling into stake. @@ -590,12 +614,6 @@ func (bc *Blockchain) SettleIntoStake(req SettleIntoStakeRequest) (*types.Transa return nil, err } - transactor, cancel, err := bc.getTransactorFromRequest(req.WriteRequest) - defer cancel() - if err != nil { - return nil, fmt.Errorf("could not get transactor: %w", err) - } - amount := req.Promise.Amount fee := req.Promise.Fee lock := [32]byte{} @@ -604,7 +622,15 @@ func (bc *Blockchain) SettleIntoStake(req SettleIntoStakeRequest) (*types.Transa channelID := [32]byte{} copy(channelID[:], req.Promise.ChannelID) - return t.SettleIntoStake(transactor, req.ProviderID, amount, fee, lock, req.Promise.Signature) + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) + defer cancel() + + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err + } + + return t.SettleIntoStake(to, req.ProviderID, amount, fee, lock, req.Promise.Signature) } // DecreaseProviderStakeRequest represents all the parameters required for decreasing provider stake. @@ -648,8 +674,10 @@ func (bc *Blockchain) DecreaseProviderStake(req DecreaseProviderStakeRequest) (* return nil, err } - transactor, cancel, err := bc.getTransactorFromRequest(req.WriteRequest) + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() + + transactor, err := bc.makeTransactOpts(ctx, &req.WriteRequest) if err != nil { return nil, fmt.Errorf("could not get transactor: %w", err) } @@ -657,28 +685,6 @@ func (bc *Blockchain) DecreaseProviderStake(req DecreaseProviderStakeRequest) (* return t.DecreaseStake(transactor, req.ProviderID, req.Request.Amount, req.Request.TransactorFee, req.Request.Signature) } -func (bc *Blockchain) getTransactorFromRequest(req WriteRequest) (*bind.TransactOpts, func(), error) { - parent := context.Background() - ctx, cancel := context.WithTimeout(parent, bc.bcTimeout) - - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, cancel, errors.Wrap(err, "could not get nonce") - } - req.Nonce = big.NewInt(0).SetUint64(nonce) - } - - return &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, cancel, nil -} - // GetHermesOperator returns operator address of given hermes func (bc *Blockchain) GetHermesOperator(hermesID common.Address) (common.Address, error) { caller, err := bindings.NewHermesImplementationCaller(hermesID, bc.ethClient.Client()) @@ -737,25 +743,17 @@ func (bc *Blockchain) SettleAndRebalance(req SettleAndRebalanceRequest) (*types. if err != nil { return nil, err } + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return transactor.SettlePromise(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + return transactor.SettlePromise( + to, req.ProviderID, req.Promise.Amount, req.Promise.Fee, @@ -923,15 +921,13 @@ func (bc *Blockchain) SettlePromise(req SettleRequest) (*types.Transaction, erro req.Nonce = new(big.Int).SetUint64(nonce) } - return transactor.SettlePromise(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, - amount, fee, lock, req.Promise.Signature, + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err + + } + return transactor.SettlePromise( + to, amount, fee, lock, req.Promise.Signature, ) } @@ -1090,15 +1086,26 @@ func (bc *Blockchain) TransferEth(etr EthTransferRequest) (*types.Transaction, e ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if etr.Nonce == nil { - nonce, err := bc.getNonce(etr.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - etr.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &etr.WriteRequest) + if err != nil { + return nil, err + } + + var tx *types.Transaction + if to.GasPrice != nil && to.GasPrice.Cmp(big.NewInt(0)) > 0 { + tx = types.NewTransaction(to.Nonce.Uint64(), etr.To, etr.Amount, to.GasLimit, to.GasPrice, nil) + } else { + tx = types.NewTx(&types.DynamicFeeTx{ + Nonce: to.Nonce.Uint64(), + To: &etr.To, + Value: etr.Amount, + Gas: to.GasLimit, + GasFeeCap: to.GasFeeCap, + GasTipCap: to.GasTipCap, + Data: nil, + }) } - tx := types.NewTransaction(etr.Nonce.Uint64(), etr.To, etr.Amount, etr.GasLimit, etr.GasPrice, nil) signedTx, err := etr.Signer(etr.Identity, tx) if err != nil { return nil, fmt.Errorf("could not sign tx: %w", err) @@ -1228,22 +1235,13 @@ func (bc *Blockchain) SettleWithBeneficiary(req SettleWithBeneficiaryRequest) (* ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return transactor.SettleWithBeneficiary(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + return transactor.SettleWithBeneficiary( + to, req.ProviderID, req.Promise.Amount, req.Promise.Fee, @@ -1294,22 +1292,13 @@ func (bc *Blockchain) RewarderUpdateRoot(req RewarderUpdateRoot) (*types.Transac ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return transactor.UpdateRoot(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + return transactor.UpdateRoot( + to, ToBytes32(req.ClaimRoot), req.BlockNumber, req.TotalReward, @@ -1332,22 +1321,13 @@ func (bc *Blockchain) RewarderAirDrop(req RewarderAirDrop) (*types.Transaction, ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return transactor.Airdrop(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + return transactor.Airdrop( + to, req.Beneficiaries, req.TotalEarnings, ) @@ -1398,23 +1378,13 @@ func (bc *Blockchain) CustodyTransferTokens(req CustodyTokensTransfer) (*types.T ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } return transactor.Payout( - &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + to, req.Recipients, req.Amounts, ) @@ -1516,23 +1486,13 @@ func (bc *Blockchain) TopperupperApproveAddresses(req TopperupperApproveAddresse ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } return transactor.ApproveAddresses( - &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + to, []common.Address{req.Address}, []*big.Int{req.LimitsNative}, []*big.Int{req.LimitsToken}, @@ -1555,23 +1515,13 @@ func (bc *Blockchain) TopperupperSetManagers(req TopperupperModeratorsReq) (*typ ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } return transactor.SetManagers( - &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + to, req.Managers, ) } @@ -1592,23 +1542,13 @@ func (bc *Blockchain) TopperupperTopupNative(req TopperupperTopupNativeReq) (*ty ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } return transactor.TopupNative( - &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + to, req.To, req.Amount, ) @@ -1630,23 +1570,13 @@ func (bc *Blockchain) TopperupperTopupToken(req TopperupperTopupTokenReq) (*type ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } return transactor.TopupToken( - &bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, + to, req.To, req.Amount, ) @@ -1668,22 +1598,12 @@ func (bc *Blockchain) MystTokenApprove(req MystApproveReq) (*types.Transaction, ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) defer cancel() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err } - return txer.Approve(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, req.Spender, req.Amount) + return txer.Approve(to, req.Spender, req.Amount) } func (bc *Blockchain) MystAllowance(mystTokenAddress, holder, spender common.Address) (*big.Int, error) { @@ -1721,42 +1641,37 @@ func (bc *Blockchain) UniswapV3ExactInputSingle(req UniswapExactInputSingleReq) return nil, err } - ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) - defer cancel() + ctx1, cancel1 := context.WithTimeout(context.Background(), bc.bcTimeout) + defer cancel1() - if req.Nonce == nil { - nonce, err := bc.getNonce(req.Identity) - if err != nil { - return nil, errors.Wrap(err, "could not get nonce") - } - req.Nonce = new(big.Int).SetUint64(nonce) + b, err := bc.ethClient.Client().BlockByNumber(ctx1, nil) + if err != nil { + return nil, err } - b, err := bc.ethClient.Client().BlockByNumber(ctx, nil) + ctx2, cancel2 := context.WithTimeout(context.Background(), bc.bcTimeout) + defer cancel2() + + to, err := bc.makeTransactOpts(ctx2, &req.WriteRequest) if err != nil { return nil, err } - return txer.ExactInputSingle(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, uniswapv3.ISwapRouterExactInputSingleParams{ - TokenIn: req.TokenIn, - TokenOut: req.TokenOut, - Fee: req.Fee, + return txer.ExactInputSingle( + to, + uniswapv3.ISwapRouterExactInputSingleParams{ + TokenIn: req.TokenIn, + TokenOut: req.TokenOut, + Fee: req.Fee, - Recipient: req.Recipient, - Deadline: big.NewInt(0).SetUint64(b.Time() + req.DeadlineSeconds), + Recipient: req.Recipient, + Deadline: big.NewInt(0).SetUint64(b.Time() + req.DeadlineSeconds), - AmountIn: req.AmountIn, - AmountOutMinimum: req.AmountOutMinimum, + AmountIn: req.AmountIn, + AmountOutMinimum: req.AmountOutMinimum, - SqrtPriceLimitX96: big.NewInt(0), // We can mostly ignore it for our purposes. - }) + SqrtPriceLimitX96: big.NewInt(0), // We can mostly ignore it for our purposes. + }) } type SwapTokenPair struct { @@ -1835,9 +1750,6 @@ func (bc *Blockchain) WMaticWithdraw(req WMaticWithdrawReq) (*types.Transaction, return nil, err } - ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) - defer cancel() - amount := req.Amount if amount == nil { all, err := bc.WMaticBalance(req.Identity, req.WMaticAddress) @@ -1848,12 +1760,13 @@ func (bc *Blockchain) WMaticWithdraw(req WMaticWithdrawReq) (*types.Transaction, amount = all } - return caller.Withdraw(&bind.TransactOpts{ - From: req.Identity, - Signer: req.Signer, - Context: ctx, - GasLimit: req.GasLimit, - GasPrice: req.GasPrice, - Nonce: req.Nonce, - }, amount) + ctx, cancel := context.WithTimeout(context.Background(), bc.bcTimeout) + defer cancel() + + to, err := bc.makeTransactOpts(ctx, &req.WriteRequest) + if err != nil { + return nil, err + } + + return caller.Withdraw(to, amount) } diff --git a/transaction/delivery.go b/transaction/delivery.go index ff320e0..586a130 100644 --- a/transaction/delivery.go +++ b/transaction/delivery.go @@ -34,10 +34,14 @@ type Delivery struct { UniqueID string Sender common.Address - Nonce uint64 - ChainID int64 + Nonce uint64 + ChainID int64 + GasPrice *big.Int + GasTip *big.Int + BaseFee *big.Int + Type DeliverableType State DeliveryState @@ -73,13 +77,22 @@ type SignFunc func(common.Address, *types.Transaction) (*types.Transaction, erro // ToWriteRequest will convert a Delivery to a typical write request used by `client` package. func (t *Delivery) ToWriteRequest(signer SignFunc, gasLimit uint64) client.WriteRequest { - return client.WriteRequest{ + wr := client.WriteRequest{ Identity: t.Sender, Signer: bind.SignerFn(signer), GasLimit: gasLimit, - GasPrice: t.GasPrice, Nonce: new(big.Int).SetUint64(t.Nonce), } + + // Support pre EIP-1559 transactions. + if t.GasPrice != nil && t.GasPrice.Cmp(big.NewInt(0)) > 0 { + wr.GasPrice = t.GasPrice + return wr + } + + wr.GasTip = t.GasTip + wr.BaseFee = t.BaseFee + return wr } func (t *Delivery) GetLastTransaction() (*types.Transaction, error) { @@ -91,6 +104,18 @@ func (t *Delivery) GetLastTransaction() (*types.Transaction, error) { return tx, tx.UnmarshalJSON(t.SentTransaction) } +func (d *Delivery) applyFees(f *fees) *Delivery { + dd := *d + if d.GasPrice != nil && d.GasPrice.Cmp(big.NewInt(0)) > 0 { + dd.GasPrice = new(big.Int).Add(f.Base, f.Tip) + } else { + dd.GasTip = f.Tip + dd.BaseFee = f.Base + } + + return &dd +} + type DeliveryRequest struct { ChainID int64 Sender common.Address @@ -120,6 +145,8 @@ func (t *DeliveryRequest) toDelivery(nonce uint64) (Delivery, error) { Nonce: nonce, ChainID: t.ChainID, GasPrice: new(big.Int).SetInt64(0), + GasTip: new(big.Int).SetInt64(0), + BaseFee: new(big.Int).SetInt64(0), Type: t.Type, State: DeliveryStateWaiting, diff --git a/transaction/depot.go b/transaction/depot.go index 6acda48..5b79a2e 100644 --- a/transaction/depot.go +++ b/transaction/depot.go @@ -326,7 +326,7 @@ func (d *Depot) sendOutTransaction(td Delivery) (Delivery, error) { } func (d *Depot) calculateNewGasPrice(td Delivery) (Delivery, error) { - newPrice := big.NewInt(0) + newPrice := &fees{} switch td.State { case DeliveryStatePacking, DeliveryStateWaiting: gasPrice, err := d.gasStation.ReceiveInitialGas(td.ChainID) @@ -365,7 +365,7 @@ func (d *Depot) calculateNewGasPrice(td Delivery) (Delivery, error) { return Delivery{}, fmt.Errorf("impossible to handle state: %v", td.State) } - if newPrice.Cmp(big.NewInt(0)) == 0 { + if newPrice == nil && newPrice.Tip.Cmp(big.NewInt(0)) == 0 { return Delivery{}, errors.New("ended up with 0 gas price, cannot continue") } @@ -376,8 +376,8 @@ func (d *Depot) calculateNewGasPrice(td Delivery) (Delivery, error) { return newDelivery, nil } -func (d *Depot) deliveryUpdateGasPrice(td Delivery, newGas *big.Int) (Delivery, error) { - td.GasPrice = newGas +func (d *Depot) deliveryUpdateGasPrice(td Delivery, newGas *fees) (Delivery, error) { + td = *td.applyFees(newGas) td.UpdateUTC = time.Now().UTC() if err := d.storage.UpsertDeliveryRequest(td); err != nil { return td, fmt.Errorf("failed to update delivery gas price: %w", err) diff --git a/transaction/gas/eth.go b/transaction/gas/eth.go deleted file mode 100644 index 0f07db9..0000000 --- a/transaction/gas/eth.go +++ /dev/null @@ -1,171 +0,0 @@ -package gas - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "math/big" - "net" - "net/http" - "strings" - "time" - - "github.com/mysteriumnetwork/payments/units" -) - -// DefaultDefiPulseEndpointURI the default defipulse api endpoint. -const DefaultDefiPulseEndpointURI = "https://data-api.defipulse.com/api/v1/" - -// EthStation represents the defi pulse api to retrive gas prices. -type EthStation struct { - APIKey string - EndpointURI string - upperBound *big.Int - - client *http.Client -} - -// NewEthStation returns a new instance of defi pulse api for gas price checks. -func NewEthStation(timeout time.Duration, apiKey, endpointURI string, upperBound *big.Int) *EthStation { - endpoint := endpointURI - if !strings.HasSuffix(endpoint, "/") { - endpoint += "/" - } - - return &EthStation{ - client: &http.Client{ - Transport: &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 5 * time.Second, - }).DialContext, - TLSHandshakeTimeout: 5 * time.Second, - ExpectContinueTimeout: 4 * time.Second, - ResponseHeaderTimeout: 3 * time.Second, - }, - Timeout: timeout, - }, - APIKey: apiKey, - EndpointURI: endpoint, - upperBound: upperBound, - } -} - -func (dpa *EthStation) GetGasPrices() (*GasPrices, error) { - res, err := dpa.defiRequest() - if err != nil { - return nil, err - } - prices := GasPrices{ - SafeLow: dpa.result(res.SafeLow), - Average: dpa.result(res.Average), - Fast: dpa.result(res.Fast), - } - return &prices, nil -} - -func (dpa *EthStation) defiRequest() (*GasStationResponse, error) { - if dpa.APIKey == "" { - return nil, errors.New("no API key set, can't use eth gas station") - } - - response, err := dpa.client.Get(fmt.Sprintf("%v%v%v", dpa.EndpointURI, "egs/api/ethgasAPI.json?api-key=", dpa.APIKey)) - if err != nil { - return nil, err - } - defer response.Body.Close() - - resp, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - res := GasStationResponse{} - err = json.Unmarshal(resp, &res) - if err != nil { - return nil, err - } - - return &res, nil -} - -func (dpa *EthStation) result(p float64) *big.Int { - pb := p / 10.0 - return priceMaxUpperBound(units.FloatGweiToBigIntWei(pb), dpa.upperBound) -} - -// GasStationResponse returns the gas station response. -type GasStationResponse struct { - Fast float64 `json:"fast"` - Fastest float64 `json:"fastest"` - SafeLow float64 `json:"safeLow"` - Average float64 `json:"average"` - BlockTime float64 `json:"block_time"` - BlockNum int64 `json:"blockNum"` - Speed float64 `json:"speed"` - SafeLowWait float64 `json:"safeLowWait"` - AvgWait float64 `json:"avgWait"` - FastWait float64 `json:"fastWait"` - FastestWait float64 `json:"fastestWait"` - GasPriceRange GasPriceRange `json:"gasPriceRange"` -} - -// GasPriceRange the gas price range report. -type GasPriceRange struct { - Num4 float64 `json:"4"` - Num6 float64 `json:"6"` - Num8 float64 `json:"8"` - Num10 float64 `json:"10"` - Num20 float64 `json:"20"` - Num30 float64 `json:"30"` - Num40 float64 `json:"40"` - Num50 float64 `json:"50"` - Num60 float64 `json:"60"` - Num70 float64 `json:"70"` - Num80 float64 `json:"80"` - Num90 float64 `json:"90"` - Num100 float64 `json:"100"` - Num110 float64 `json:"110"` - Num120 float64 `json:"120"` - Num130 float64 `json:"130"` - Num140 float64 `json:"140"` - Num150 float64 `json:"150"` - Num160 float64 `json:"160"` - Num170 float64 `json:"170"` - Num180 float64 `json:"180"` - Num190 float64 `json:"190"` - Num200 float64 `json:"200"` - Num220 float64 `json:"220"` - Num240 float64 `json:"240"` - Num260 float64 `json:"260"` - Num280 float64 `json:"280"` - Num300 float64 `json:"300"` - Num320 float64 `json:"320"` - Num340 float64 `json:"340"` - Num360 float64 `json:"360"` - Num380 float64 `json:"380"` - Num400 float64 `json:"400"` - Num420 float64 `json:"420"` - Num440 float64 `json:"440"` - Num460 float64 `json:"460"` - Num480 float64 `json:"480"` - Num500 float64 `json:"500"` - Num520 float64 `json:"520"` - Num540 float64 `json:"540"` - Num560 float64 `json:"560"` - Num570 float64 `json:"570"` - Num580 float64 `json:"580"` - Num600 float64 `json:"600"` - Num620 float64 `json:"620"` - Num640 float64 `json:"640"` - Num660 float64 `json:"660"` - Num680 float64 `json:"680"` - Num700 float64 `json:"700"` - Num720 float64 `json:"720"` - Num740 float64 `json:"740"` - Num760 float64 `json:"760"` - Num780 float64 `json:"780"` - Num800 float64 `json:"800"` - Num820 float64 `json:"820"` -} diff --git a/transaction/gas/etherscan.go b/transaction/gas/etherscan.go index d547dbe..98dcd25 100644 --- a/transaction/gas/etherscan.go +++ b/transaction/gas/etherscan.go @@ -60,10 +60,16 @@ func (esa *EtherscanStation) GetGasPrices() (*GasPrices, error) { if err != nil { return nil, err } + base, err := strconv.ParseFloat(res.Result.SuggestBaseFee, 64) + if err != nil { + return nil, err + } prices := GasPrices{ SafeLow: esa.result(safeLow), Average: esa.result(average), Fast: esa.result(fast), + + BaseFee: units.FloatGweiToBigIntWei(base), } return &prices, nil } @@ -117,10 +123,11 @@ type etherscanGasPriceResponseFail struct { // gasPriceResult the gas prices for the last block. type gasPriceResult struct { - LastBlock string `json:"LastBlock"` - SafeGasPrice string `json:"SafeGasPrice"` - ProposeGasPrice string `json:"ProposeGasPrice"` + LastBlock string `json:"LastBlock"` + GasUsedRatio string `json:"gasUsedRatio"` + FastGasPrice string `json:"FastGasPrice"` + ProposeGasPrice string `json:"ProposeGasPrice"` + SafeGasPrice string `json:"SafeGasPrice"` SuggestBaseFee string `json:"suggestBaseFee"` - GasUsedRatio string `json:"gasUsedRatio"` } diff --git a/transaction/gas/multichain_station_test.go b/transaction/gas/multichain_station_test.go index d0aac20..bcb68ac 100644 --- a/transaction/gas/multichain_station_test.go +++ b/transaction/gas/multichain_station_test.go @@ -13,7 +13,7 @@ func TestMultichain(t *testing.T) { t.Run("green path", func(t *testing.T) { defaultPrice := big.NewInt(10) mq := MultichainStation{ - 1: []Station{NewStaticStation(defaultPrice), NewStaticStation(big.NewInt(600))}, + 1: []Station{NewStaticStation(defaultPrice, big.NewInt(1)), NewStaticStation(big.NewInt(600), big.NewInt(1))}, } prices, err := mq.GetGasPrices(1) @@ -25,7 +25,7 @@ func TestMultichain(t *testing.T) { t.Run("fallback", func(t *testing.T) { defaultPrice := big.NewInt(10) mq := MultichainStation{ - 1: []Station{NewFailingStationMock(), NewStaticStation(defaultPrice)}, + 1: []Station{NewFailingStationMock(), NewStaticStation(defaultPrice, big.NewInt(1))}, } prices, err := mq.GetGasPrices(1) @@ -37,7 +37,7 @@ func TestMultichain(t *testing.T) { t.Run("chain does not exist", func(t *testing.T) { defaultPrice := big.NewInt(10) mq := MultichainStation{ - 1: []Station{NewFailingStationMock(), NewStaticStation(defaultPrice)}, + 1: []Station{NewFailingStationMock(), NewStaticStation(defaultPrice, big.NewInt(1))}, } prices, err := mq.GetGasPrices(2) diff --git a/transaction/gas/matic.go b/transaction/gas/polygon_official.go similarity index 67% rename from transaction/gas/matic.go rename to transaction/gas/polygon_official.go index e6d0386..13b0c42 100644 --- a/transaction/gas/matic.go +++ b/transaction/gas/polygon_official.go @@ -12,7 +12,7 @@ import ( // DefaultMaticStationURI is the default gas station URL that can be used in matic gas station. // Default URL is for mainnet of matic gas station service. -const DefaultMaticStationURI = "https://gasstation-mainnet.matic.network" +const DefaultMaticStationURI = "https://gasstation-mainnet.matic.network/v2" // MaticStation represents matic gas station api. type MaticStation struct { @@ -22,12 +22,21 @@ type MaticStation struct { } type maticGasPriceResp struct { - SafeLow float64 `json:"safeLow"` - Standard float64 `json:"standard"` - Fast float64 `json:"fast"` - Fastest float64 `json:"fastest"` - BlockTime int `json:"blockTime"` - BlockNumber int `json:"blockNumber"` + BlockNumber int64 `json:"blockNumber"` + BlockTime int64 `json:"blockTime"` + EstimatedBaseFee float64 `json:"estimatedBaseFee"` + Fast struct { + MaxFee float64 `json:"maxFee"` + MaxPriorityFee float64 `json:"maxPriorityFee"` + } `json:"fast"` + SafeLow struct { + MaxFee float64 `json:"maxFee"` + MaxPriorityFee float64 `json:"maxPriorityFee"` + } `json:"safeLow"` + Standard struct { + MaxFee float64 `json:"maxFee"` + MaxPriorityFee float64 `json:"maxPriorityFee"` + } `json:"standard"` } // NewMaticStation returns a new instance of matic gas station which can be used for gas price checks. @@ -47,9 +56,11 @@ func (m *MaticStation) GetGasPrices() (*GasPrices, error) { return nil, err } prices := GasPrices{ - SafeLow: m.result(resp.SafeLow), - Average: m.result(resp.Standard), - Fast: m.result(resp.Fast), + SafeLow: m.result(resp.SafeLow.MaxPriorityFee), + Average: m.result(resp.Standard.MaxPriorityFee), + Fast: m.result(resp.Fast.MaxPriorityFee), + + BaseFee: units.FloatGweiToBigIntWei(resp.EstimatedBaseFee), } return &prices, nil } diff --git a/transaction/gas/polygonscan.go b/transaction/gas/polygonscan.go index 31e89d0..d505833 100644 --- a/transaction/gas/polygonscan.go +++ b/transaction/gas/polygonscan.go @@ -60,10 +60,16 @@ func (esa *PolygonscanStation) GetGasPrices() (*GasPrices, error) { if err != nil { return nil, err } + base, err := strconv.ParseFloat(res.Result.SuggestBaseFee, 64) + if err != nil { + return nil, err + } prices := GasPrices{ SafeLow: esa.result(safeLow), Average: esa.result(average), Fast: esa.result(fast), + + BaseFee: units.FloatGweiToBigIntWei(base), } return &prices, nil } @@ -115,10 +121,13 @@ type polygonscanGasPriceResponse struct { // polygonscanPriceResult the polygonscan prices for the last block. type polygonscanPriceResult struct { - LastBlock string `json:"LastBlock"` - UsdPrice string `json:"UsdPrice"` + LastBlock string `json:"LastBlock"` + SuggestBaseFee string `json:"suggestBaseFee"` FastGasPrice string `json:"FastGasPrice"` ProposeGasPrice string `json:"ProposeGasPrice"` SafeGasPrice string `json:"SafeGasPrice"` + + UsdPrice string `json:"UsdPrice"` + GasUsedRatio string `json:"gasUsedRatio"` } diff --git a/transaction/gas/static.go b/transaction/gas/static.go index 245b0aa..b07e91c 100644 --- a/transaction/gas/static.go +++ b/transaction/gas/static.go @@ -1,21 +1,28 @@ package gas -import "math/big" +import ( + "math/big" +) // StaticStation returns static non changing gas price. type StaticStation struct { - staticPrice *big.Int + tip *big.Int + base *big.Int } -func NewStaticStation(price *big.Int) *StaticStation { - return &StaticStation{staticPrice: price} +func NewStaticStation(tip, base *big.Int) *StaticStation { + return &StaticStation{ + tip: tip, + base: base, + } } func (s *StaticStation) GetGasPrices() (*GasPrices, error) { prices := GasPrices{ - SafeLow: new(big.Int).Set(s.staticPrice), - Average: new(big.Int).Set(s.staticPrice), - Fast: new(big.Int).Set(s.staticPrice), + SafeLow: new(big.Int).Set(s.tip), + Average: new(big.Int).Set(s.tip), + Fast: new(big.Int).Set(s.tip), + BaseFee: s.base, } return &prices, nil } diff --git a/transaction/gas/station.go b/transaction/gas/station.go index f23a4ce..6ca776d 100644 --- a/transaction/gas/station.go +++ b/transaction/gas/station.go @@ -13,6 +13,8 @@ type GasPrices struct { SafeLow *big.Int Average *big.Int Fast *big.Int + + BaseFee *big.Int } func priceMaxUpperBound(price *big.Int, bound *big.Int) *big.Int { diff --git a/transaction/gas_tracker.go b/transaction/gas_tracker.go index 22a0ee5..7d1eaff 100644 --- a/transaction/gas_tracker.go +++ b/transaction/gas_tracker.go @@ -22,6 +22,11 @@ type GasIncreaseOpts struct { IncreaseInterval time.Duration } +type fees struct { + Base *big.Int + Tip *big.Int +} + type GasStation interface { GetGasPrices(chainID int64) (*gas.GasPrices, error) } @@ -37,6 +42,10 @@ const ( var errMaxPriceReached = errors.New("max price reached, cannot increase") func NewGasTracker(gs GasStation, opts map[int64]GasIncreaseOpts, speed GasTrackerSpeed) *GasTracker { + if speed == "" { + speed = GasTrackerSpeedMedium + } + return &GasTracker{ gs: gs, opts: opts, @@ -54,26 +63,32 @@ func (g *GasTracker) CanReceiveMoreGas(chainID int64, lastFillUpUTC time.Time) ( return time.Now().UTC().After(receiveAfter), nil } -func (g *GasTracker) ReceiveInitialGas(chainID int64) (*big.Int, error) { +func (g *GasTracker) ReceiveInitialGas(chainID int64) (*fees, error) { prices, err := g.gs.GetGasPrices(chainID) if err != nil { return nil, err } + fees := &fees{ + Base: prices.BaseFee, + } + switch g.speed { case GasTrackerSpeedSlow: - return prices.SafeLow, nil + fees.Tip = prices.SafeLow case GasTrackerSpeedMedium: - return prices.Average, nil + fees.Tip = prices.Average case GasTrackerSpeedFast: - return prices.Fast, nil + fees.Tip = prices.Fast + default: + return nil, errors.New("gas station not configured") } - return nil, errors.New("gas station not configured") + return fees, nil } -func (g *GasTracker) RecalculateDeliveryGas(chainID int64, lastKnownGas *big.Int) (*big.Int, error) { - if lastKnownGas == nil || lastKnownGas.Cmp(big.NewInt(0)) <= 0 { +func (g *GasTracker) RecalculateDeliveryGas(chainID int64, lastKnownTip *big.Int) (*fees, error) { + if lastKnownTip == nil || lastKnownTip.Cmp(big.NewInt(0)) <= 0 { return g.ReceiveInitialGas(chainID) } @@ -82,31 +97,40 @@ func (g *GasTracker) RecalculateDeliveryGas(chainID int64, lastKnownGas *big.Int return nil, fmt.Errorf("no opts for chain %d", chainID) } - newGasPrice, _ := new(big.Float).Mul( - big.NewFloat(opts.Multiplier), - new(big.Float).SetInt(lastKnownGas), - ).Int(nil) - recalculatedPrice, _ := g.ReceiveInitialGas(chainID) - if recalculatedPrice != nil { - if newGasPrice != nil { - if newGasPrice.Cmp(recalculatedPrice) < 0 { - return recalculatedPrice, nil - } - } else { - return recalculatedPrice, nil - } + newTip := g.calculateNewPrice(chainID, lastKnownTip, opts.Multiplier) + + newFees, err := g.ReceiveInitialGas(chainID) + if err != nil { + return nil, fmt.Errorf("could not recalculate gas price: %w", err) } - if newGasPrice == nil { - return opts.PriceLimit, nil + // If new tip is increased way above, use that. + if newFees.Tip.Cmp(newTip) > 0 || newTip == nil { + newTip = newFees.Tip } - if newGasPrice.Cmp(opts.PriceLimit) > 0 { - if lastKnownGas.Cmp(opts.PriceLimit) < 0 { - return opts.PriceLimit, nil + // Check that new tip is not exceeding our limits. + if newTip.Cmp(opts.PriceLimit) > 0 { + if lastKnownTip.Cmp(opts.PriceLimit) < 0 { + return &fees{ + Base: newFees.Base, + Tip: opts.PriceLimit, + }, nil } return nil, errMaxPriceReached } - return newGasPrice, nil + return &fees{ + Base: newFees.Base, + Tip: newTip, + }, nil +} + +func (g *GasTracker) calculateNewPrice(chainID int64, lastKnownTip *big.Int, multi float64) *big.Int { + newTip, _ := new(big.Float).Mul( + big.NewFloat(multi), + new(big.Float).SetInt(lastKnownTip), + ).Int(nil) + + return newTip }