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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## Unreleased
- TBD
- [Define Transaction Factory and Gas Estimator](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/203)

## 10.1.2
- [Fix ABI endpoint definition: attribute "mutability"](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/200)
Expand Down
224 changes: 132 additions & 92 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/gasEstimator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { assert } from "chai";
import { GasEstimator } from "./gasEstimator";

describe("test gas estimator", () => {
it("should estimate gas limit (default gas configuration)", () => {
const estimator = new GasEstimator();

assert.equal(estimator.forEGLDTransfer(0), 50000);
assert.equal(estimator.forEGLDTransfer(3), 50000 + 3 * 1500);

assert.equal(estimator.forESDTTransfer(80), 50000 + 80 * 1500 + 200000 + 100000);
assert.equal(estimator.forESDTTransfer(100), 50000 + 100 * 1500 + 200000 + 100000);

assert.equal(estimator.forESDTNFTTransfer(80), 50000 + 80 * 1500 + 200000 + 800000);
assert.equal(estimator.forESDTNFTTransfer(100), 50000 + 100 * 1500 + 200000 + 800000);

assert.equal(estimator.forMultiESDTNFTTransfer(80, 1), 50000 + 80 * 1500 + (200000 + 800000) * 1);
assert.equal(estimator.forMultiESDTNFTTransfer(80, 3), 50000 + 80 * 1500 + (200000 + 800000) * 3);
});

it("should estimate gas limit (custom gas configuration)", () => {
const estimator = new GasEstimator({
minGasLimit: 10000,
gasPerDataByte: 3000,
gasCostESDTTransfer: 200000,
gasCostESDTNFTTransfer: 300000,
gasCostESDTNFTMultiTransfer: 400000
});

assert.equal(estimator.forEGLDTransfer(0), 10000);
assert.equal(estimator.forEGLDTransfer(3), 10000 + 3 * 3000);

assert.equal(estimator.forESDTTransfer(80), 10000 + 80 * 3000 + 200000 + 100000);
assert.equal(estimator.forESDTTransfer(100), 10000 + 100 * 3000 + 200000 + 100000);

assert.equal(estimator.forESDTNFTTransfer(80), 10000 + 80 * 3000 + 300000 + 800000);
assert.equal(estimator.forESDTNFTTransfer(100), 10000 + 100 * 3000 + 300000 + 800000);

assert.equal(estimator.forMultiESDTNFTTransfer(80, 1), 10000 + 80 * 3000 + (400000 + 800000) * 1);
assert.equal(estimator.forMultiESDTNFTTransfer(80, 3), 10000 + 80 * 3000 + (400000 + 800000) * 3);
});
});
73 changes: 73 additions & 0 deletions src/gasEstimator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
interface IGasConfiguration {
readonly minGasLimit: number;
readonly gasPerDataByte: number;
readonly gasCostESDTTransfer: number;
readonly gasCostESDTNFTTransfer: number;
readonly gasCostESDTNFTMultiTransfer: number;
}

/**
* This is mirroring (on a best efforts basis) the network's gas configuration & gas schedule:
* - https://gateway.elrond.com/network/config
* - https://github.com/ElrondNetwork/elrond-config-mainnet/tree/master/gasSchedules
* - https://github.com/ElrondNetwork/elrond-config-mainnet/blob/master/enableEpochs.toml#L200
*/
export const DefaultGasConfiguration: IGasConfiguration = {
minGasLimit: 50000,
gasPerDataByte: 1500,
gasCostESDTTransfer: 200000,
gasCostESDTNFTTransfer: 200000,
gasCostESDTNFTMultiTransfer: 200000
};

// Additional gas to account for eventual increases in gas requirements (thus avoid fast-breaking changes in clients of erdjs).
const ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000;

// Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs),
// and for eventual increases in gas requirements (thus avoid fast-breaking changes in clients of erdjs).
const ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 800000;

export class GasEstimator {
private readonly gasConfiguration: IGasConfiguration;

constructor(gasConfiguration?: IGasConfiguration) {
this.gasConfiguration = gasConfiguration || DefaultGasConfiguration;
}

forEGLDTransfer(dataLength: number) {
const gasLimit =
this.gasConfiguration.minGasLimit +
this.gasConfiguration.gasPerDataByte * dataLength;

return gasLimit;
}

forESDTTransfer(dataLength: number) {
const gasLimit =
this.gasConfiguration.minGasLimit +
this.gasConfiguration.gasCostESDTTransfer +
this.gasConfiguration.gasPerDataByte * dataLength +
ADDITIONAL_GAS_FOR_ESDT_TRANSFER;

return gasLimit;
}

forESDTNFTTransfer(dataLength: number) {
const gasLimit =
this.gasConfiguration.minGasLimit +
this.gasConfiguration.gasCostESDTNFTTransfer +
this.gasConfiguration.gasPerDataByte * dataLength +
ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER;

return gasLimit;
}

forMultiESDTNFTTransfer(dataLength: number, numTransfers: number) {
const gasLimit =
this.gasConfiguration.minGasLimit +
(this.gasConfiguration.gasCostESDTNFTMultiTransfer + ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER) * numTransfers +
this.gasConfiguration.gasPerDataByte * dataLength;

return gasLimit;
}
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export * from "./errors";
export * from "./account";
export * from "./address";
export * from "./asyncTimer";
export * from "./gasEstimator";
export * from "./transaction";
export * from "./transactionFactory";
export * from "./transactionPayload";
export * from "./transactionWatcher";
export * from "./logger";
Expand Down
8 changes: 8 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ export interface IChainID { valueOf(): string; }
export interface IGasLimit { valueOf(): number; }
export interface IGasPrice { valueOf(): number; }

export interface ITransactionPayload {
length(): number;
encoded(): string;
toString(): string;
valueOf(): Buffer;
}

export interface ITokenPayment {
readonly tokenIdentifier: string;
readonly nonce: number;
readonly amountAsBigInteger: BigNumber;
valueOf(): BigNumber;
}
2 changes: 1 addition & 1 deletion src/proto/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ProtoSerializer {
SndUserName: null,
GasPrice: transaction.getGasPrice().valueOf(),
GasLimit: transaction.getGasLimit().valueOf(),
Data: transaction.getData().isEmpty() ? null : transaction.getData().valueOf(),
Data: transaction.getData().length() == 0 ? null : transaction.getData().valueOf(),
ChainID: Buffer.from(transaction.getChainID().valueOf()),
Version: transaction.getVersion().valueOf(),
Signature: Buffer.from(transaction.getSignature().hex(), "hex")
Expand Down
14 changes: 7 additions & 7 deletions src/tokenTransferBuilders.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Address } from "./address";
import { IAddress } from "./interface";
import { IAddress, ITokenPayment } from "./interface";
import { ArgSerializer } from "./smartcontracts/argSerializer";
import { AddressValue, BigUIntValue, BytesValue, TypedValue, U16Value, U64Value } from "./smartcontracts/typesystem";
import { TokenPayment } from "./tokenPayment";
import { TransactionPayload } from "./transactionPayload";

export class ESDTTransferPayloadBuilder {
payment = TokenPayment.fungibleFromAmount("", "0", 0);
payment: ITokenPayment = TokenPayment.fungibleFromAmount("", "0", 0);

setPayment(payment: TokenPayment): ESDTTransferPayloadBuilder {
setPayment(payment: ITokenPayment): ESDTTransferPayloadBuilder {
this.payment = payment;
return this;
}
Expand All @@ -28,10 +28,10 @@ export class ESDTTransferPayloadBuilder {
}

export class ESDTNFTTransferPayloadBuilder {
payment: TokenPayment = TokenPayment.nonFungible("", 0);
payment: ITokenPayment = TokenPayment.nonFungible("", 0);
destination: IAddress = new Address("");

setPayment(payment: TokenPayment): ESDTNFTTransferPayloadBuilder {
setPayment(payment: ITokenPayment): ESDTNFTTransferPayloadBuilder {
this.payment = payment;
return this;
}
Expand Down Expand Up @@ -60,10 +60,10 @@ export class ESDTNFTTransferPayloadBuilder {
}

export class MultiESDTNFTTransferPayloadBuilder {
payments: TokenPayment[] = [];
payments: ITokenPayment[] = [];
destination: IAddress = new Address("");

setPayments(payments: TokenPayment[]): MultiESDTNFTTransferPayloadBuilder {
setPayments(payments: ITokenPayment[]): MultiESDTNFTTransferPayloadBuilder {
this.payments = payments;
return this;
}
Expand Down
10 changes: 5 additions & 5 deletions src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BigNumber } from "bignumber.js";
import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ISignature, ITransactionValue } from "./interface";
import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ISignature, ITransactionPayload, ITransactionValue } from "./interface";
import { Address } from "./address";
import {
TransactionOptions,
Expand Down Expand Up @@ -54,7 +54,7 @@ export class Transaction {
/**
* The payload of the transaction.
*/
private readonly data: TransactionPayload;
private readonly data: ITransactionPayload;

/**
* The chain ID of the Network (e.g. "1" for Mainnet).
Expand Down Expand Up @@ -102,7 +102,7 @@ export class Transaction {
sender?: IAddress;
gasPrice?: IGasPrice;
gasLimit: IGasLimit;
data?: TransactionPayload;
data?: ITransactionPayload;
chainID: IChainID;
version?: TransactionVersion;
options?: TransactionOptions;
Expand Down Expand Up @@ -165,7 +165,7 @@ export class Transaction {
this.gasLimit = gasLimit;
}

getData(): TransactionPayload {
getData(): ITransactionPayload {
return this.data;
}

Expand Down Expand Up @@ -226,7 +226,7 @@ export class Transaction {
sender: sender ? sender.bech32() : this.sender.bech32(),
gasPrice: this.gasPrice.valueOf(),
gasLimit: this.gasLimit.valueOf(),
data: this.data.isEmpty() ? undefined : this.data.encoded(),
data: this.data.length() == 0 ? undefined : this.data.encoded(),
chainID: this.chainID.valueOf(),
version: this.version.valueOf(),
options: this.options.valueOf() == 0 ? undefined : this.options.valueOf(),
Expand Down
86 changes: 86 additions & 0 deletions src/transactionFactory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { assert } from "chai";
import { Address } from "./address";
import { GasEstimator } from "./gasEstimator";
import { TokenPayment } from "./tokenPayment";
import { TransactionFactory } from "./transactionFactory";
import { TransactionPayload } from "./transactionPayload";

describe("test transaction factory", () => {
const factory = new TransactionFactory(new GasEstimator());

it("should create EGLD transfers", () => {
const transactionWithData = factory.createEGLDTransfer({
value: TokenPayment.egldFromAmount(10.5),
receiver: new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"),
data: new TransactionPayload("hello"),
chainID: "D"
})

assert.equal(transactionWithData.getReceiver().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transactionWithData.getValue(), "10500000000000000000");
assert.equal(transactionWithData.getGasLimit(), 50000 + 5 * 1500);
assert.equal(transactionWithData.getData().toString(), "hello");
assert.equal(transactionWithData.getChainID(), "D");

const transactionWithoutData = factory.createEGLDTransfer({
value: TokenPayment.egldFromAmount(10.5),
receiver: new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"),
chainID: "D"
})

assert.equal(transactionWithoutData.getReceiver().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transactionWithoutData.getValue(), "10500000000000000000");
assert.equal(transactionWithoutData.getGasLimit(), 50000);
assert.equal(transactionWithoutData.getData().toString(), "");
assert.equal(transactionWithoutData.getChainID(), "D");
});

it("should create ESDT transfers", () => {
const transaction = factory.createESDTTransfer({
payment: TokenPayment.fungibleFromAmount("COUNTER-8b028f", "100.00", 0),
receiver: new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"),
chainID: "D"
})

assert.equal(transaction.getReceiver().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transaction.getValue(), "");
assert.equal(transaction.getGasLimit(), 50000 + 44 * 1500 + 200000 + 100000);
assert.equal(transaction.getData().toString(), "ESDTTransfer@434f554e5445522d386230323866@64");
assert.equal(transaction.getChainID(), "D");
});

it("should create ESDTNFT transfers", () => {
const transaction = factory.createESDTNFTTransfer({
payment: TokenPayment.nonFungible("ERDJS-38f249", 1),
destination: new Address("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
sender: new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"),
chainID: "D"
})

assert.equal(transaction.getSender().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transaction.getReceiver().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transaction.getValue(), "");
assert.equal(transaction.getGasLimit(), 50000 + 111 * 1500 + 200000 + 800000);
assert.equal(transaction.getData().toString(), "ESDTNFTTransfer@4552444a532d333866323439@01@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8");
assert.equal(transaction.getChainID(), "D");
});

it("should create Multi ESDTNFT transfers", () => {
const transaction = factory.createMultiESDTNFTTransfer({
payments: [
TokenPayment.nonFungible("ERDJS-38f249", 1),
TokenPayment.fungibleFromAmount("BAR-c80d29", "10.00", 18)
],
destination: new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"),
sender: new Address("erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha"),
chainID: "D"
})

assert.equal(transaction.getSender().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transaction.getReceiver().bech32(), "erd1dc3yzxxeq69wvf583gw0h67td226gu2ahpk3k50qdgzzym8npltq7ndgha");
assert.equal(transaction.getValue(), "");
assert.equal(transaction.getGasLimit(), 50000 + 158 * 1500 + (200000 + 800000) * 2);
assert.equal(transaction.getData().toString(), "MultiESDTNFTTransfer@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@02@4552444a532d333866323439@01@01@4241522d633830643239@@8ac7230489e80000");
assert.equal(transaction.getChainID(), "D");
});
});
Loading