Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check_with_mesh_cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check_with_mesh_cli_on_localnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check_with_mesh_cli_on_mainnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
runs-on: ${{ matrix.runs-on }}
name: Build
steps:
- name: Set up Go 1.23
uses: actions/setup-go@v5
- name: Set up Go 1.26
uses: actions/setup-go@v6
with:
go-version: 1.23.6
go-version: 1.26.6
id: go

- name: Check out code into the Go module directory
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ jobs:
name: golangci linter
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: 1.23.6
- uses: actions/checkout@v4
go-version: 1.26.6
- uses: actions/checkout@v6
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v7
with:
version: v1.64.5
version: v2.12.2

# Optional: golangci-lint command line arguments.
args: --timeout 10m0s --max-issues-per-linter 0 --max-same-issues 0 --print-issued-lines
args: --timeout 10m0s --max-issues-per-linter 0 --max-same-issues 0

# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
4 changes: 2 additions & 2 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.23.6
go-version: 1.26.6
id: go

- name: Check out code into the Go module directory
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regularly_check_devnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regularly_check_mainnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regularly_check_testnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
python-version: 3.11

- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Install dependencies
run: |
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
module github.com/multiversx/mx-chain-rosetta

go 1.23.6
go 1.26

require (
github.com/coinbase/rosetta-sdk-go v0.8.3
github.com/coinbase/rosetta-sdk-go/types v1.0.0
github.com/coinbase/rosetta-sdk-go v0.12.0
github.com/coinbase/rosetta-sdk-go/types v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219122727-014ae9f9311f
github.com/multiversx/mx-chain-go v1.11.1
github.com/multiversx/mx-chain-logger-go v1.1.0
github.com/multiversx/mx-chain-proxy-go v1.4.0
github.com/multiversx/mx-chain-storage-go v1.1.0
github.com/multiversx/mx-chain-vm-common-go v1.6.0
github.com/stretchr/testify v1.10.0
github.com/urfave/cli v1.22.16
)
Expand Down Expand Up @@ -45,7 +46,6 @@ require (
github.com/multiversx/mx-chain-communication-go v1.3.0 // indirect
github.com/multiversx/mx-chain-crypto-go v1.3.0 // indirect
github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6 // indirect
github.com/multiversx/mx-chain-vm-common-go v1.6.0 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coinbase/rosetta-sdk-go v0.8.3 h1:IYqd+Ser5NVh0s7p8p2Ir82iCvi75E1l0NH2H4NEr0Y=
github.com/coinbase/rosetta-sdk-go v0.8.3/go.mod h1:ChOHc+BNq7zqJDDkui0DA124GOvlAiRbdgAc1U9GMDQ=
github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA=
github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c=
github.com/coinbase/rosetta-sdk-go v0.12.0 h1:x3T5ScBw0J5fmu9Nkx4QNI+s8NqvSYupb39RIOm5HjQ=
github.com/coinbase/rosetta-sdk-go v0.12.0/go.mod h1:rdzdWYdC3AKsR7IDXKFu7HW7sa9q+czdYGxw44vNRSQ=
github.com/coinbase/rosetta-sdk-go/types v1.7.2 h1:iRt/R+Esn4GNyd2cijjKPiIine/nAlUaCleaAdj8d+A=
github.com/coinbase/rosetta-sdk-go/types v1.7.2/go.mod h1:bN5JAIEG/dGZNnYzsfizeHhzFOf9s5rkJoaNM/7L5QI=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
2 changes: 1 addition & 1 deletion server/provider/currenciesProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (provider *currenciesProvider) GetCustomCurrencies() []resources.Currency {
return provider.customCurrencies
}

// GetCustomCurrencyBySymbol gets a custom currency (ESDT) by symbol (identifier)
// GetCustomCurrenciesSymbols returns the enabled custom currency (ESDT) symbols.
func (provider *currenciesProvider) GetCustomCurrenciesSymbols() []string {
return provider.customCurrenciesSymbols
}
Expand Down
5 changes: 5 additions & 0 deletions server/services/accountService.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,8 @@ func (service *accountService) getNativeSymbol() string {
func (service *accountService) AccountCoins(_ context.Context, _ *types.AccountCoinsRequest) (*types.AccountCoinsResponse, *types.Error) {
return nil, service.errFactory.newErr(ErrNotImplemented)
}

// AllAccountBalances implements the /account/all-balances.
func (service *accountService) AllAccountBalances(_ context.Context, _ *types.AllAccountBalancesRequest) (*types.AllAccountBalancesResponse, *types.Error) {
return nil, service.errFactory.newErr(ErrNotImplemented)
}
14 changes: 14 additions & 0 deletions server/services/constructionOptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package services

import (
"errors"
"math/big"
)

type constructionOptions struct {
Expand Down Expand Up @@ -40,6 +41,19 @@ func (options *constructionOptions) coalesceGasPrice(minGasPrice uint64) uint64
return options.GasPrice
}

func validateTransferAmount(amount string) error {
bigAmount, ok := big.NewInt(0).SetString(amount, 10)
if !ok {
return errors.New("option 'amount' is not a valid integer")
}

if bigAmount.Sign() <= 0 {
return errors.New("option 'amount' must be a positive integer")
}

return nil
}

func (options *constructionOptions) validate(nativeCurrencySymbol string) error {
if len(options.Sender) == 0 {
return errors.New("missing option: 'sender'")
Expand Down
80 changes: 80 additions & 0 deletions server/services/constructionService.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,83 @@ func (service *constructionService) ConstructionSubmit(
},
}, nil
}

// ConstructionPreprocessOperations will validate the requested operations and options,
// prepare the corresponding transaction (handling native or custom currency transfers)
// and return the resulting operations along with the maximum fee and metadata required
// for the subsequent payload construction
func (service *constructionService) ConstructionPreprocessOperations(
Comment on lines +417 to +422

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

_ context.Context,
request *types.ConstructionPreprocessOperationsRequest,
) (*types.ConstructionPreprocessOperationsResponse, *types.Error) {
log.Debug("constructionService.ConstructionPreprocessOperations()", "construct_op", request.ConstructOp, "from_address", request.FromAddress)

requestOptions, err := newConstructionOptions(request.Options)
if err != nil {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, err)
}
if request.FromAddress != nil && len(*request.FromAddress) > 0 && len(requestOptions.Sender) == 0 {
requestOptions.Sender = *request.FromAddress
}

isNative := service.extension.isNativeCurrencySymbol(requestOptions.CurrencySymbol)
if !isNative && !service.provider.HasCustomCurrency(requestOptions.CurrencySymbol) {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, fmt.Errorf("unsupported currency: %s", requestOptions.CurrencySymbol))
}

nativeCurrencySymbol := service.extension.getNativeCurrencySymbol()
if request.ConstructOp == "transfer" {
err = requestOptions.validate(nativeCurrencySymbol)
if err != nil {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, err)
}
err = validateTransferAmount(requestOptions.Amount)
if err != nil {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, err)
}
} else {
return nil, service.errFactory.newErrWithOriginal(ErrNotImplemented, fmt.Errorf("unsupported construct_op: %s", request.ConstructOp))
}

tx := &data.Transaction{
Sender: requestOptions.Sender,
Receiver: requestOptions.Receiver,
Value: requestOptions.Amount,
Data: requestOptions.Data,
}

if !isNative {
tx.Value = amountZero
tx.Data = service.computeDataForCustomCurrencyTransfer(requestOptions.CurrencySymbol, requestOptions.Amount)
} else if isCustomCurrencyTransfer(string(tx.Data)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may not have a very good overview of the code and this might be wrong but, shouldn't this check for other kind of transfers? Currently only the ESDTTransfer prefix is checked leaving ESDTNFTTransfer and MultiESDTNFTTransfer unchecked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for ESDT tokens transfer only function ESDTTransfer is supported.

return nil, service.errFactory.newErrWithOriginal(
ErrConstruction,
errors.New("for native currency transfers, option 'data' must not encode a custom currency (ESDT) transfer"),
)
}

operations, err := service.createOperationsFromPreparedTx(tx)
if err != nil {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, err)
}

_, gasLimit, gasPrice, errTyped := service.computeFeeComponents(requestOptions, tx.Data)
if errTyped != nil {
return nil, errTyped
}

requestOptions.GasLimit = gasLimit
requestOptions.GasPrice = gasPrice

maxFee := service.computeMaxFee(tx.Data, gasLimit, gasPrice)

metadataBytes, err := json.Marshal(requestOptions)
if err != nil {
return nil, service.errFactory.newErrWithOriginal(ErrConstruction, err)
}
return &types.ConstructionPreprocessOperationsResponse{
Operations: operations,
MaxFee: service.extension.valueToNativeAmount(maxFee.String()),
Metadata: new(string(metadataBytes)),
}, nil
}
14 changes: 14 additions & 0 deletions server/services/constructionServiceFee.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ func (service *constructionService) computeFeeComponents(options *constructionOp
return fee, gasLimit, gasPrice, nil
}

// computeMaxFee returns the maximum fee chargeable for a transaction built with the given options,
// taking into account the user-provided gas limit, so it stays consistent with the metadata returned
// to the client. Following the protocol's gas categorization (see SplitTxGasInCategoriesInEpoch),
// movement gas is MinGasLimit + GasPerDataByte * len(data), charged at the base gas price, while all
// remaining gas is processing gas, charged with the gas price modifier.
func (service *constructionService) computeMaxFee(computedData []byte, gasLimit uint64, gasPrice uint64) *big.Int {
networkConfig := service.provider.GetNetworkConfig()

movementGasLimit := networkConfig.MinGasLimit + networkConfig.GasPerDataByte*uint64(len(computedData))
processingGasLimit := gasLimit - movementGasLimit

return computeFee(movementGasLimit, processingGasLimit, gasPrice, networkConfig.GasPriceModifier)
}

func computeFee(movementGasLimit uint64, executionGasLimit uint64, gasPrice uint64, gasPriceModifier float64) *big.Int {
movementFee := multiplyUint64(movementGasLimit, gasPrice)
executionGasPrice := uint64(float64(gasPrice) * gasPriceModifier)
Expand Down
Loading
Loading