diff --git a/chain_capabilities/aptos/actions/write_report.go b/chain_capabilities/aptos/actions/write_report.go index eda24c236..7357e43e7 100644 --- a/chain_capabilities/aptos/actions/write_report.go +++ b/chain_capabilities/aptos/actions/write_report.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "math" - "math/big" "slices" "strings" "time" @@ -38,11 +37,6 @@ import ( // Set to 2x with margin for gas schedule changes. const ForwarderGasOverhead uint64 = 4000 -// aptosOctasToAPT converts a fee in octas (10^-8 APT) to a *big.Float in APT. -func aptosOctasToAPT(octas uint64) *big.Float { - return new(big.Float).Quo(new(big.Float).SetUint64(octas), big.NewFloat(1e8)) -} - // WriteReport validates and submits a signed report to the Aptos chain via the CRE forwarder. func (s *Aptos) WriteReport( ctx context.Context, @@ -231,14 +225,13 @@ func (wr *writeReport) execute( ownLedgerVersion, ownFeeInOctas, ownVMStatus, ownBlockTimestamp, feeErr := wr.getTxnInfoFromChain(ctx, txReply.TxHash) if feeErr != nil { wr.lggr.Errorw("Failed to get transaction fee, using zero for metering", "txHash", txReply.TxHash, "error", feeErr) - ownMeteringMetadata = metering.GetResponseMetadataWriteReport(big.NewFloat(0), wr.chainSelector) + ownMeteringMetadata = metering.GetResponseMetadataWriteReport(0, wr.chainSelector) monitoring.LogAndEmitError(ctx, wr.lggr, wr.beholderProcessor, wr.messageBuilder.BuildWriteReportTxFeeCalculationError(telemetryContext, request, txReply.TxHash, feeErr.Error())) } else { pinnedLedgerVersion = &ownLedgerVersion - feeInAPT := aptosOctasToAPT(ownFeeInOctas) - ownMeteringMetadata = metering.GetResponseMetadataWriteReport(feeInAPT, wr.chainSelector) - wr.lggr.Debugw("WriteReport fee", "feeInAPT", feeInAPT.String(), "feeInOctas", ownFeeInOctas, "ledgerVersion", ownLedgerVersion) + ownMeteringMetadata = metering.GetResponseMetadataWriteReport(ownFeeInOctas, wr.chainSelector) + wr.lggr.Debugw("WriteReport fee", "feeInOctas", ownFeeInOctas, "ledgerVersion", ownLedgerVersion) } // Measured to the transaction's on-chain block timestamp rather than to when the submit call @@ -284,9 +277,8 @@ func (wr *writeReport) execute( return nil, capabilities.ResponseMetadata{}, fmt.Errorf("failed to get successful transmission hash: %w", txHashErr) } feeInOctas := successResult.GasUsed * successResult.GasUnitPrice - feeInAPT := aptosOctasToAPT(feeInOctas) - meteringMetadata := metering.GetResponseMetadataWriteReport(feeInAPT, wr.chainSelector) - wr.lggr.Debugw("WriteReport fee", "feeInAPT", feeInAPT.String(), "feeInOctas", feeInOctas) + meteringMetadata := metering.GetResponseMetadataWriteReport(feeInOctas, wr.chainSelector) + wr.lggr.Debugw("WriteReport fee", "feeInOctas", feeInOctas) monitoring.LogAndEmitSuccess(ctx, "WriteReport sent a duplicate transaction, report already on-chain", wr.lggr, wr.beholderProcessor, @@ -294,7 +286,7 @@ func (wr *writeReport) execute( if txReply.TxStatus == aptostypes.TxFatal { wr.lggr.Errorw("Transaction failed to get processed, but report was already submitted, this is unexpected and should be investigated", "txHash", txReply.TxHash) - meteringMetadata = metering.GetResponseMetadataWriteReport(big.NewFloat(0), wr.chainSelector) + meteringMetadata = metering.GetResponseMetadataWriteReport(0, wr.chainSelector) } return &aptoscap.WriteReportReply{ @@ -362,8 +354,7 @@ func (wr *writeReport) execute( // charge the user for the failed txs if it was reverted on user contract var replyMeta capabilities.ResponseMetadata if recvStatus != nil && *recvStatus == aptoscap.ReceiverContractExecutionStatus_RECEIVER_CONTRACT_EXECUTION_STATUS_REVERTED { - feeInAPT := aptosOctasToAPT(feeOctas) - replyMeta = metering.GetResponseMetadataWriteReport(feeInAPT, wr.chainSelector) + replyMeta = metering.GetResponseMetadataWriteReport(feeOctas, wr.chainSelector) } return &aptoscap.WriteReportReply{ TxStatus: aptoscap.TxStatus_TX_STATUS_FATAL, diff --git a/chain_capabilities/aptos/go.mod b/chain_capabilities/aptos/go.mod index 272e06715..11d265b55 100644 --- a/chain_capabilities/aptos/go.mod +++ b/chain_capabilities/aptos/go.mod @@ -8,7 +8,7 @@ require ( github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 github.com/smartcontractkit/chain-selectors v1.0.104 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260506112908-f0d993b5bd6d - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 ) require ( @@ -20,6 +20,7 @@ require ( github.com/jonboulle/clockwork v0.5.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect github.com/stretchr/objx v0.5.2 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect go.uber.org/goleak v1.3.0 // indirect @@ -90,8 +91,8 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260713185857-30ad2e76c0f4 - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 // indirect + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e // indirect diff --git a/chain_capabilities/aptos/go.sum b/chain_capabilities/aptos/go.sum index 7c33ef01d..333be5ec2 100644 --- a/chain_capabilities/aptos/go.sum +++ b/chain_capabilities/aptos/go.sum @@ -243,16 +243,18 @@ github.com/smartcontractkit/chain-selectors v1.0.104 h1:/n9pPGM5W/+r1eHoWZv4VwX9 github.com/smartcontractkit/chain-selectors v1.0.104/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260506112908-f0d993b5bd6d h1:KMZmEzlZ+iknVFCuv+8o9iEKK52HXyeauIWbo+KhYv8= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260506112908-f0d993b5bd6d/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260713185857-30ad2e76c0f4 h1:KSg0EnUdefIGyR3Fa6/nXhWXaCUlMP5qFAsLHwzi6Fk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260713185857-30ad2e76c0f4/go.mod h1:snfVBRRQTpC2x5O3bQHZe9SvJX5yv/SbG8oHkJTKLtE= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa h1:rAict9GzIskugu/EdmCoBX1dI+DJWqZ9NlIX1BiTa2s= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa/go.mod h1:BJ/X2jB9qPYb835qXtNGpYoq/yiO39FzehhpwGTzJFw= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72/go.mod h1:UYcRMb4dZcoaIPgZJ3hckCySTqtJc9K4Q+tOKErwTq0= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 h1:oW88YVT5ENU6rCPVOLV/hofmFBux2Mu1EOwz8KJu5ic= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= +github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= +github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260528173149-f5b8336b19d9 h1:LQy2j2+TdKLSWsUTUYuqmQPn8kjqCLjGI3ZJYGtDc08= diff --git a/chain_capabilities/aptos/metering/metering.go b/chain_capabilities/aptos/metering/metering.go index f48e2c611..c25d3fa7a 100644 --- a/chain_capabilities/aptos/metering/metering.go +++ b/chain_capabilities/aptos/metering/metering.go @@ -3,18 +3,24 @@ package metering import ( "fmt" "math/big" + "strconv" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" ) var WriteReportSpendUnitFormat = "GAS.%d" // %d will be replaced with the chain selector -func GetResponseMetadataWriteReport(fee *big.Float, chainSelector uint64) capabilities.ResponseMetadata { +// GetResponseMetadataWriteReport returns billing ResponseMetadata for a completed write-report +// submission. feeInOctas is the transaction fee in octas (native fixed-point integer). +// The legacy SpendValue (in APT) is derived from feeInOctas for backwards compatibility. +func GetResponseMetadataWriteReport(feeInOctas uint64, chainSelector uint64) capabilities.ResponseMetadata { + feeInAPT := new(big.Float).Quo(new(big.Float).SetUint64(feeInOctas), big.NewFloat(1e8)) return capabilities.ResponseMetadata{ Metering: []capabilities.MeteringNodeDetail{ { - SpendValue: fee.Text('f', -1), // stored in APT - SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), + SpendValue: feeInAPT.Text('f', -1), + SpendValueInGasUnits: strconv.FormatUint(feeInOctas, 10), + SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), }, }, } diff --git a/chain_capabilities/aptos/metering/metering_test.go b/chain_capabilities/aptos/metering/metering_test.go index 1f9b4f72c..4f8018c69 100644 --- a/chain_capabilities/aptos/metering/metering_test.go +++ b/chain_capabilities/aptos/metering/metering_test.go @@ -2,6 +2,7 @@ package metering import ( "math/big" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -11,35 +12,35 @@ import ( func TestGetResponseMetadataWriteReport(t *testing.T) { tests := []struct { name string - fee *big.Float + feeInOctas uint64 chainSelector uint64 expectedSpendUnit string expectedValue string }{ { name: "Standard Aptos fee (500 octas = 0.000005 APT)", - fee: new(big.Float).Quo(new(big.Float).SetUint64(500), big.NewFloat(1e8)), + feeInOctas: 500, chainSelector: 1, expectedSpendUnit: "GAS.1", expectedValue: "0.000005", }, { name: "Large fee (1 APT)", - fee: new(big.Float).SetFloat64(1.0), + feeInOctas: 100_000_000, chainSelector: 42, expectedSpendUnit: "GAS.42", expectedValue: "1", }, { name: "Zero fee", - fee: new(big.Float).SetFloat64(0), + feeInOctas: 0, chainSelector: 100, expectedSpendUnit: "GAS.100", expectedValue: "0", }, { name: "Typical fee (gasUsed=500 * gasUnitPrice=100 = 50000 octas = 0.0005 APT)", - fee: new(big.Float).Quo(new(big.Float).SetUint64(50_000), big.NewFloat(1e8)), + feeInOctas: 50_000, chainSelector: 1, expectedSpendUnit: "GAS.1", expectedValue: "0.0005", @@ -48,11 +49,17 @@ func TestGetResponseMetadataWriteReport(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result := GetResponseMetadataWriteReport(test.fee, test.chainSelector) + result := GetResponseMetadataWriteReport(test.feeInOctas, test.chainSelector) require.Len(t, result.Metering, 1) assert.Equal(t, test.expectedSpendUnit, result.Metering[0].SpendUnit) assert.Equal(t, test.expectedValue, result.Metering[0].SpendValue) + assert.Equal(t, strconv.FormatUint(test.feeInOctas, 10), result.Metering[0].SpendValueInGasUnits) assert.Empty(t, result.Metering[0].Peer2PeerID, "Peer2PeerID should be empty") }) } } + +func TestAptosOctasToAPT(t *testing.T) { + assert.Equal(t, "0.000005", new(big.Float).Quo(new(big.Float).SetUint64(500), big.NewFloat(1e8)).Text('f', -1)) + assert.Equal(t, "1", new(big.Float).Quo(new(big.Float).SetUint64(100_000_000), big.NewFloat(1e8)).Text('f', -1)) +} diff --git a/chain_capabilities/evm/actions/write_report.go b/chain_capabilities/evm/actions/write_report.go index 2bba7b204..94f260c75 100644 --- a/chain_capabilities/evm/actions/write_report.go +++ b/chain_capabilities/evm/actions/write_report.go @@ -103,7 +103,7 @@ func (e *EVM) executeWriteReport(ctx context.Context, request *evm.WriteReportRe return wr.executeWriteReport(ctx, request, metadata, telemetryContext) } -func (e *WriteReport) getFee(ctx context.Context, txIdempotencyKey string) (*big.Float, error) { +func (e *WriteReport) getFee(ctx context.Context, txIdempotencyKey string) (*big.Int, error) { if txIdempotencyKey == "" { return nil, fmt.Errorf("txIdempotencyKey is empty, cannot retrieve transaction fee") } @@ -114,7 +114,7 @@ func (e *WriteReport) getFee(ctx context.Context, txIdempotencyKey string) (*big } feeInEth := new(big.Float).Quo(new(big.Float).SetInt(feeInWei.TransactionFee), big.NewFloat(1e18)) e.lggr.Debugw("WriteReport fee", "feeInEth", feeInEth.String(), "feeInWei", feeInWei.TransactionFee.String()) - return feeInEth, nil + return feeInWei.TransactionFee, nil } func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.WriteReportRequest, metadata capabilities.RequestMetadata, telemetryContext monitoring.TelemetryContext) (*evm.WriteReportReply, capabilities.ResponseMetadata, error) { @@ -243,11 +243,11 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write e.lggr.Infow("Got final transmission status", newTransmissionInfo.LogAttrs()...) var meteringMetadata capabilities.ResponseMetadata - transactionFee, err := e.getFee(ctx, transactionResult.TxIdempotencyKey) + feeInWei, err := e.getFee(ctx, transactionResult.TxIdempotencyKey) if err != nil { monitoring.LogAndEmitError(ctx, e.lggr, e.beholderProcessor, e.messageBuilder.BuildWriteReportTxFeeCalculationError(telemetryContext, request, transactionResult.TxIdempotencyKey, err.Error())) } else { - meteringMetadata = metering.GetResponseMetadataWriteReport(transactionFee, e.chainSelector) + meteringMetadata = metering.GetResponseMetadataWriteReport(feeInWei, e.chainSelector) } switch newTransmissionInfo.State { diff --git a/chain_capabilities/evm/actions/write_report_test.go b/chain_capabilities/evm/actions/write_report_test.go index 362c562a6..e77ca0a5d 100644 --- a/chain_capabilities/evm/actions/write_report_test.go +++ b/chain_capabilities/evm/actions/write_report_test.go @@ -1021,7 +1021,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { }, txResult.Response) // Retried tx => should be metered. - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") }) t.Run("TX first transmission - Successful TX execution (ensures non-nil + positive gas config passed to forwarder)", func(t *testing.T) { @@ -1093,7 +1093,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { TransactionFee: pb.NewBigIntFromInt(big.NewInt(retryTxFee)), }, txResult.Response) - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") }) t.Run("TX first transmission - Error submitting TX (ensures gas config passed is non-nil + positive)", func(t *testing.T) { @@ -1193,7 +1193,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { TransactionFee: pb.NewBigIntFromInt(big.NewInt(2000)), }, txResult.Response) - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") }) t.Run("TX first transmission - Duplicate tx: txmgr reports reverted but transmission succeeded => use onchain tx hash", func(t *testing.T) { @@ -1271,7 +1271,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { TransactionFee: pb.NewBigIntFromInt(big.NewInt(txFee)), }, txResult.Response) - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") }) t.Run("TX first transmission - Fatal tx but transmission succeeded => use onchain tx hash", func(t *testing.T) { @@ -1344,7 +1344,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { TransactionFee: pb.NewBigIntFromInt(big.NewInt(txFee)), }, txResult.Response) - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") }) t.Run("TX first transmission - Fatal tx and GetSuccessfulTransmissionHash fails => returns error", func(t *testing.T) { @@ -1518,7 +1518,7 @@ func TestWriteReport_ExecuteWriteReport(t *testing.T) { require.Equal(t, receiptTxHash[:], txResult.Response.TxHash) require.NotNil(t, txResult.Response.ReceiverContractExecutionStatus) require.Equal(t, evm.ReceiverContractExecutionStatus_RECEIVER_CONTRACT_EXECUTION_STATUS_REVERTED.Enum(), txResult.Response.ReceiverContractExecutionStatus.Enum()) - evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, txResult.ResponseMetadata, 1, "0.0000000000000003", "300") if queuePosition == 0 { mockForwarderClient.AssertNotCalled(t, "GetReportProcessedEvents", mock.Anything, mock.Anything, mock.Anything, mock.Anything) @@ -2253,7 +2253,7 @@ func TestWriteReport_RevertReceiptFetchFailsReturnsUserError(t *testing.T) { assertMetadata: func(t *testing.T, metadata capabilities.ResponseMetadata) { t.Helper() - evmtest.ValidateMeteringWriteReport(t, metadata, 1, "0.0000000000000003") + evmtest.ValidateMeteringWriteReport(t, metadata, 1, "0.0000000000000003", "300") }, }, } diff --git a/chain_capabilities/evm/go.mod b/chain_capabilities/evm/go.mod index 12628bd08..b9152e8d7 100644 --- a/chain_capabilities/evm/go.mod +++ b/chain_capabilities/evm/go.mod @@ -8,11 +8,11 @@ require ( github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260729165045-b00a39493e13 github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 github.com/smartcontractkit/chain-selectors v1.0.106 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260714130758-475cb096a3e1 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260410162948-2dca02f24e98 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1 github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.43.0 go.uber.org/zap v1.27.1 @@ -90,10 +90,11 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/smartcontractkit/chainlink-common/keystore v1.1.1-0.20260529092756-a94bc8ce96d6 // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 // indirect github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 // indirect github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect + github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260528173149-f5b8336b19d9 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/chain_capabilities/evm/go.sum b/chain_capabilities/evm/go.sum index cdb7ab7be..cfca37864 100644 --- a/chain_capabilities/evm/go.sum +++ b/chain_capabilities/evm/go.sum @@ -18,8 +18,8 @@ github.com/XSAM/otelsql v0.37.0 h1:ya5RNw028JW0eJW8Ma4AmoKxAYsJSGuNVbC7F1J457A= github.com/XSAM/otelsql v0.37.0/go.mod h1:LHbCu49iU8p255nCn1oi04oX2UjSoRcUMiKEHo2a5qM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM= +github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/apache/arrow-go/v18 v18.3.1 h1:oYZT8FqONiK74JhlH3WKVv+2NKYoyZ7C2ioD4Dj3ixk= github.com/apache/arrow-go/v18 v18.3.1/go.mod h1:12QBya5JZT6PnBihi5NJTzbACrDGXYkrgjujz3MRQXU= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= @@ -470,12 +470,12 @@ github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64/go.mod h1:DZkOtlIRV6ecpCskdXMF+k2es3uJQqTT1m5TRB9etjQ= github.com/smartcontractkit/chain-selectors v1.0.106 h1:6Mz7FDooWJEcfvuOX3nH7oh5s44fqg/WqVDQS63Mjzs= github.com/smartcontractkit/chain-selectors v1.0.106/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260714130758-475cb096a3e1 h1:qxSP9Rlaq9hxyLsDUdGj21MdHLhiHF7HhEtwaHSvWcw= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260714130758-475cb096a3e1/go.mod h1:snfVBRRQTpC2x5O3bQHZe9SvJX5yv/SbG8oHkJTKLtE= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa h1:rAict9GzIskugu/EdmCoBX1dI+DJWqZ9NlIX1BiTa2s= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa/go.mod h1:BJ/X2jB9qPYb835qXtNGpYoq/yiO39FzehhpwGTzJFw= github.com/smartcontractkit/chainlink-common/keystore v1.1.1-0.20260529092756-a94bc8ce96d6 h1:fWsYxxj35fp1/6YZngoTsOTMLqDie4N5X0osAOdhUTE= github.com/smartcontractkit/chainlink-common/keystore v1.1.1-0.20260529092756-a94bc8ce96d6/go.mod h1:6JexOOhPhknQ0QMuppFIlOpm6wCp54yZMxai+tWugwY= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72/go.mod h1:UYcRMb4dZcoaIPgZJ3hckCySTqtJc9K4Q+tOKErwTq0= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260410162948-2dca02f24e98 h1:h/L6wrXYLQalI/vHm6qg/KBv6d7kMb3geMHV5hCM1t4= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260410162948-2dca02f24e98/go.mod h1:6vCMfxz7cMW0wWseNKtct+b1JJbbRVJJhh/t6pQWN3M= github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1 h1:NTODgwAil7BLoijS7y6KnEuNbQ9v60VUhIR9FcAzIhg= @@ -488,10 +488,12 @@ github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512- github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 h1:oW88YVT5ENU6rCPVOLV/hofmFBux2Mu1EOwz8KJu5ic= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= +github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= +github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe/go.mod h1:z7lx7wI3XZ4u9kmUtAVdwn1BCC9T8aieWSDcuDgPTdQ= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 h1:/vkKPJoweLkRd56V4YHGRAtTG4+/JAlgklGEfvH6l4c= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260528173149-f5b8336b19d9 h1:LQy2j2+TdKLSWsUTUYuqmQPn8kjqCLjGI3ZJYGtDc08= diff --git a/chain_capabilities/evm/metering/metering.go b/chain_capabilities/evm/metering/metering.go index 77aea3949..8b3132fe4 100644 --- a/chain_capabilities/evm/metering/metering.go +++ b/chain_capabilities/evm/metering/metering.go @@ -39,14 +39,18 @@ func GetResponseMetadata(action SpendValueCredits) capabilities.ResponseMetadata } } -// GetMeteringNodeDetail returns a MeteringNodeDetail for a given SpendValueCredits. -func GetResponseMetadataWriteReport(fee *big.Float, chainSelector uint64) capabilities.ResponseMetadata { +// GetResponseMetadataWriteReport returns billing ResponseMetadata for a completed write-report +// submission. feeInWei is the transaction fee in wei (native fixed-point integer). +// The legacy SpendValue (in ETH) is derived from feeInWei for backwards compatibility. +func GetResponseMetadataWriteReport(feeInWei *big.Int, chainSelector uint64) capabilities.ResponseMetadata { + feeInEth := new(big.Float).Quo(new(big.Float).SetInt(feeInWei), big.NewFloat(1e18)) return capabilities.ResponseMetadata{ Metering: []capabilities.MeteringNodeDetail{ { //Peer2PeerID will be assigned by the engine, leaving it empty here. - SpendValue: fee.Text('f', -1), // have to be stored in eth - SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), + SpendValue: feeInEth.Text('f', -1), + SpendValueInGasUnits: feeInWei.String(), + SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), }, }, } diff --git a/chain_capabilities/evm/test/test_utils.go b/chain_capabilities/evm/test/test_utils.go index 117fcf416..1be71de8f 100644 --- a/chain_capabilities/evm/test/test_utils.go +++ b/chain_capabilities/evm/test/test_utils.go @@ -19,11 +19,12 @@ func ValidateMetering(t *testing.T, metadata capabilities.ResponseMetadata, expe require.Empty(t, meteringNodeDetail.Peer2PeerID, "Peer2PeerID should be empty as it will be assigned by the engine") } -func ValidateMeteringWriteReport(t *testing.T, metadata capabilities.ResponseMetadata, chainSelector int, expectedValue string) { +func ValidateMeteringWriteReport(t *testing.T, metadata capabilities.ResponseMetadata, chainSelector int, expectedValue string, expectedValueInGasUnits string) { require.Len(t, metadata.Metering, 1) meteringNodeDetail := metadata.Metering[0] require.Equal(t, fmt.Sprintf(metering.WriteReportSpendUnitFormat, chainSelector), meteringNodeDetail.SpendUnit) require.Equal(t, expectedValue, meteringNodeDetail.SpendValue) + require.Equal(t, expectedValueInGasUnits, meteringNodeDetail.SpendValueInGasUnits) require.Empty(t, meteringNodeDetail.Peer2PeerID, "Peer2PeerID should be empty as it will be assigned by the engine") } diff --git a/chain_capabilities/solana/actions/write_report.go b/chain_capabilities/solana/actions/write_report.go index b90aa5cc4..f47ef903c 100644 --- a/chain_capabilities/solana/actions/write_report.go +++ b/chain_capabilities/solana/actions/write_report.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "errors" "fmt" - "math/big" "strings" "time" @@ -224,12 +223,11 @@ func (wr *WriteReport) executeWriteReport( } var meteringMetadata capabilities.ResponseMetadata - transactionFee, err := wr.getFee(ctx, last.Signature) + feeInLamports, err := wr.getFee(ctx, last.Signature) if err != nil { monitoring.LogAndEmitError(ctx, wr.lggr, wr.beholderProcessor, wr.messageBuilder.BuildWriteReportTxFeeCalculationError(telemetryContext, request, last.Signature, err.Error())) } else { - meteringMetadata = metering.GetResponseMetadataWriteReport(transactionFee, - wr.chainSelector) + meteringMetadata = metering.GetResponseMetadataWriteReport(feeInLamports, wr.chainSelector) } switch last.State { @@ -451,22 +449,22 @@ func (wr *WriteReport) pollTransmissionInfo( } } -func (wr *WriteReport) getFee(ctx context.Context, sig solana.Signature) (*big.Float, error) { +func (wr *WriteReport) getFee(ctx context.Context, sig solana.Signature) (uint64, error) { tx, err := wr.GetTransaction(ctx, soltypes.GetTransactionRequest{Signature: soltypes.Signature(sig)}) if err != nil { - return nil, fmt.Errorf("failed to get transaction: %w", err) + return 0, fmt.Errorf("failed to get transaction: %w", err) } if tx == nil { - return nil, errors.New("failed to get transaction fee: empty transaction response") + return 0, errors.New("failed to get transaction fee: empty transaction response") } if tx.Meta == nil { - return nil, errors.New("failed to get transaction fee: empty transaction meta") + return 0, errors.New("failed to get transaction fee: empty transaction meta") } - feeInSol := new(big.Float).Quo(new(big.Float).SetUint64(tx.Meta.Fee), big.NewFloat(1e9)) + feeInLamports := tx.Meta.Fee - wr.lggr.Debugw("WriteReport fee", "feeInSol", feeInSol.String(), "feeInLamports", tx.Meta.Fee) - return feeInSol, nil + wr.lggr.Debugw("WriteReport fee", "feeInLamports", feeInLamports) + return feeInLamports, nil } func (wr *WriteReport) successWriteReportReply(sig *solana.Signature) *solcap.WriteReportReply { diff --git a/chain_capabilities/solana/actions/write_report_test.go b/chain_capabilities/solana/actions/write_report_test.go index 59a6a900a..be933d729 100644 --- a/chain_capabilities/solana/actions/write_report_test.go +++ b/chain_capabilities/solana/actions/write_report_test.go @@ -787,9 +787,9 @@ func TestGetFee(t *testing.T) { Meta: &soltypes.TransactionMeta{Fee: txFeeInLamports}, }, nil) - fee, err := wr.getFee(t.Context(), sig) + feeInLamports, err := wr.getFee(t.Context(), sig) require.NoError(t, err) - require.Equal(t, "0.000005", fee.Text('f', -1)) + require.Equal(t, txFeeInLamports, feeInLamports) }) t.Run("Handles large fee values", func(t *testing.T) { @@ -807,9 +807,9 @@ func TestGetFee(t *testing.T) { Meta: &soltypes.TransactionMeta{Fee: txFeeInLamports}, }, nil) - fee, err := wr.getFee(t.Context(), sig) + feeInLamports, err := wr.getFee(t.Context(), sig) require.NoError(t, err) - require.Equal(t, "1", fee.Text('f', -1)) + require.Equal(t, txFeeInLamports, feeInLamports) }) t.Run("Returns error when GetTransaction fails", func(t *testing.T) { diff --git a/chain_capabilities/solana/go.mod b/chain_capabilities/solana/go.mod index 37cef8057..087b4d656 100644 --- a/chain_capabilities/solana/go.mod +++ b/chain_capabilities/solana/go.mod @@ -9,9 +9,9 @@ require ( github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260729165045-b00a39493e13 github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 github.com/smartcontractkit/chain-selectors v1.0.104 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260803161028-985f5a5a03d9 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260803170030-90043607d911 github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260803170030-90043607d911 github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 diff --git a/chain_capabilities/solana/go.sum b/chain_capabilities/solana/go.sum index 6eeb0ad5b..2e6537b7e 100644 --- a/chain_capabilities/solana/go.sum +++ b/chain_capabilities/solana/go.sum @@ -56,8 +56,8 @@ github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkK github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM= +github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/apache/arrow-go/v18 v18.3.1 h1:oYZT8FqONiK74JhlH3WKVv+2NKYoyZ7C2ioD4Dj3ixk= github.com/apache/arrow-go/v18 v18.3.1/go.mod h1:12QBya5JZT6PnBihi5NJTzbACrDGXYkrgjujz3MRQXU= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= @@ -629,8 +629,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288/go.mod h1:jPHlo/IN2YAArI001JJixmm6ZHQwgnAVJXY8VBFiFTc= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 h1:eEjTgIQn4RW0ZPRepUDYTdgGwaRCMawMwgXkHItUc9U= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260803161028-985f5a5a03d9 h1:oyBg6nkl1SDDUB+Z1GNYKTNtepabGkq7pudbyJHgtM8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260803161028-985f5a5a03d9/go.mod h1:euYQ2WxVYGU2ivHXPBRpbU0Z3/SkekfIK5YPf7hdDY8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa h1:rAict9GzIskugu/EdmCoBX1dI+DJWqZ9NlIX1BiTa2s= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa/go.mod h1:BJ/X2jB9qPYb835qXtNGpYoq/yiO39FzehhpwGTzJFw= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72/go.mod h1:UYcRMb4dZcoaIPgZJ3hckCySTqtJc9K4Q+tOKErwTq0= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk= @@ -641,8 +641,8 @@ github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805- github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 h1:oW88YVT5ENU6rCPVOLV/hofmFBux2Mu1EOwz8KJu5ic= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= diff --git a/chain_capabilities/solana/metering/metering.go b/chain_capabilities/solana/metering/metering.go index dfabe19a3..a9bd3d42c 100644 --- a/chain_capabilities/solana/metering/metering.go +++ b/chain_capabilities/solana/metering/metering.go @@ -3,6 +3,7 @@ package metering import ( "fmt" "math/big" + "strconv" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" ) @@ -16,14 +17,18 @@ const ( var WriteReportSpendUnitFormat = "GAS.%d" // %d will be replaced with the chain selector -// GetMeteringNodeDetail returns a MeteringNodeDetail for a given SpendValueCredits. -func GetResponseMetadataWriteReport(fee *big.Float, chainSelector uint64) capabilities.ResponseMetadata { +// GetResponseMetadataWriteReport returns billing ResponseMetadata for a completed write-report +// submission. feeInLamports is the transaction fee in lamports (native fixed-point integer). +// The legacy SpendValue (in SOL) is derived from feeInLamports for backwards compatibility. +func GetResponseMetadataWriteReport(feeInLamports uint64, chainSelector uint64) capabilities.ResponseMetadata { + feeInSol := new(big.Float).Quo(new(big.Float).SetUint64(feeInLamports), big.NewFloat(1e9)) return capabilities.ResponseMetadata{ Metering: []capabilities.MeteringNodeDetail{ { //Peer2PeerID will be assigned by the engine, leaving it empty here. - SpendValue: fee.Text('f', -1), // have to be stored in SOL - SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), + SpendValue: feeInSol.Text('f', -1), + SpendValueInGasUnits: strconv.FormatUint(feeInLamports, 10), + SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), }, }, } diff --git a/chain_capabilities/solana/metering/metering_test.go b/chain_capabilities/solana/metering/metering_test.go index 8dc4cfbca..34903ef41 100644 --- a/chain_capabilities/solana/metering/metering_test.go +++ b/chain_capabilities/solana/metering/metering_test.go @@ -2,6 +2,7 @@ package metering import ( "math/big" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -11,35 +12,35 @@ import ( func TestGetResponseMetadataWriteReport(t *testing.T) { tests := []struct { name string - fee *big.Float + feeInLamports uint64 chainSelector uint64 expectedSpendUnit string expectedValue string }{ { name: "Standard Solana fee (5000 lamports)", - fee: new(big.Float).Quo(new(big.Float).SetUint64(5000), big.NewFloat(1e9)), + feeInLamports: 5000, chainSelector: 1, expectedSpendUnit: "GAS.1", expectedValue: "0.000005", }, { name: "Large fee (1 SOL)", - fee: new(big.Float).SetFloat64(1.0), + feeInLamports: 1_000_000_000, chainSelector: 42, expectedSpendUnit: "GAS.42", expectedValue: "1", }, { name: "Zero fee", - fee: new(big.Float).SetFloat64(0), + feeInLamports: 0, chainSelector: 100, expectedSpendUnit: "GAS.100", expectedValue: "0", }, { name: "Sub-lamport precision fee", - fee: new(big.Float).Quo(new(big.Float).SetUint64(1), big.NewFloat(1e9)), + feeInLamports: 1, chainSelector: 1, expectedSpendUnit: "GAS.1", expectedValue: "0.000000001", @@ -48,11 +49,17 @@ func TestGetResponseMetadataWriteReport(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result := GetResponseMetadataWriteReport(test.fee, test.chainSelector) + result := GetResponseMetadataWriteReport(test.feeInLamports, test.chainSelector) require.Len(t, result.Metering, 1) assert.Equal(t, test.expectedSpendUnit, result.Metering[0].SpendUnit) assert.Equal(t, test.expectedValue, result.Metering[0].SpendValue) + assert.Equal(t, strconv.FormatUint(test.feeInLamports, 10), result.Metering[0].SpendValueInGasUnits) assert.Empty(t, result.Metering[0].Peer2PeerID, "Peer2PeerID should be empty") }) } } + +func TestSolanaLamportsToSol(t *testing.T) { + assert.Equal(t, "0.000005", new(big.Float).Quo(new(big.Float).SetUint64(5000), big.NewFloat(1e9)).Text('f', -1)) + assert.Equal(t, "1", new(big.Float).Quo(new(big.Float).SetUint64(1_000_000_000), big.NewFloat(1e9)).Text('f', -1)) +} diff --git a/chain_capabilities/stellar/actions/write_report_test.go b/chain_capabilities/stellar/actions/write_report_test.go index 908960130..4c9ed3f3f 100644 --- a/chain_capabilities/stellar/actions/write_report_test.go +++ b/chain_capabilities/stellar/actions/write_report_test.go @@ -297,6 +297,7 @@ func validateWRMetering(t *testing.T, meta capabilities.ResponseMetadata, chainS m := meta.Metering[0] require.Equal(t, fmt.Sprintf(metering.WriteReportSpendUnitFormat, chainSelector), m.SpendUnit) require.Equal(t, fmt.Sprintf("%d", expectedStroops), m.SpendValue) + require.Equal(t, fmt.Sprintf("%d", expectedStroops), m.SpendValueInGasUnits) require.Empty(t, m.Peer2PeerID) } diff --git a/chain_capabilities/stellar/go.mod b/chain_capabilities/stellar/go.mod index 3b64b08ba..655b32e25 100644 --- a/chain_capabilities/stellar/go.mod +++ b/chain_capabilities/stellar/go.mod @@ -4,7 +4,7 @@ go 1.26.4 require ( github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260717225314-5fc9656c1b02 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa github.com/stellar/go-stellar-sdk v0.7.1 ) @@ -80,9 +80,9 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260729165045-b00a39493e13 github.com/smartcontractkit/chain-selectors v1.0.104 - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 // indirect github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260709145319-7782fb89eb16 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e // indirect diff --git a/chain_capabilities/stellar/go.sum b/chain_capabilities/stellar/go.sum index b95c5a4c1..fd2e15146 100644 --- a/chain_capabilities/stellar/go.sum +++ b/chain_capabilities/stellar/go.sum @@ -216,18 +216,18 @@ github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64 github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64/go.mod h1:DZkOtlIRV6ecpCskdXMF+k2es3uJQqTT1m5TRB9etjQ= github.com/smartcontractkit/chain-selectors v1.0.104 h1:/n9pPGM5W/+r1eHoWZv4VwX9LNS1af4+ICyhM8zKRNM= github.com/smartcontractkit/chain-selectors v1.0.104/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260717225314-5fc9656c1b02 h1:+zR0LxBXwyNxx04Jk0otLHcPnOw9htWKK0mtWngp+Xg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260717225314-5fc9656c1b02/go.mod h1:tAsXbMsNrwIp7vCqeauNsT4f3gVATbTmIviCkZPDgdo= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa h1:rAict9GzIskugu/EdmCoBX1dI+DJWqZ9NlIX1BiTa2s= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260819122115-078a7ae707aa/go.mod h1:BJ/X2jB9qPYb835qXtNGpYoq/yiO39FzehhpwGTzJFw= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72/go.mod h1:UYcRMb4dZcoaIPgZJ3hckCySTqtJc9K4Q+tOKErwTq0= github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b h1:L1So1EDBDRET3j/TdV1Gjv3qWARoa/NPRaU7k4r30yA= github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b/go.mod h1:HG/aei0MgBOpsyRLexdKGtOUO8yjSJO3iUu0Uu8KBm4= github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 h1:71PGTkjdFZ0JrloEC2Fs8eHl1b1gmUuH+bq7q23usKk= github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33 h1:oW88YVT5ENU6rCPVOLV/hofmFBux2Mu1EOwz8KJu5ic= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260707203317-661b54b51a33/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe h1:MDnY5wQbWTpFdDnMRicEnoMfSP5nM/KncARr4skP1ug= diff --git a/chain_capabilities/stellar/metering/metering.go b/chain_capabilities/stellar/metering/metering.go index 0aac1ee19..6cc3fb120 100644 --- a/chain_capabilities/stellar/metering/metering.go +++ b/chain_capabilities/stellar/metering/metering.go @@ -20,6 +20,7 @@ const ( GetLatestLedger SpendValueCredits = "1" // WriteReportSpendUnitFormat is the spend unit for write operations, parameterised by chain selector. + // TODO: Switch to "GAS.%d" after the workflow DON accepts SpendValueInGasUnits WriteReportSpendUnitFormat = "STROOP.%d" ) @@ -43,8 +44,9 @@ func GetResponseMetadataWriteReport(feeStroops uint64, chainSelector uint64) cap Metering: []capabilities.MeteringNodeDetail{ { // Peer2PeerID is assigned by the engine, leaving it empty here. - SpendValue: strconv.FormatUint(feeStroops, 10), - SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), + SpendValue: strconv.FormatUint(feeStroops, 10), + SpendValueInGasUnits: strconv.FormatUint(feeStroops, 10), + SpendUnit: fmt.Sprintf(WriteReportSpendUnitFormat, chainSelector), }, }, }