77 "encoding/hex"
88 "errors"
99 "fmt"
10+ "math/big"
1011 "time"
1112
1213 "go.opentelemetry.io/otel/attribute"
@@ -57,6 +58,8 @@ type TargetStrategy interface {
5758 TransmitReport (ctx context.Context , report []byte , reportContext []byte , signatures [][]byte , request capabilities.CapabilityRequest ) (string , error )
5859 // Wrapper around the ChainWriter to get the transaction status
5960 GetTransactionStatus (ctx context.Context , transactionID string ) (commontypes.TransactionStatus , error )
61+ // Wrapper around the ChainWriter to get the fee esimate
62+ GetEstimateFee (ctx context.Context , contract string , method string , args any , toAddress string , meta * commontypes.TxMeta , val * big.Int ) (commontypes.EstimateFee , error )
6063}
6164
6265var (
@@ -175,6 +178,53 @@ func success() capabilities.CapabilityResponse {
175178 return capabilities.CapabilityResponse {}
176179}
177180
181+ // getGasSpendLimit returns the gas spend limit for the given chain ID from the request metadata
182+ func (c * writeTarget ) getGasSpendLimit (request capabilities.CapabilityRequest ) (string , error ) {
183+ spendType := "GAS." + c .chainInfo .ChainID
184+
185+ for _ , limit := range request .Metadata .SpendLimits {
186+ if spendType == string (limit .SpendType ) {
187+ return limit .Limit , nil
188+ }
189+ }
190+ return "" , fmt .Errorf ("no gas spend limit found for chain %s" , c .chainInfo .ChainID )
191+ }
192+
193+ // checkGasEstimate verifies if the estimated gas fee is within the spend limit and returns the fee
194+ func (c * writeTarget ) checkGasEstimate (ctx context.Context , request capabilities.CapabilityRequest ) (* big.Int , uint32 , error ) {
195+ spendLimit , err := c .getGasSpendLimit (request )
196+ if err != nil {
197+ return nil , 0 , fmt .Errorf ("failed to get gas spend limit, not performing gas estimation: %w" , err )
198+ }
199+
200+ // Get gas estimate from ContractWriter
201+ fee , err := c .targetStrategy .GetEstimateFee (ctx , "" , "" , nil , "" , nil , nil )
202+ if err != nil {
203+ return nil , 0 , fmt .Errorf ("failed to get gas estimate: %w" , err )
204+ }
205+
206+ // Convert spend limit from ETH to wei
207+ limitFloat , ok := new (big.Float ).SetString (spendLimit )
208+ if ! ok {
209+ return nil , 0 , fmt .Errorf ("invalid gas spend limit format: %s" , spendLimit )
210+ }
211+
212+ // Multiply by 10^decimals to convert from ETH to wei
213+ multiplier := new (big.Float ).SetInt (new (big.Int ).Exp (big .NewInt (10 ), big .NewInt (int64 (fee .Decimals )), nil ))
214+ limitFloat .Mul (limitFloat , multiplier )
215+
216+ // Convert to big.Int for comparison
217+ limit := new (big.Int )
218+ limitFloat .Int (limit )
219+
220+ // Compare estimate with limit
221+ if fee .Fee .Cmp (limit ) > 0 {
222+ return nil , 0 , fmt .Errorf ("estimated gas fee %s exceeds spend limit %s" , fee .Fee .String (), limit .String ())
223+ }
224+
225+ return fee .Fee , fee .Decimals , nil
226+ }
227+
178228func (c * writeTarget ) Execute (ctx context.Context , request capabilities.CapabilityRequest ) (capabilities.CapabilityResponse , error ) {
179229 // Take the local timestamp
180230 tsStart := time .Now ().UnixMilli ()
@@ -189,6 +239,23 @@ func (c *writeTarget) Execute(ctx context.Context, request capabilities.Capabili
189239
190240 c .lggr .Debugw ("Execute" , "request" , request , "capInfo" , capInfo )
191241
242+ // Check gas estimate before proceeding
243+ fee , _ , err := c .checkGasEstimate (ctx , request )
244+ if err != nil {
245+ // Build error message
246+ info := & requestInfo {
247+ tsStart : tsStart ,
248+ node : c .nodeAddress ,
249+ request : request ,
250+ }
251+ errMsg := c .asEmittedError (ctx , & wt.WriteError {
252+ Code : uint32 (TransmissionStateFatal ),
253+ Summary : "InsufficientFunds" ,
254+ Cause : err .Error (),
255+ }, "info" , info )
256+ return capabilities.CapabilityResponse {}, errMsg
257+ }
258+
192259 // Helper to keep track of the request info
193260 info := requestInfo {
194261 tsStart : tsStart ,
@@ -342,7 +409,18 @@ func (c *writeTarget) Execute(ctx context.Context, request capabilities.Capabili
342409 if err != nil {
343410 return capabilities.CapabilityResponse {}, err
344411 }
345- return success (), nil
412+
413+ return capabilities.CapabilityResponse {
414+ Metadata : capabilities.ResponseMetadata {
415+ Metering : []capabilities.MeteringNodeDetail {
416+ {
417+ Peer2PeerID : c .nodeAddress ,
418+ SpendUnit : "GAS" ,
419+ SpendValue : fee .String (),
420+ },
421+ },
422+ },
423+ }, nil
346424}
347425
348426func (c * writeTarget ) RegisterToWorkflow (ctx context.Context , request capabilities.RegisterToWorkflowRequest ) error {
0 commit comments