diff --git a/src/account.ts b/src/account.ts index 68ccf609..d371afdc 100644 --- a/src/account.ts +++ b/src/account.ts @@ -8,7 +8,7 @@ export class Account { /** * The address of the account. */ - readonly address: IAddress = new Address(); + readonly address: IAddress = Address.empty(); /** * The nonce of the account (the account sequence number). diff --git a/src/address.spec.ts b/src/address.spec.ts index 248b858a..48313ab0 100644 --- a/src/address.spec.ts +++ b/src/address.spec.ts @@ -17,7 +17,7 @@ describe("test address", () => { }); it("should create empty address", async () => { - let nobody = new Address(); + const nobody = Address.empty(); assert.isEmpty(nobody.hex()); assert.isEmpty(nobody.bech32()); @@ -55,4 +55,16 @@ describe("test address", () => { assert.isFalse(Address.isValid("xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); assert.isFalse(Address.isValid("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2")); }); + + it("should check whether isSmartContract", () => { + assert.isFalse( + Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").isSmartContract(), + ); + assert.isTrue( + Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l").isSmartContract(), + ); + assert.isTrue( + Address.fromBech32("erd1qqqqqqqqqqqqqpgqxwakt2g7u9atsnr03gqcgmhcv38pt7mkd94q6shuwt").isSmartContract(), + ); + }); }); diff --git a/src/address.ts b/src/address.ts index 54601745..a5b3fda5 100644 --- a/src/address.ts +++ b/src/address.ts @@ -23,7 +23,7 @@ export class Address { /** * Creates an address object, given a raw string (whether a hex pubkey or a Bech32 address), a sequence of bytes, or another Address object. */ - public constructor(value?: Address | Buffer | string) { + public constructor(value: Address | Buffer | string) { if (!value) { return; } @@ -48,7 +48,7 @@ export class Address { } private static fromValidHex(value: string): Address { - let result = new Address(); + let result = Address.empty(); result.valueHex = value; return result; } @@ -91,10 +91,11 @@ export class Address { } /** - * Creates an empty address object + * Creates an empty address object. + * Generally speaking, this should not be used by client code (internal use only). */ static empty(): Address { - return new Address(); + return new Address(""); } /** @@ -109,12 +110,12 @@ export class Address { throw new errors.ErrAddressCannotCreate(value, err); } - let prefix = decoded.prefix; + const prefix = decoded.prefix; if (prefix != HRP) { throw new errors.ErrAddressBadHrp(HRP, prefix); } - let pubkey = Buffer.from(bech32.fromWords(decoded.words)); + const pubkey = Buffer.from(bech32.fromWords(decoded.words)); if (pubkey.length != PUBKEY_LENGTH) { throw new errors.ErrAddressCannotCreate(value); } @@ -138,9 +139,16 @@ export class Address { } /** - * Returns the hex representation of the address (pubkey) + * Use {@link toHex} instead. */ hex(): string { + return this.toHex(); + } + + /** + * Returns the hex representation of the address (pubkey) + */ + toHex(): string { if (this.isEmpty()) { return ""; } @@ -149,9 +157,16 @@ export class Address { } /** - * Returns the bech32 representation of the address + * Use {@link toBech32} instead. */ bech32(): string { + return this.toBech32(); + } + + /** + * Returns the bech32 representation of the address + */ + toBech32(): string { if (this.isEmpty()) { return ""; } @@ -162,9 +177,16 @@ export class Address { } /** - * Returns the pubkey as raw bytes (buffer) + * Use {@link getPublicKey} instead. */ pubkey(): Buffer { + return this.getPublicKey(); + } + + /** + * Returns the pubkey as raw bytes (buffer) + */ + getPublicKey(): Buffer { if (this.isEmpty()) { return Buffer.from([]); } @@ -172,6 +194,14 @@ export class Address { return Buffer.from(this.valueHex, "hex"); } + /** + * Returns the human-readable-part of the bech32 addresses. + * The HRP is currently hardcoded to "erd". + */ + getHrp(): string { + return HRP; + } + /** * Returns whether the address is empty. */ @@ -194,7 +224,7 @@ export class Address { * Returns the bech32 representation of the address */ toString(): string { - return this.bech32(); + return this.toBech32(); } /** @@ -202,19 +232,30 @@ export class Address { */ toJSON(): object { return { - bech32: this.bech32(), - pubkey: this.hex(), + bech32: this.toBech32(), + pubkey: this.toHex(), }; } /** - * Creates the Zero address (the one that should be used when deploying smart contracts) + * Creates the Zero address (the one that should be used when deploying smart contracts). + * Generally speaking, this should not be used by client code (internal use only). */ static Zero(): Address { return new Address("0".repeat(64)); } + /** + * Use {@link isSmartContract} instead. + */ isContractAddress(): boolean { - return this.hex().startsWith(SMART_CONTRACT_HEX_PUBKEY_PREFIX); + return this.isSmartContract(); + } + + /** + * Returns whether the address is a smart contract address. + */ + isSmartContract(): boolean { + return this.toHex().startsWith(SMART_CONTRACT_HEX_PUBKEY_PREFIX); } } diff --git a/src/compatibility.ts b/src/compatibility.ts index 3aec4764..b214c4f2 100644 --- a/src/compatibility.ts +++ b/src/compatibility.ts @@ -1,26 +1,21 @@ import { Address } from "./address"; +import { Err } from "./errors"; import { IAddress } from "./interface"; /** * For internal use only. */ export class Compatibility { - static areWarningsEnabled: boolean = true; - /** * For internal use only. */ static guardAddressIsSetAndNonZero(address: IAddress | undefined, context: string, resolution: string) { - if (!this.areWarningsEnabled) { - return; - } - if (!address || address.bech32() == "") { - console.warn( + throw new Err( `${context}: address should be set; ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`, ); } else if (address.bech32() == Address.Zero().bech32()) { - console.warn( + throw new Err( `${context}: address should not be the 'zero' address (also known as the 'contracts deployment address'); ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`, ); } diff --git a/src/constants.ts b/src/constants.ts index c00f61a3..63784a2f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,3 +14,5 @@ export const CONTRACT_DEPLOY_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq export const DELEGATION_MANAGER_SC_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6"; export const DEFAULT_HRP = "erd"; export const ESDT_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"; +export const DEFAULT_MESSAGE_VERSION = 1; +export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; diff --git a/src/interface.ts b/src/interface.ts index 0fe96076..1b26134e 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -26,16 +26,45 @@ export interface IPlainTransactionObject { guardianSignature?: string; } -export interface ISignature { hex(): string; } -export interface IAddress { bech32(): string; } -export interface ITransactionValue { toString(): string; } -export interface IAccountBalance { toString(): string; } -export interface INonce { valueOf(): number; } -export interface IChainID { valueOf(): string; } -export interface IGasLimit { valueOf(): number; } -export interface IGasPrice { valueOf(): number; } -export interface ITransactionVersion { valueOf(): number; } -export interface ITransactionOptions { valueOf(): number; } +export interface ISignature { + hex(): string; +} + +export interface IAddress { + bech32(): string; +} + +export interface ITransactionValue { + toString(): string; +} + +export interface IAccountBalance { + toString(): string; +} + +export interface INonce { + valueOf(): number; +} + +export interface IChainID { + valueOf(): string; +} + +export interface IGasLimit { + valueOf(): number; +} + +export interface IGasPrice { + valueOf(): number; +} + +export interface ITransactionVersion { + valueOf(): number; +} + +export interface ITransactionOptions { + valueOf(): number; +} export interface ITransactionPayload { length(): number; @@ -59,17 +88,17 @@ export type ITokenPayment = ITokenTransfer; export interface ITransactionNext { sender: string; receiver: string; - gasLimit: BigNumber.Value; + gasLimit: bigint; chainID: string; - nonce: BigNumber.Value; - value: BigNumber.Value; + nonce: bigint; + value: bigint; senderUsername: string; receiverUsername: string; - gasPrice: BigNumber.Value; + gasPrice: bigint; data: Uint8Array; version: number; options: number; guardian: string; signature: Uint8Array; guardianSignature: Uint8Array; - } +} diff --git a/src/message.spec.ts b/src/message.spec.ts new file mode 100644 index 00000000..3abe9cef --- /dev/null +++ b/src/message.spec.ts @@ -0,0 +1,87 @@ +import { assert } from "chai"; +import { Message, MessageComputer } from "./message"; +import { loadTestWallets, TestWallet } from "./testutils"; +import { UserVerifier } from "@multiversx/sdk-wallet"; +import { DEFAULT_MESSAGE_VERSION } from "./constants"; + +describe("test message", () => { + let alice: TestWallet; + const messageComputer = new MessageComputer(); + + before(async function () { + ({ alice } = await loadTestWallets()); + }); + + it("should test message compute bytes for signing", async () => { + const data = Buffer.from("test message"); + + const message = new Message({ + data: data, + }); + + const serialized = messageComputer.computeBytesForSigning(message); + + assert.equal( + Buffer.from(serialized).toString("hex"), + "2162d6271208429e6d3e664139e98ba7c5f1870906fb113e8903b1d3f531004d", + ); + }); + + it("should create, sign, pack, unpack and verify message", async () => { + const data = Buffer.from("test"); + + const message = new Message({ + data: data, + address: alice.getAddress(), + }); + + message.signature = await alice.signer.sign(Buffer.from(messageComputer.computeBytesForSigning(message))); + + assert.equal( + Buffer.from(message.signature).toString("hex"), + "7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", + ); + + const packedMessage = messageComputer.packMessage(message); + assert.deepEqual(packedMessage, { + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + message: "74657374", + signature: + "7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", + version: 1, + }); + + const unpackedMessage = messageComputer.unpackMessage(packedMessage); + assert.deepEqual(unpackedMessage.address, alice.getAddress()); + assert.deepEqual(unpackedMessage.data, message.data); + assert.deepEqual(unpackedMessage.signature, message.signature); + assert.deepEqual(unpackedMessage.version, message.version); + + const verifier = UserVerifier.fromAddress(alice.getAddress()); + const isValid = verifier.verify( + Buffer.from(messageComputer.computeBytesForVerifying(unpackedMessage)), + Buffer.from(unpackedMessage.signature!), + ); + assert.equal(isValid, true); + }); + + it("should unpack legacy message", async () => { + const legacyMessage = { + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + message: "0x7468697320697320612074657374206d657373616765", + signature: + "0xb16847437049986f936dd4a0917c869730cbf29e40a0c0821ca70db33f44758c3d41bcbea446dee70dea13d50942343bb78e74979dc434bbb2b901e0f4fd1809", + version: 1, + signer: "ErdJS", + }; + + const message = messageComputer.unpackMessage(legacyMessage); + assert.deepEqual(message.address, alice.getAddress()); + assert.deepEqual(Buffer.from(message.data).toString(), "this is a test message"); + assert.deepEqual( + Buffer.from(message.signature!).toString("hex"), + "b16847437049986f936dd4a0917c869730cbf29e40a0c0821ca70db33f44758c3d41bcbea446dee70dea13d50942343bb78e74979dc434bbb2b901e0f4fd1809", + ); + assert.deepEqual(message.version, DEFAULT_MESSAGE_VERSION); + }); +}); diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 00000000..d018f26d --- /dev/null +++ b/src/message.ts @@ -0,0 +1,90 @@ +import { IAddress } from "./interface"; +import { DEFAULT_MESSAGE_VERSION, MESSAGE_PREFIX } from "./constants"; +import { Address } from "./address"; + +const createKeccakHash = require("keccak"); + +export class Message { + /** + * Actual message being signed. + */ + public data: Uint8Array; + /** + * The message signature. + */ + public signature?: Uint8Array; + /** + * Address of the wallet that performed the signing operation. + */ + public address?: IAddress; + /** + * Number representing the message version. + */ + public version: number; + + constructor(options: { data: Uint8Array; signature?: Uint8Array; address?: IAddress; version?: number }) { + this.data = options.data; + this.signature = options.signature; + this.address = options.address; + this.version = options.version || DEFAULT_MESSAGE_VERSION; + } +} + +export class MessageComputer { + constructor() {} + + computeBytesForSigning(message: Message): Uint8Array { + const messageSize = Buffer.from(message.data.length.toString()); + const signableMessage = Buffer.concat([messageSize, message.data]); + let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); + + return createKeccakHash("keccak256").update(bytesToHash).digest(); + } + + computeBytesForVerifying(message: Message): Uint8Array { + return this.computeBytesForSigning(message); + } + + packMessage(message: Message): { + message: string; + signature: string; + address: string; + version: number; + } { + return { + message: Buffer.from(message.data).toString("hex"), + signature: message.signature ? Buffer.from(message.signature).toString("hex") : "", + address: message.address ? message.address.bech32() : "", + version: message.version ? message.version : DEFAULT_MESSAGE_VERSION, + }; + } + + unpackMessage(packedMessage: { message: string; signature?: string; address?: string; version?: number }): Message { + const dataHex = this.trimHexPrefix(packedMessage.message); + const data = Buffer.from(dataHex, "hex"); + + const signatureHex = this.trimHexPrefix(packedMessage.signature || ""); + const signature = Buffer.from(signatureHex, "hex"); + + let address: Address | undefined = undefined; + if (packedMessage.address) { + address = Address.fromBech32(packedMessage.address); + } + + const version = packedMessage.version || DEFAULT_MESSAGE_VERSION; + + return new Message({ + data: data, + signature: signature, + address: address, + version: version, + }); + } + + private trimHexPrefix(data: string): string { + if (data.startsWith("0x") || data.startsWith("0X")) { + return data.slice(2); + } + return data; + } +} diff --git a/src/signableMessage.ts b/src/signableMessage.ts index 4fe82b7e..872407cf 100644 --- a/src/signableMessage.ts +++ b/src/signableMessage.ts @@ -1,80 +1,81 @@ import { Address } from "./address"; import { ISignature } from "./interface"; import { interpretSignatureAsBuffer } from "./signature"; +import { MESSAGE_PREFIX } from "./constants"; const createKeccakHash = require("keccak"); -export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; - +/** + * @deprecated Use {@link Message} instead. + */ export class SignableMessage { - - /** - * Actual message being signed. - */ - message: Buffer; - /** - * Signature obtained by a signer of type @param signer . - */ - signature: Buffer; - - /** - * Address of the wallet that performed the signing operation - */ - address: Address; - - /** - * Text representing the identifer for the application that signed the message - */ - signer: string; - - /** - * Number representing the signable message version - */ - version: number; - - public constructor(init?: Partial) { - this.message = Buffer.from([]); - this.signature = Buffer.from([]); - this.version = 1; - this.signer = "ErdJS"; - this.address = new Address(); - - Object.assign(this, init); - } - - serializeForSigning(): Buffer { - const messageSize = Buffer.from(this.message.length.toString()); - const signableMessage = Buffer.concat([messageSize, this.message]); - let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); - - return createKeccakHash("keccak256").update(bytesToHash).digest(); - } - - serializeForSigningRaw(): Buffer { - return Buffer.concat([this.getMessageSize(), this.message]); - } - - getSignature(): Buffer { - return this.signature; - } - - applySignature(signature: ISignature | Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - } - - getMessageSize(): Buffer { - const messageSize = Buffer.alloc(4); - messageSize.writeUInt32BE(this.message.length, 0); - - return messageSize; - } - - toJSON(): object { - return { - address: this.address.bech32(), - message: "0x" + this.message.toString("hex"), - signature: "0x" + this.signature.toString("hex"), - version: this.version, - signer: this.signer, - }; - } + /** + * Actual message being signed. + */ + message: Buffer; + /** + * Signature obtained by a signer of type @param signer . + */ + signature: Buffer; + + /** + * Address of the wallet that performed the signing operation + */ + address: Address; + + /** + * Text representing the identifer for the application that signed the message + */ + signer: string; + + /** + * Number representing the signable message version + */ + version: number; + + public constructor(init?: Partial) { + this.message = Buffer.from([]); + this.signature = Buffer.from([]); + this.version = 1; + this.signer = "ErdJS"; + this.address = Address.empty(); + + Object.assign(this, init); + } + + serializeForSigning(): Buffer { + const messageSize = Buffer.from(this.message.length.toString()); + const signableMessage = Buffer.concat([messageSize, this.message]); + let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); + + return createKeccakHash("keccak256").update(bytesToHash).digest(); + } + + serializeForSigningRaw(): Buffer { + return Buffer.concat([this.getMessageSize(), this.message]); + } + + getSignature(): Buffer { + return this.signature; + } + + applySignature(signature: ISignature | Uint8Array) { + this.signature = interpretSignatureAsBuffer(signature); + } + + getMessageSize(): Buffer { + const messageSize = Buffer.alloc(4); + messageSize.writeUInt32BE(this.message.length, 0); + + return messageSize; + } + + toJSON(): object { + return { + address: this.address.bech32(), + message: "0x" + this.message.toString("hex"), + signature: "0x" + this.signature.toString("hex"), + version: this.version, + signer: this.signer, + }; + } } diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index 1b5b71f3..9cf2b7b7 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -35,9 +35,9 @@ export class Interaction { private gasLimit: IGasLimit = 0; private gasPrice: IGasPrice | undefined = undefined; private chainID: IChainID = ""; - private querent: IAddress = new Address(); + private querent: IAddress = Address.empty(); private explicitReceiver?: IAddress; - private sender: IAddress = new Address(); + private sender: IAddress = Address.empty(); private isWithSingleESDTTransfer: boolean = false; private isWithSingleESDTNFTTransfer: boolean = false; diff --git a/src/smartcontracts/query.ts b/src/smartcontracts/query.ts index a298ebf0..3db54519 100644 --- a/src/smartcontracts/query.ts +++ b/src/smartcontracts/query.ts @@ -18,7 +18,7 @@ export class Query { args?: TypedValue[], value?: ITransactionValue }) { - this.caller = obj.caller || new Address(); + this.caller = obj.caller || Address.empty(); this.address = obj.address; this.func = obj.func; this.args = obj.args || []; diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index e257cc07..2c033a65 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -202,7 +202,7 @@ describe("test smart contract results parser", () => { it("should parse contract outcome, on signal error", async () => { let transaction = new TransactionOnNetwork({ logs: new TransactionLogs({ - address: new Address(), + address: Address.empty(), events: [ new TransactionEvent({ identifier: "signalError", @@ -222,7 +222,7 @@ describe("test smart contract results parser", () => { it("should parse contract outcome, on too much gas warning", async () => { let transaction = new TransactionOnNetwork({ logs: new TransactionLogs({ - address: new Address(), + address: Address.empty(), events: [ new TransactionEvent({ identifier: "writeLog", diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index 788c9d54..b2330b36 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -31,7 +31,7 @@ interface IAbi { * An abstraction for deploying and interacting with Smart Contracts. */ export class SmartContract implements ISmartContract { - private address: IAddress = new Address(); + private address: IAddress = Address.empty(); private abi?: IAbi; /** @@ -53,7 +53,7 @@ export class SmartContract implements ISmartContract { * Create a SmartContract object by providing its address on the Network. */ constructor(options: { address?: IAddress, abi?: IAbi } = {}) { - this.address = options.address || new Address(); + this.address = options.address || Address.empty(); this.abi = options.abi; if (this.abi) { @@ -130,7 +130,7 @@ export class SmartContract implements ISmartContract { const nextTx = scNextTransactionFactory.createTransactionForDeploy({ sender: deployer, bytecode: bytecode, - gasLimit: gasLimit.valueOf(), + gasLimit: BigInt(gasLimit.valueOf()), args: initArguments, isUpgradeable: metadataAsJson.upgradeable, isReadable: metadataAsJson.readable, @@ -191,7 +191,7 @@ export class SmartContract implements ISmartContract { sender: caller, contract: this.getAddress(), bytecode: bytecode, - gasLimit: gasLimit.valueOf(), + gasLimit: BigInt(gasLimit.valueOf()), args: initArguments, isUpgradeable: metadataAsJson.upgradeable, isReadable: metadataAsJson.readable, @@ -229,7 +229,7 @@ export class SmartContract implements ISmartContract { sender: caller, contract: receiver ? receiver : this.getAddress(), functionName: func.toString(), - gasLimit: gasLimit.valueOf(), + gasLimit: BigInt(gasLimit.valueOf()), args: args }) diff --git a/src/smartcontracts/transactionPayloadBuilders.ts b/src/smartcontracts/transactionPayloadBuilders.ts index c70fbdcc..4234381f 100644 --- a/src/smartcontracts/transactionPayloadBuilders.ts +++ b/src/smartcontracts/transactionPayloadBuilders.ts @@ -6,11 +6,10 @@ import { TypedValue } from "./typesystem"; export const WasmVirtualMachine = "0500"; -/** - * A builder for {@link TransactionPayload} objects, to be used for Smart Contract deployment transactions. - */ /** * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * + * A builder for {@link TransactionPayload} objects, to be used for Smart Contract deployment transactions. */ export class ContractDeployPayloadBuilder { private code: ICode | null = null; @@ -65,6 +64,8 @@ export class ContractDeployPayloadBuilder { } /** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * * A builder for {@link TransactionPayload} objects, to be used for Smart Contract upgrade transactions. */ export class ContractUpgradePayloadBuilder { @@ -120,6 +121,8 @@ export class ContractUpgradePayloadBuilder { } /** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * * A builder for {@link TransactionPayload} objects, to be used for Smart Contract execution transactions. */ export class ContractCallPayloadBuilder { diff --git a/src/tokens.spec.ts b/src/tokens.spec.ts index 9fd54cd4..e7949b56 100644 --- a/src/tokens.spec.ts +++ b/src/tokens.spec.ts @@ -1,12 +1,12 @@ -import { Token, TokenComputer } from "./tokens"; import { assert } from "chai"; +import { Token, TokenComputer } from "./tokens"; describe("test token computer", async () => { const tokenComputer = new TokenComputer(); it("should test if token is fungible", async () => { - const fungibleToken = new Token("TEST-123456", 0); - const nonFungibleToken = new Token("NFT-987654", 7); + const fungibleToken = new Token("TEST-123456", 0n); + const nonFungibleToken = new Token("NFT-987654", 7n); assert.equal(tokenComputer.isFungible(fungibleToken), true); assert.equal(tokenComputer.isFungible(nonFungibleToken), false); diff --git a/src/tokens.ts b/src/tokens.ts index e6d18c28..361800ac 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -1,11 +1,10 @@ -import BigNumber from "bignumber.js"; import { ErrInvalidTokenIdentifier } from "./errors"; export class Token { identifier: string; - nonce: BigNumber.Value; + nonce: bigint; - constructor(identifier: string, nonce: BigNumber.Value) { + constructor(identifier: string, nonce: bigint) { this.identifier = identifier; this.nonce = nonce; } @@ -13,9 +12,9 @@ export class Token { export class NextTokenTransfer { token: Token; - amount: BigNumber.Value; + amount: bigint; - constructor(token: Token, amount: BigNumber.Value) { + constructor(token: Token, amount: bigint) { this.token = token; this.amount = amount; } @@ -25,7 +24,7 @@ export class TokenComputer { constructor() {} isFungible(token: Token): boolean { - return token.nonce === 0; + return token.nonce === 0n; } extractNonceFromExtendedIdentifier(identifier: string): number { diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 988c8f41..755529b6 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -22,8 +22,8 @@ describe("test transaction construction", async () => { const plainTransactionNextObject = { sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - gasLimit: 56000, - value: "1000000000000000000", + gasLimit: 56000n, + value: 1000000000000000000n, data: Buffer.from("test"), chainID: "T" }; @@ -32,8 +32,8 @@ describe("test transaction construction", async () => { const transaction = Transaction.fromTransactionNext(nextTransaction); assert.deepEqual(transaction.getSender(), Address.fromBech32(plainTransactionNextObject.sender)); assert.deepEqual(transaction.getReceiver(), Address.fromBech32(plainTransactionNextObject.receiver)); - assert.equal(transaction.getGasLimit().valueOf(), plainTransactionNextObject.gasLimit); - assert.equal(transaction.getValue().toString(), plainTransactionNextObject.value); + assert.equal(transaction.getGasLimit().valueOf().toFixed(0), plainTransactionNextObject.gasLimit.toString()); + assert.equal(transaction.getValue().toString(), plainTransactionNextObject.value.toString()); assert.equal(transaction.getData().toString(), plainTransactionNextObject.data.toString()); assert.equal(transaction.getChainID().valueOf(), plainTransactionNextObject.chainID); assert.equal(transaction.getNonce().valueOf(), 0); diff --git a/src/transaction.ts b/src/transaction.ts index 746482b6..a9a026cf 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -4,7 +4,20 @@ import { Compatibility } from "./compatibility"; import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_VERSION_DEFAULT } from "./constants"; import * as errors from "./errors"; import { Hash } from "./hash"; -import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObject, ISignature, ITransactionNext, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface"; +import { + IAddress, + IChainID, + IGasLimit, + IGasPrice, + INonce, + IPlainTransactionObject, + ISignature, + ITransactionNext, + ITransactionOptions, + ITransactionPayload, + ITransactionValue, + ITransactionVersion, +} from "./interface"; import { INetworkConfig } from "./interfaceOfNetwork"; import { TransactionOptions, TransactionVersion } from "./networkParams"; import { ProtoSerializer } from "./proto"; @@ -19,462 +32,470 @@ const TRANSACTION_HASH_LENGTH = 32; * An abstraction for creating, signing and broadcasting transactions. */ export class Transaction { - /** - * The nonce of the transaction (the account sequence number of the sender). - */ - private nonce: INonce; - - /** - * The value to transfer. - */ - private value: ITransactionValue; - - /** - * The address of the sender. - */ - private sender: IAddress; - - /** - * The address of the receiver. - */ - private readonly receiver: IAddress; - - /** - * The username of the sender. - */ - private senderUsername: string; - - /** - * The username of the receiver. - */ - private receiverUsername: string; - - /** - * The gas price to be used. - */ - private gasPrice: IGasPrice; - - /** - * The maximum amount of gas to be consumed when processing the transaction. - */ - private gasLimit: IGasLimit; - - /** - * The payload of the transaction. - */ - private readonly data: ITransactionPayload; - - /** - * The chain ID of the Network (e.g. "1" for Mainnet). - */ - private chainID: IChainID; - - /** - * The version, required by the Network in order to correctly interpret the contents of the transaction. - * @deprecated Use getVersion() and setVersion() instead. - */ - version: TransactionVersion; - - /** - * The options field, useful for describing different settings available for transactions - * @deprecated Use getOptions() and setOptions() instead. - */ - options: TransactionOptions; - - /** - * The address of the guardian. - */ - private guardian: IAddress; - - /** - * The signature. - */ - private signature: Buffer; - - /** - * The signature of the guardian. - */ - private guardianSignature: Buffer; - - /** - * The transaction hash, also used as a transaction identifier. - */ - private hash: TransactionHash; - - /** - * Creates a new Transaction object. - */ - public constructor({ - nonce, - value, - sender, - receiver, - senderUsername, - receiverUsername, - gasPrice, - gasLimit, - data, - chainID, - version, - options, - guardian, - }: { - nonce?: INonce; - value?: ITransactionValue; - sender: IAddress; - receiver: IAddress; - senderUsername?: string; - receiverUsername?: string; - gasPrice?: IGasPrice; - gasLimit: IGasLimit; - data?: ITransactionPayload; - chainID: IChainID; - version?: ITransactionVersion; - options?: ITransactionOptions; - guardian?: IAddress; - }) { - this.nonce = nonce || 0; - this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; - this.sender = sender; - this.receiver = receiver; - this.senderUsername = senderUsername || ""; - this.receiverUsername = receiverUsername || ""; - this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; - this.gasLimit = gasLimit; - this.data = data || new TransactionPayload(); - this.chainID = chainID; - this.version = version ? new TransactionVersion(version.valueOf()) : TransactionVersion.withDefaultVersion(); - this.options = options ? new TransactionOptions(options.valueOf()) : TransactionOptions.withDefaultOptions(); - this.guardian = guardian || Address.empty(); - - this.signature = Buffer.from([]); - this.guardianSignature = Buffer.from([]); - this.hash = TransactionHash.empty(); - } - - getNonce(): INonce { - return this.nonce; - } - - /** - * Sets the account sequence number of the sender. Must be done prior signing. - */ - setNonce(nonce: INonce) { - this.nonce = nonce; - } - - getValue(): ITransactionValue { - return this.value; - } - - setValue(value: ITransactionValue) { - this.value = value; - } - - getSender(): IAddress { - return this.sender; - } - - setSender(sender: IAddress) { - this.sender = sender; - } - - getReceiver(): IAddress { - return this.receiver; - } - - getSenderUsername(): string { - return this.senderUsername; - } - - setSenderUsername(senderUsername: string) { - this.senderUsername = senderUsername; - } - - getReceiverUsername(): string { - return this.receiverUsername; - } - - setReceiverUsername(receiverUsername: string) { - this.receiverUsername = receiverUsername; - } - - getGuardian(): IAddress { - return this.guardian; - } - - getGasPrice(): IGasPrice { - return this.gasPrice; - } - - setGasPrice(gasPrice: IGasPrice) { - this.gasPrice = gasPrice; - } - - getGasLimit(): IGasLimit { - return this.gasLimit; - } - - setGasLimit(gasLimit: IGasLimit) { - this.gasLimit = gasLimit; - } - - getData(): ITransactionPayload { - return this.data; - } - - getChainID(): IChainID { - return this.chainID; - } - - setChainID(chainID: IChainID) { - this.chainID = chainID; - } - - getVersion(): TransactionVersion { - return this.version; - } - - setVersion(version: ITransactionVersion) { - this.version = new TransactionVersion(version.valueOf()); - } - - getOptions(): TransactionOptions { - // Make sure that "sdk-core v12" is compatible (for a while) with (older) libraries that were previously setting the (soon to be private) "options" field directly, - // instead of using the "setOptions()" method. - const options = new TransactionOptions(this.options.valueOf()); - return options; - } - - setOptions(options: ITransactionOptions) { - this.options = new TransactionOptions(options.valueOf()); - } - - getSignature(): Buffer { - return this.signature; - } - - getGuardianSignature(): Buffer { - return this.guardianSignature; - } - - setGuardian(guardian: IAddress) { - this.guardian = guardian; - } - - getHash(): TransactionHash { - guardNotEmpty(this.hash, "hash"); - return this.hash; - } - - /** - * Serializes a transaction to a sequence of bytes, ready to be signed. - * This function is called internally by signers. - */ - serializeForSigning(): Buffer { - // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash - let plain = this.toPlainObject(); - // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) - if (plain.signature) { - delete plain.signature; - } - - if (plain.guardianSignature) { - delete plain.guardianSignature; - } - - if (!plain.guardian) { - delete plain.guardian - } - - let serialized = JSON.stringify(plain); - - return Buffer.from(serialized); - } - - /** - * Checks the integrity of the guarded transaction - */ - isGuardedTransaction(): boolean { - const hasGuardian = this.guardian.bech32().length > 0; - const hasGuardianSignature = this.guardianSignature.length > 0; - return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; - } - - /** - * Converts the transaction object into a ready-to-serialize, plain JavaScript object. - * This function is called internally within the signing procedure. - */ - toPlainObject(): IPlainTransactionObject { - const plainObject = { - nonce: this.nonce.valueOf(), - value: this.value.toString(), - receiver: this.receiver.bech32(), - sender: this.sender.bech32(), - senderUsername: this.senderUsername ? Buffer.from(this.senderUsername).toString("base64") : undefined, - receiverUsername: this.receiverUsername ? Buffer.from(this.receiverUsername).toString("base64") : undefined, - gasPrice: this.gasPrice.valueOf(), - gasLimit: this.gasLimit.valueOf(), - data: this.data.length() == 0 ? undefined : this.data.encoded(), - chainID: this.chainID.valueOf(), - version: this.getVersion().valueOf(), - options: this.getOptions().valueOf() == 0 ? undefined : this.getOptions().valueOf(), - guardian: this.guardian?.bech32() ? (this.guardian.bech32() == "" ? undefined : this.guardian.bech32()) : undefined, - signature: this.signature.toString("hex") ? this.signature.toString("hex") : undefined, - guardianSignature: this.guardianSignature.toString("hex") ? this.guardianSignature.toString("hex") : undefined, - }; - - Compatibility.guardAddressIsSetAndNonZero(new Address(plainObject.sender), "'sender' of transaction", "pass the actual sender to the Transaction constructor") - - return plainObject; - } - - /** - * Converts a plain object transaction into a Transaction Object. - * - * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() - */ - static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { - const tx = new Transaction({ - nonce: Number(plainObjectTransaction.nonce), - value: new BigNumber(plainObjectTransaction.value).toFixed(0), - receiver: Address.fromString(plainObjectTransaction.receiver), - receiverUsername: plainObjectTransaction.receiverUsername ? Buffer.from(plainObjectTransaction.receiverUsername, "base64").toString() : undefined, - sender: Address.fromString(plainObjectTransaction.sender), - senderUsername: plainObjectTransaction.senderUsername ? Buffer.from(plainObjectTransaction.senderUsername, "base64").toString() : undefined, - guardian: plainObjectTransaction.guardian ? Address.fromString(plainObjectTransaction.guardian) : undefined, - gasPrice: Number(plainObjectTransaction.gasPrice), - gasLimit: Number(plainObjectTransaction.gasLimit), - data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), - chainID: String(plainObjectTransaction.chainID), - version: new TransactionVersion(plainObjectTransaction.version), - options: plainObjectTransaction.options != null ? new TransactionOptions(plainObjectTransaction.options) : undefined - }); - - if (plainObjectTransaction.signature) { - tx.applySignature( - new Signature(plainObjectTransaction.signature), - ); - } - - if (plainObjectTransaction.guardianSignature) { - tx.applyGuardianSignature( - new Signature(plainObjectTransaction.guardianSignature) - ); - } - - return tx; - } - - /** - * Applies the signature on the transaction. - * - * @param signature The signature, as computed by a signer. - */ - applySignature(signature: ISignature | Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - this.hash = TransactionHash.compute(this); - } - - /** - * Applies the guardian signature on the transaction. - * - * @param guardianSignature The signature, as computed by a signer. - */ - applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { - this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); - this.hash = TransactionHash.compute(this); - } - - /** - * Converts a transaction to a ready-to-broadcast object. - * Called internally by the network provider. - */ - toSendable(): any { - return this.toPlainObject(); - } - - /** - * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties - * @param networkConfig {@link NetworkConfig} - */ - computeFee(networkConfig: INetworkConfig): BigNumber { - let moveBalanceGas = - networkConfig.MinGasLimit.valueOf() + - this.data.length() * networkConfig.GasPerDataByte.valueOf(); - if (moveBalanceGas > this.gasLimit.valueOf()) { - throw new errors.ErrNotEnoughGas(this.gasLimit.valueOf()); - } - - let gasPrice = new BigNumber(this.gasPrice.valueOf()); - let feeForMove = new BigNumber(moveBalanceGas).multipliedBy(gasPrice); - if (moveBalanceGas === this.gasLimit.valueOf()) { - return feeForMove; - } - - let diff = new BigNumber(this.gasLimit.valueOf() - moveBalanceGas); - let modifiedGasPrice = gasPrice.multipliedBy( - new BigNumber(networkConfig.GasPriceModifier.valueOf()) - ); - let processingFee = diff.multipliedBy(modifiedGasPrice); - - return feeForMove.plus(processingFee); - } - - /** - * Creates a new Transaction object from a TransactionNext object. - */ - static fromTransactionNext(transaction: ITransactionNext): Transaction { - const tx = new Transaction({ - sender: Address.fromBech32(transaction.sender), - receiver: Address.fromBech32(transaction.receiver), - gasLimit: new BigNumber(transaction.gasLimit).toNumber(), - chainID: transaction.chainID, - value: new BigNumber(transaction.value).toFixed(0), - data: new TransactionPayload(Buffer.from(transaction.data)), - nonce: Number(transaction.nonce), - gasPrice: Number(transaction.gasPrice), - receiverUsername: transaction.receiverUsername, - senderUsername: transaction.senderUsername, - options: transaction.options, - version: transaction.version - }); - - if (transaction.guardian) { - tx.guardian = Address.fromBech32(transaction.guardian) - } - - if (transaction.signature.length) { - tx.applySignature(transaction.signature); - } - - if (transaction.guardianSignature.length) { - tx.applyGuardianSignature(transaction.guardianSignature); - } - - return tx; - } + /** + * The nonce of the transaction (the account sequence number of the sender). + */ + private nonce: INonce; + + /** + * The value to transfer. + */ + private value: ITransactionValue; + + /** + * The address of the sender. + */ + private sender: IAddress; + + /** + * The address of the receiver. + */ + private readonly receiver: IAddress; + + /** + * The username of the sender. + */ + private senderUsername: string; + + /** + * The username of the receiver. + */ + private receiverUsername: string; + + /** + * The gas price to be used. + */ + private gasPrice: IGasPrice; + + /** + * The maximum amount of gas to be consumed when processing the transaction. + */ + private gasLimit: IGasLimit; + + /** + * The payload of the transaction. + */ + private readonly data: ITransactionPayload; + + /** + * The chain ID of the Network (e.g. "1" for Mainnet). + */ + private chainID: IChainID; + + /** + * The version, required by the Network in order to correctly interpret the contents of the transaction. + * @deprecated Use getVersion() and setVersion() instead. + */ + version: TransactionVersion; + + /** + * The options field, useful for describing different settings available for transactions + * @deprecated Use getOptions() and setOptions() instead. + */ + options: TransactionOptions; + + /** + * The address of the guardian. + */ + private guardian: IAddress; + + /** + * The signature. + */ + private signature: Buffer; + + /** + * The signature of the guardian. + */ + private guardianSignature: Buffer; + + /** + * The transaction hash, also used as a transaction identifier. + */ + private hash: TransactionHash; + + /** + * Creates a new Transaction object. + */ + public constructor({ + nonce, + value, + sender, + receiver, + senderUsername, + receiverUsername, + gasPrice, + gasLimit, + data, + chainID, + version, + options, + guardian, + }: { + nonce?: INonce; + value?: ITransactionValue; + sender: IAddress; + receiver: IAddress; + senderUsername?: string; + receiverUsername?: string; + gasPrice?: IGasPrice; + gasLimit: IGasLimit; + data?: ITransactionPayload; + chainID: IChainID; + version?: ITransactionVersion; + options?: ITransactionOptions; + guardian?: IAddress; + }) { + this.nonce = nonce || 0; + this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; + this.sender = sender; + this.receiver = receiver; + this.senderUsername = senderUsername || ""; + this.receiverUsername = receiverUsername || ""; + this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; + this.gasLimit = gasLimit; + this.data = data || new TransactionPayload(); + this.chainID = chainID; + this.version = version ? new TransactionVersion(version.valueOf()) : TransactionVersion.withDefaultVersion(); + this.options = options ? new TransactionOptions(options.valueOf()) : TransactionOptions.withDefaultOptions(); + this.guardian = guardian || Address.empty(); + + this.signature = Buffer.from([]); + this.guardianSignature = Buffer.from([]); + this.hash = TransactionHash.empty(); + } + + getNonce(): INonce { + return this.nonce; + } + + /** + * Sets the account sequence number of the sender. Must be done prior signing. + */ + setNonce(nonce: INonce) { + this.nonce = nonce; + } + + getValue(): ITransactionValue { + return this.value; + } + + setValue(value: ITransactionValue) { + this.value = value; + } + + getSender(): IAddress { + return this.sender; + } + + setSender(sender: IAddress) { + this.sender = sender; + } + + getReceiver(): IAddress { + return this.receiver; + } + + getSenderUsername(): string { + return this.senderUsername; + } + + setSenderUsername(senderUsername: string) { + this.senderUsername = senderUsername; + } + + getReceiverUsername(): string { + return this.receiverUsername; + } + + setReceiverUsername(receiverUsername: string) { + this.receiverUsername = receiverUsername; + } + + getGuardian(): IAddress { + return this.guardian; + } + + getGasPrice(): IGasPrice { + return this.gasPrice; + } + + setGasPrice(gasPrice: IGasPrice) { + this.gasPrice = gasPrice; + } + + getGasLimit(): IGasLimit { + return this.gasLimit; + } + + setGasLimit(gasLimit: IGasLimit) { + this.gasLimit = gasLimit; + } + + getData(): ITransactionPayload { + return this.data; + } + + getChainID(): IChainID { + return this.chainID; + } + + setChainID(chainID: IChainID) { + this.chainID = chainID; + } + + getVersion(): TransactionVersion { + return this.version; + } + + setVersion(version: ITransactionVersion) { + this.version = new TransactionVersion(version.valueOf()); + } + + getOptions(): TransactionOptions { + // Make sure that "sdk-core v12" is compatible (for a while) with (older) libraries that were previously setting the (soon to be private) "options" field directly, + // instead of using the "setOptions()" method. + const options = new TransactionOptions(this.options.valueOf()); + return options; + } + + setOptions(options: ITransactionOptions) { + this.options = new TransactionOptions(options.valueOf()); + } + + getSignature(): Buffer { + return this.signature; + } + + getGuardianSignature(): Buffer { + return this.guardianSignature; + } + + setGuardian(guardian: IAddress) { + this.guardian = guardian; + } + + getHash(): TransactionHash { + guardNotEmpty(this.hash, "hash"); + return this.hash; + } + + /** + * Serializes a transaction to a sequence of bytes, ready to be signed. + * This function is called internally by signers. + */ + serializeForSigning(): Buffer { + // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash + let plain = this.toPlainObject(); + // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) + if (plain.signature) { + delete plain.signature; + } + + if (plain.guardianSignature) { + delete plain.guardianSignature; + } + + if (!plain.guardian) { + delete plain.guardian; + } + + let serialized = JSON.stringify(plain); + + return Buffer.from(serialized); + } + + /** + * Checks the integrity of the guarded transaction + */ + isGuardedTransaction(): boolean { + const hasGuardian = this.guardian.bech32().length > 0; + const hasGuardianSignature = this.guardianSignature.length > 0; + return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; + } + + /** + * Converts the transaction object into a ready-to-serialize, plain JavaScript object. + * This function is called internally within the signing procedure. + */ + toPlainObject(): IPlainTransactionObject { + const plainObject = { + nonce: this.nonce.valueOf(), + value: this.value.toString(), + receiver: this.receiver.bech32(), + sender: this.sender.bech32(), + senderUsername: this.senderUsername ? Buffer.from(this.senderUsername).toString("base64") : undefined, + receiverUsername: this.receiverUsername ? Buffer.from(this.receiverUsername).toString("base64") : undefined, + gasPrice: this.gasPrice.valueOf(), + gasLimit: this.gasLimit.valueOf(), + data: this.data.length() == 0 ? undefined : this.data.encoded(), + chainID: this.chainID.valueOf(), + version: this.getVersion().valueOf(), + options: this.getOptions().valueOf() == 0 ? undefined : this.getOptions().valueOf(), + guardian: this.guardian?.bech32() + ? this.guardian.bech32() == "" + ? undefined + : this.guardian.bech32() + : undefined, + signature: this.signature.toString("hex") ? this.signature.toString("hex") : undefined, + guardianSignature: this.guardianSignature.toString("hex") + ? this.guardianSignature.toString("hex") + : undefined, + }; + + Compatibility.guardAddressIsSetAndNonZero( + new Address(plainObject.sender), + "'sender' of transaction", + "pass the actual sender to the Transaction constructor", + ); + + return plainObject; + } + + /** + * Converts a plain object transaction into a Transaction Object. + * + * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() + */ + static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { + const tx = new Transaction({ + nonce: Number(plainObjectTransaction.nonce), + value: new BigNumber(plainObjectTransaction.value).toFixed(0), + receiver: Address.fromString(plainObjectTransaction.receiver), + receiverUsername: plainObjectTransaction.receiverUsername + ? Buffer.from(plainObjectTransaction.receiverUsername, "base64").toString() + : undefined, + sender: Address.fromString(plainObjectTransaction.sender), + senderUsername: plainObjectTransaction.senderUsername + ? Buffer.from(plainObjectTransaction.senderUsername, "base64").toString() + : undefined, + guardian: plainObjectTransaction.guardian ? Address.fromString(plainObjectTransaction.guardian) : undefined, + gasPrice: Number(plainObjectTransaction.gasPrice), + gasLimit: Number(plainObjectTransaction.gasLimit), + data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), + chainID: String(plainObjectTransaction.chainID), + version: new TransactionVersion(plainObjectTransaction.version), + options: + plainObjectTransaction.options != null + ? new TransactionOptions(plainObjectTransaction.options) + : undefined, + }); + + if (plainObjectTransaction.signature) { + tx.applySignature(new Signature(plainObjectTransaction.signature)); + } + + if (plainObjectTransaction.guardianSignature) { + tx.applyGuardianSignature(new Signature(plainObjectTransaction.guardianSignature)); + } + + return tx; + } + + /** + * Applies the signature on the transaction. + * + * @param signature The signature, as computed by a signer. + */ + applySignature(signature: ISignature | Uint8Array) { + this.signature = interpretSignatureAsBuffer(signature); + this.hash = TransactionHash.compute(this); + } + + /** + * Applies the guardian signature on the transaction. + * + * @param guardianSignature The signature, as computed by a signer. + */ + applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { + this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); + this.hash = TransactionHash.compute(this); + } + + /** + * Converts a transaction to a ready-to-broadcast object. + * Called internally by the network provider. + */ + toSendable(): any { + return this.toPlainObject(); + } + + /** + * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties + * @param networkConfig {@link NetworkConfig} + */ + computeFee(networkConfig: INetworkConfig): BigNumber { + let moveBalanceGas = + networkConfig.MinGasLimit.valueOf() + this.data.length() * networkConfig.GasPerDataByte.valueOf(); + if (moveBalanceGas > this.gasLimit.valueOf()) { + throw new errors.ErrNotEnoughGas(this.gasLimit.valueOf()); + } + + let gasPrice = new BigNumber(this.gasPrice.valueOf()); + let feeForMove = new BigNumber(moveBalanceGas).multipliedBy(gasPrice); + if (moveBalanceGas === this.gasLimit.valueOf()) { + return feeForMove; + } + + let diff = new BigNumber(this.gasLimit.valueOf() - moveBalanceGas); + let modifiedGasPrice = gasPrice.multipliedBy(new BigNumber(networkConfig.GasPriceModifier.valueOf())); + let processingFee = diff.multipliedBy(modifiedGasPrice); + + return feeForMove.plus(processingFee); + } + + /** + * Creates a new Transaction object from a TransactionNext object. + */ + static fromTransactionNext(transaction: ITransactionNext): Transaction { + const tx = new Transaction({ + sender: Address.fromBech32(transaction.sender), + receiver: Address.fromBech32(transaction.receiver), + gasLimit: Number(transaction.gasLimit), + chainID: transaction.chainID, + value: new BigNumber(transaction.value.toString()).toFixed(0), + data: new TransactionPayload(Buffer.from(transaction.data)), + nonce: Number(transaction.nonce), + gasPrice: Number(transaction.gasPrice), + receiverUsername: transaction.receiverUsername, + senderUsername: transaction.senderUsername, + options: transaction.options, + version: transaction.version, + }); + + if (transaction.guardian) { + tx.guardian = Address.fromBech32(transaction.guardian); + } + + if (transaction.signature.length) { + tx.applySignature(transaction.signature); + } + + if (transaction.guardianSignature.length) { + tx.applyGuardianSignature(transaction.guardianSignature); + } + + return tx; + } } /** * An abstraction for handling and computing transaction hashes. */ export class TransactionHash extends Hash { - constructor(hash: string) { - super(hash); - } - - /** - * Computes the hash of a transaction. - */ - static compute(transaction: Transaction): TransactionHash { - let serializer = new ProtoSerializer(); - let buffer = serializer.serializeTransaction(transaction); - let hash = createTransactionHasher(TRANSACTION_HASH_LENGTH) - .update(buffer) - .digest("hex"); - return new TransactionHash(hash); - } + constructor(hash: string) { + super(hash); + } + + /** + * Computes the hash of a transaction. + */ + static compute(transaction: Transaction): TransactionHash { + let serializer = new ProtoSerializer(); + let buffer = serializer.serializeTransaction(transaction); + let hash = createTransactionHasher(TRANSACTION_HASH_LENGTH).update(buffer).digest("hex"); + return new TransactionHash(hash); + } } /** @@ -482,103 +503,103 @@ export class TransactionHash extends Hash { * Will replace the {@link Transaction} class in the future. */ export class TransactionNext { - /** - * The nonce of the transaction (the account sequence number of the sender). - */ - public nonce: BigNumber.Value; - - /** - * The value to transfer. - */ - public value: BigNumber.Value; - - /** - * The address of the sender. - */ - public sender: string; - - /** - * The address of the receiver. - */ - public receiver: string; - - /** - * The username of the sender. - */ - public senderUsername: string; - - /** - * The username of the receiver. - */ - public receiverUsername: string; - - /** - * The gas price to be used. - */ - public gasPrice: BigNumber.Value; - - /** - * The maximum amount of gas to be consumed when processing the transaction. - */ - public gasLimit: BigNumber.Value; - - /** - * The payload of the transaction. - */ - public data: Uint8Array; - - /** - * The chain ID of the Network (e.g. "1" for Mainnet). - */ - public chainID: string; - - /** - * The version, required by the Network in order to correctly interpret the contents of the transaction. - */ - public version: number; - - /** - * The options field of the transactions. - */ - public options: number; - - /** - * The address of the guardian. - */ - public guardian: string; - - /** - * The signature. - */ - public signature: Uint8Array; - - /** - * The signature of the guardian. - */ - public guardianSignature: Uint8Array; - - /** - * Creates a new Transaction object. - */ + /** + * The nonce of the transaction (the account sequence number of the sender). + */ + public nonce: bigint; + + /** + * The value to transfer. + */ + public value: bigint; + + /** + * The address of the sender. + */ + public sender: string; + + /** + * The address of the receiver. + */ + public receiver: string; + + /** + * The username of the sender. + */ + public senderUsername: string; + + /** + * The username of the receiver. + */ + public receiverUsername: string; + + /** + * The gas price to be used. + */ + public gasPrice: bigint; + + /** + * The maximum amount of gas to be consumed when processing the transaction. + */ + public gasLimit: bigint; + + /** + * The payload of the transaction. + */ + public data: Uint8Array; + + /** + * The chain ID of the Network (e.g. "1" for Mainnet). + */ + public chainID: string; + + /** + * The version, required by the Network in order to correctly interpret the contents of the transaction. + */ + public version: number; + + /** + * The options field of the transactions. + */ + public options: number; + + /** + * The address of the guardian. + */ + public guardian: string; + + /** + * The signature. + */ + public signature: Uint8Array; + + /** + * The signature of the guardian. + */ + public guardianSignature: Uint8Array; + + /** + * Creates a new Transaction object. + */ public constructor(init: Partial) { - this.nonce = 0; - this.value = new BigNumber(0); - this.sender = ""; - this.receiver = ""; - this.senderUsername = ""; - this.receiverUsername = ""; - this.gasPrice = new BigNumber(TRANSACTION_MIN_GAS_PRICE); - this.gasLimit = 0; - this.data = new Uint8Array(); - this.chainID = ""; - this.version = TRANSACTION_VERSION_DEFAULT; - this.options = TRANSACTION_OPTIONS_DEFAULT; - this.guardian = ""; - - this.signature = new Uint8Array(); - this.guardianSignature = new Uint8Array(); - - Object.assign(this, init); + this.nonce = 0n; + this.value = 0n; + this.sender = ""; + this.receiver = ""; + this.senderUsername = ""; + this.receiverUsername = ""; + this.gasPrice = BigInt(TRANSACTION_MIN_GAS_PRICE); + this.gasLimit = 0n; + this.data = new Uint8Array(); + this.chainID = ""; + this.version = TRANSACTION_VERSION_DEFAULT; + this.options = TRANSACTION_OPTIONS_DEFAULT; + this.guardian = ""; + + this.signature = new Uint8Array(); + this.guardianSignature = new Uint8Array(); + + Object.assign(this, init); } } @@ -586,80 +607,89 @@ export class TransactionNext { * An utilitary class meant to work together with the {@link TransactionNext} class. */ export class TransactionComputer { - constructor() { } - - computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): BigNumber { - const moveBalanceGas = new BigNumber( - networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte); - if (moveBalanceGas > transaction.gasLimit) { - throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); + constructor() {} + + computeTransactionFee(transaction: ITransactionNext, networkConfig: INetworkConfig): bigint { + const moveBalanceGas = BigInt( + networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte, + ); + if (moveBalanceGas > transaction.gasLimit) { + throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); + } + + const gasPrice = transaction.gasPrice; + const feeForMove = moveBalanceGas * gasPrice; + if (moveBalanceGas === transaction.gasLimit) { + return feeForMove; + } + + const diff = transaction.gasLimit - moveBalanceGas; + const modifiedGasPrice = BigInt( + new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0), + ); + const processingFee = diff * modifiedGasPrice; + + return feeForMove + processingFee; } - const gasPrice = new BigNumber(transaction.gasPrice); - const feeForMove = moveBalanceGas.multipliedBy(gasPrice); - if (moveBalanceGas === transaction.gasLimit) { - return feeForMove; - } + computeBytesForSigning(transaction: ITransactionNext): Uint8Array { + const plainTransaction = this.toPlainObject(transaction); - const diff = new BigNumber(transaction.gasLimit).minus(moveBalanceGas); - const modifiedGasPrice = gasPrice.multipliedBy( - new BigNumber(networkConfig.GasPriceModifier) - ); - const processingFee = diff.multipliedBy(modifiedGasPrice); + if (plainTransaction.signature) { + delete plainTransaction.signature; + } - return feeForMove.plus(processingFee); - } + if (plainTransaction.guardianSignature) { + delete plainTransaction.guardianSignature; + } - computeBytesForSigning(transaction: ITransactionNext): Uint8Array { - const plainTransaction = this.toPlainObject(transaction); - - if (plainTransaction.signature) { - delete plainTransaction.signature; - } + if (!plainTransaction.guardian) { + delete plainTransaction.guardian; + } - if (plainTransaction.guardianSignature) { - delete plainTransaction.guardianSignature; - } + const serialized = JSON.stringify(plainTransaction); - if (!plainTransaction.guardian) { - delete plainTransaction.guardian + return Buffer.from(serialized); } - const serialized = JSON.stringify(plainTransaction); - - return new Uint8Array(Buffer.from(serialized)); - } - - computeTransactionHash(transaction: ITransactionNext): Uint8Array { - let serializer = new ProtoSerializer(); + computeTransactionHash(transaction: ITransactionNext): Uint8Array { + let serializer = new ProtoSerializer(); - const tx = Transaction.fromTransactionNext(transaction); - const buffer = serializer.serializeTransaction(tx); - const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH) - .update(buffer) - .digest("hex"); + const tx = Transaction.fromTransactionNext(transaction); + const buffer = serializer.serializeTransaction(tx); + const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH).update(buffer).digest("hex"); - return Buffer.from(hash, "hex"); - } - - private toPlainObject(transaction: ITransactionNext) { - return { - nonce: Number(transaction.nonce), - value: new BigNumber(transaction.value).toFixed(0), - receiver: transaction.receiver, - sender: transaction.sender, - senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined, - receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined, - gasPrice: Number(transaction.gasPrice), - gasLimit: Number(transaction.gasLimit), - data: transaction.data && transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"), - chainID: transaction.chainID, - version: transaction.version, - options: transaction.options ? transaction.options : undefined, - guardian: transaction.guardian ? transaction.guardian : undefined, - signature: transaction.signature.length == 0 ? undefined : Buffer.from(transaction.signature).toString("hex"), - guardianSignature: transaction.guardianSignature.length == 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex") + return Buffer.from(hash, "hex"); } - } + private toPlainObject(transaction: ITransactionNext) { + return { + nonce: Number(transaction.nonce), + value: transaction.value.toString(), + receiver: transaction.receiver, + sender: transaction.sender, + senderUsername: transaction.senderUsername + ? Buffer.from(transaction.senderUsername).toString("base64") + : undefined, + receiverUsername: transaction.receiverUsername + ? Buffer.from(transaction.receiverUsername).toString("base64") + : undefined, + gasPrice: Number(transaction.gasPrice), + gasLimit: Number(transaction.gasLimit), + data: + transaction.data && transaction.data.length === 0 + ? undefined + : Buffer.from(transaction.data).toString("base64"), + chainID: transaction.chainID, + version: transaction.version, + options: transaction.options ? transaction.options : undefined, + guardian: transaction.guardian ? transaction.guardian : undefined, + signature: + transaction.signature.length == 0 ? undefined : Buffer.from(transaction.signature).toString("hex"), + guardianSignature: + transaction.guardianSignature.length == 0 + ? undefined + : Buffer.from(transaction.guardianSignature).toString("hex"), + }; + } } diff --git a/src/transactionNext.spec.ts b/src/transactionNext.spec.ts index 3649ad01..78a46832 100644 --- a/src/transactionNext.spec.ts +++ b/src/transactionNext.spec.ts @@ -1,7 +1,6 @@ import { assert } from "chai"; import { TestWallet, loadTestWallets } from "./testutils"; import { TransactionNext, TransactionComputer, Transaction } from "./transaction"; -import BigNumber from "bignumber.js"; import { ProtoSerializer } from "./proto"; class NetworkConfig { @@ -35,10 +34,10 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: sender, receiver: receiver, - gasLimit: 50000, - value: 0, + gasLimit: 50000n, + value: 0n, version: 2, - nonce: 89, + nonce: 89n, }); let serializedTransactionBytes = transactionComputer.computeBytesForSigning(transaction); @@ -46,17 +45,17 @@ describe("test transaction next", async () => { assert.equal( serializedTransaction, - `{"nonce":89,"value":"0","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":50000,"chainID":"D","version":2}` + `{"nonce":89,"value":"0","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":50000,"chainID":"D","version":2}`, ); transaction = new TransactionNext({ chainID: networkConfig.ChainID, sender: sender, receiver: receiver, - gasLimit: 70000, - value: new BigNumber("1000000000000000000"), + gasLimit: 70000n, + value: 1000000000000000000n, version: 2, - nonce: 90, + nonce: 90n, data: new Uint8Array(Buffer.from("hello")), }); @@ -65,7 +64,7 @@ describe("test transaction next", async () => { assert.equal( serializedTransaction, - `{"nonce":90,"value":"1000000000000000000","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":70000,"data":"aGVsbG8=","chainID":"D","version":2}` + `{"nonce":90,"value":"1000000000000000000","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":70000,"data":"aGVsbG8=","chainID":"D","version":2}`, ); }); @@ -74,22 +73,22 @@ describe("test transaction next", async () => { chainID: "T", sender: wallets.carol.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 50000, - value: new BigNumber("1000000000000000000"), + gasLimit: 50000n, + value: 1000000000000000000n, version: 2, - nonce: 204, + nonce: 204n, senderUsername: "carol", receiverUsername: "alice", }); transaction.signature = await wallets.carol.signer.sign( - Buffer.from(transactionComputer.computeBytesForSigning(transaction)) + Buffer.from(transactionComputer.computeBytesForSigning(transaction)), ); console.log(Buffer.from(transaction.signature).toString("hex")); assert.equal( Buffer.from(transaction.signature).toString("hex"), - "51e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06" + "51e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06", ); }); @@ -98,21 +97,21 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: wallets.alice.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 100000, - value: new BigNumber("1000000000000"), + gasLimit: 100000n, + value: 1000000000000n, version: 2, - nonce: 17243, + nonce: 17243n, data: Buffer.from("testtx"), }); transaction.signature = Buffer.from( "eaa9e4dfbd21695d9511e9754bde13e90c5cfb21748a339a79be11f744c71872e9fe8e73c6035c413f5f08eef09e5458e9ea6fc315ff4da0ab6d000b450b2a07", - "hex" + "hex", ); const hash = transactionComputer.computeTransactionHash(transaction); assert.equal( Buffer.from(hash).toString("hex"), - "169b76b752b220a76a93aeebc462a1192db1dc2ec9d17e6b4d7b0dcc91792f03" + "169b76b752b220a76a93aeebc462a1192db1dc2ec9d17e6b4d7b0dcc91792f03", ); }); @@ -121,23 +120,23 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: wallets.alice.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 100000, - value: new BigNumber("1000000000000"), + gasLimit: 100000n, + value: 1000000000000n, version: 2, - nonce: 17244, + nonce: 17244n, data: Buffer.from("testtx"), senderUsername: "alice", receiverUsername: "alice", }); transaction.signature = Buffer.from( "807bcd7de5553ea6dfc57c0510e84d46813c5963d90fec50991c500091408fcf6216dca48dae16a579a1611ed8b2834bae8bd0027dc17eb557963f7151b82c07", - "hex" + "hex", ); const hash = transactionComputer.computeTransactionHash(transaction); assert.equal( Buffer.from(hash).toString("hex"), - "41b5acf7ebaf4a9165a64206b6ebc02021b3adda55ffb2a2698aac2e7004dc29" + "41b5acf7ebaf4a9165a64206b6ebc02021b3adda55ffb2a2698aac2e7004dc29", ); }); @@ -146,7 +145,7 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: wallets.alice.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 50000, + gasLimit: 50000n, data: Buffer.from("toolittlegaslimit"), }); @@ -160,8 +159,8 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: wallets.alice.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 20, - gasPrice: 500, + gasLimit: 20n, + gasPrice: 500n, }); const config = new NetworkConfig(10); @@ -174,8 +173,8 @@ describe("test transaction next", async () => { chainID: networkConfig.ChainID, sender: wallets.alice.address.bech32(), receiver: wallets.alice.address.bech32(), - gasLimit: 12010, - gasPrice: 500, + gasLimit: 12010n, + gasPrice: 500n, data: Buffer.from("testdata"), }); @@ -191,18 +190,18 @@ describe("test transaction next", async () => { chainID: "local-testnet", sender: alice.address.bech32(), receiver: wallets.bob.address.bech32(), - gasLimit: 150000, - gasPrice: 1000000000, + gasLimit: 150000n, + gasPrice: 1000000000n, data: new Uint8Array(Buffer.from("test data field")), version: 2, options: 2, - nonce: 92, - value: new BigNumber("123456789000000000000000000000"), + nonce: 92n, + value: 123456789000000000000000000000n, guardian: "erd1x23lzn8483xs2su4fak0r0dqx6w38enpmmqf2yrkylwq7mfnvyhsxqw57y", }); transaction.guardianSignature = new Uint8Array(64); transaction.signature = new Uint8Array( - await alice.signer.sign(Buffer.from(transactionComputer.computeBytesForSigning(transaction))) + await alice.signer.sign(Buffer.from(transactionComputer.computeBytesForSigning(transaction))), ); const oldTransaction = Transaction.fromTransactionNext(transaction); @@ -211,13 +210,13 @@ describe("test transaction next", async () => { let buffer = serializer.serializeTransaction(oldTransaction); assert.equal( buffer.toString("hex"), - "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340f093094a0f746573742064617461206669656c64520d6c6f63616c2d746573746e657458026240e574d78b19e1481a6b9575c162e66f2f906a3178aec537509356385c4f1a5330a9b73a87a456fc6d7041e93b5f8a1231a92fb390174872a104a0929215600c0c6802722032a3f14cf53c4d0543954f6cf1bda0369d13e661dec095107627dc0f6d33612f7a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340f093094a0f746573742064617461206669656c64520d6c6f63616c2d746573746e657458026240e574d78b19e1481a6b9575c162e66f2f906a3178aec537509356385c4f1a5330a9b73a87a456fc6d7041e93b5f8a1231a92fb390174872a104a0929215600c0c6802722032a3f14cf53c4d0543954f6cf1bda0369d13e661dec095107627dc0f6d33612f7a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", ); const txHash = transactionComputer.computeTransactionHash(transaction); assert.equal( Buffer.from(txHash).toString("hex"), - "242022e9dcfa0ee1d8199b0043314dbda8601619f70069ebc441b9f03349a35c" + "242022e9dcfa0ee1d8199b0043314dbda8601619f70069ebc441b9f03349a35c", ); }); }); diff --git a/src/transactionsFactories/delegationTransactionsFactory.spec.ts b/src/transactionsFactories/delegationTransactionsFactory.spec.ts index a36a0334..465f4807 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.spec.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.spec.ts @@ -1,4 +1,3 @@ -import BigNumber from "bignumber.js"; import { Address } from "../address"; import { DelegationTransactionsFactory } from "./delegationTransactionsFactory"; import { assert } from "chai"; @@ -12,9 +11,9 @@ describe("test delegation transactions factory", function () { it("should create 'TransactionNext' for new delegation contract", async function () { const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); - const delagationCap = "5000000000000000000000"; - const serviceFee = 10; - const value = new BigNumber("1250000000000000000000"); + const delagationCap = 5000000000000000000000n; + const serviceFee = 10n; + const value = 1250000000000000000000n; const transaction = delegationFactory.createTransactionForNewDelegationContract({ sender: sender, @@ -27,7 +26,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, DELEGATION_MANAGER_SC_ADDRESS); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("createNewDelegationContract@010f0cf064dd59200000@0a")); - assert.equal(transaction.gasLimit.valueOf(), 60126500); + assert.equal(transaction.gasLimit, 60126500n); assert.equal(transaction.value, value); }); @@ -65,7 +64,7 @@ describe("test delegation transactions factory", function () { "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@81109fa1c8d3dc7b6c2d6e65206cc0bc1a83c9b2d1eb91a601d66ad32def430827d5eb52917bd2b0d04ce195738db216", ), ); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for removing nodes", async function () { @@ -88,7 +87,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("removeNodes@61626261")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for staking nodes", async function () { @@ -111,7 +110,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("stakeNodes@61626261")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for unbonding nodes", async function () { @@ -134,8 +133,8 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("unBondNodes@61626261")); - assert.equal(transaction.value, 0); - assert.equal(transaction.gasLimit.valueOf(), 12080000); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 12080000n); }); it("should create 'TransactionNext' for unstaking nodes", async function () { @@ -158,8 +157,8 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("unStakeNodes@61626261")); - assert.equal(transaction.value, 0); - assert.equal(transaction.gasLimit.valueOf(), 12081500); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 12081500n); }); it("should create 'TransactionNext' for unjailing nodes", async function () { @@ -182,13 +181,13 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("unJailNodes@61626261")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for changing service fee", async function () { const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); - const serviceFee = new BigNumber(10); + const serviceFee = 10n; const transaction = delegationFactory.createTransactionForChangingServiceFee({ sender: sender, @@ -200,13 +199,13 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("changeServiceFee@0a")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for changing delegation cap", async function () { const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); - const delegationCap = new BigNumber("5000000000000000000000"); + const delegationCap = 5000000000000000000000n; const transaction = delegationFactory.createTransactionForModifyingDelegationCap({ sender: sender, @@ -218,7 +217,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("modifyTotalDelegationCap@010f0cf064dd59200000")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for setting automatic activation", async function () { @@ -234,7 +233,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("setAutomaticActivation@74727565")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for unsetting automatic activation", async function () { @@ -250,7 +249,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("setAutomaticActivation@66616c7365")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for setting cap check on redelegate rewards", async function () { @@ -266,7 +265,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("setCheckCapOnReDelegateRewards@74727565")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for unsetting cap check on redelegate rewards", async function () { @@ -282,7 +281,7 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("setCheckCapOnReDelegateRewards@66616c7365")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for setting metadata", async function () { @@ -301,6 +300,6 @@ describe("test delegation transactions factory", function () { assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("setMetaData@6e616d65@77656273697465@6964656e746966696572")); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); }); diff --git a/src/transactionsFactories/delegationTransactionsFactory.ts b/src/transactionsFactories/delegationTransactionsFactory.ts index 9e389ab6..f66c51df 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.ts @@ -1,5 +1,4 @@ import { IAddress } from "../interface"; -import { BigNumber } from "bignumber.js"; import { numberToPaddedHex, byteArrayToHex, utf8ToHex } from "../utils.codec"; import { TransactionNextBuilder } from "./transactionNextBuilder"; import { Address } from "../address"; @@ -9,21 +8,24 @@ import { TransactionNext } from "../transaction"; interface Config { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; - gasLimitStake: BigNumber.Value; - gasLimitUnstake: BigNumber.Value; - gasLimitUnbond: BigNumber.Value; - gasLimitCreateDelegationContract: BigNumber.Value; - gasLimitDelegationOperations: BigNumber.Value; - additionalGasLimitPerValidatorNode: BigNumber.Value; - additionalGasLimitForDelegationOperations: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitStake: bigint; + gasLimitUnstake: bigint; + gasLimitUnbond: bigint; + gasLimitCreateDelegationContract: bigint; + gasLimitDelegationOperations: bigint; + additionalGasLimitPerValidatorNode: bigint; + additionalGasLimitForDelegationOperations: bigint; } interface IValidatorPublicKey { hex(): string; } +/** + * Use this class to create delegation related transactions like creating a new delegation contract or adding nodes. + */ export class DelegationTransactionsFactory { private readonly config: Config; @@ -33,9 +35,9 @@ export class DelegationTransactionsFactory { createTransactionForNewDelegationContract(options: { sender: IAddress; - totalDelegationCap: BigNumber.Value; - serviceFee: BigNumber.Value; - amount: BigNumber.Value; + totalDelegationCap: bigint; + serviceFee: bigint; + amount: bigint; }): TransactionNext { const dataParts = [ "createNewDelegationContract", @@ -43,9 +45,8 @@ export class DelegationTransactionsFactory { numberToPaddedHex(options.serviceFee.toString()), ]; - const executionGasLimit = new BigNumber(this.config.gasLimitCreateDelegationContract).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const executionGasLimit = + this.config.gasLimitCreateDelegationContract + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -119,13 +120,10 @@ export class DelegationTransactionsFactory { } const numNodes = options.publicKeys.length; - const additionalGasForAllNodes = new BigNumber(numNodes).multipliedBy( - this.config.additionalGasLimitPerValidatorNode, - ); + const additionalGasForAllNodes = BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode; - const executionGasLimit = additionalGasForAllNodes - .plus(this.config.gasLimitStake) - .plus(this.config.gasLimitDelegationOperations); + const executionGasLimit = + additionalGasForAllNodes + this.config.gasLimitStake + this.config.gasLimitDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -149,10 +147,10 @@ export class DelegationTransactionsFactory { } const numNodes = options.publicKeys.length; - const executionGasLimit = new BigNumber(numNodes) - .multipliedBy(this.config.additionalGasLimitPerValidatorNode) - .plus(this.config.gasLimitUnbond) - .plus(this.config.gasLimitDelegationOperations); + const executionGasLimit = + BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode + + this.config.gasLimitUnbond + + this.config.gasLimitDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -176,10 +174,10 @@ export class DelegationTransactionsFactory { } const numNodes = options.publicKeys.length; - const executionGasLimit = new BigNumber(numNodes) - .multipliedBy(this.config.additionalGasLimitPerValidatorNode) - .plus(this.config.gasLimitUnstake) - .plus(this.config.gasLimitDelegationOperations); + const executionGasLimit = + BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode + + this.config.gasLimitUnstake + + this.config.gasLimitDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -216,12 +214,11 @@ export class DelegationTransactionsFactory { createTransactionForChangingServiceFee(options: { sender: IAddress; delegationContract: IAddress; - serviceFee: BigNumber.Value; + serviceFee: bigint; }): TransactionNext { const dataParts = ["changeServiceFee", numberToPaddedHex(options.serviceFee)]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -236,12 +233,11 @@ export class DelegationTransactionsFactory { createTransactionForModifyingDelegationCap(options: { sender: IAddress; delegationContract: IAddress; - delegationCap: BigNumber.Value; + delegationCap: bigint; }): TransactionNext { const dataParts = ["modifyTotalDelegationCap", numberToPaddedHex(options.delegationCap)]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -258,9 +254,8 @@ export class DelegationTransactionsFactory { delegationContract: IAddress; }): TransactionNext { const dataParts = ["setAutomaticActivation", utf8ToHex("true")]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -277,9 +272,8 @@ export class DelegationTransactionsFactory { delegationContract: IAddress; }): TransactionNext { const dataParts = ["setAutomaticActivation", utf8ToHex("false")]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -296,9 +290,8 @@ export class DelegationTransactionsFactory { delegationContract: IAddress; }): TransactionNext { const dataParts = ["setCheckCapOnReDelegateRewards", utf8ToHex("true")]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -315,9 +308,8 @@ export class DelegationTransactionsFactory { delegationContract: IAddress; }): TransactionNext { const dataParts = ["setCheckCapOnReDelegateRewards", utf8ToHex("false")]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -343,9 +335,8 @@ export class DelegationTransactionsFactory { utf8ToHex(options.identifier), ]; - const gasLimit = new BigNumber(this.config.gasLimitDelegationOperations).plus( - this.config.additionalGasLimitForDelegationOperations, - ); + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; return new TransactionNextBuilder({ config: this.config, @@ -357,11 +348,9 @@ export class DelegationTransactionsFactory { }).build(); } - private computeExecutionGasLimitForNodesManagement(numNodes: number): BigNumber.Value { - const additionalGasForAllNodes = new BigNumber(this.config.additionalGasLimitPerValidatorNode).multipliedBy( - numNodes, - ); + private computeExecutionGasLimitForNodesManagement(numNodes: number): bigint { + const additionalGasForAllNodes = this.config.additionalGasLimitPerValidatorNode * BigInt(numNodes); - return new BigNumber(this.config.gasLimitDelegationOperations).plus(additionalGasForAllNodes); + return this.config.gasLimitDelegationOperations + additionalGasForAllNodes; } } diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index daa36d93..24aa83e4 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -3,7 +3,6 @@ import { TestWallet, loadTestWallets } from "../testutils"; import { TransactionComputer, TransactionNext } from "../transaction"; import { RelayedTransactionsFactory } from "./relayedTransactionsFactory"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; -import BigNumber from "bignumber.js"; describe("test relayed v1 transaction builder", function () { const config = new TransactionsFactoryConfig("T"); @@ -19,7 +18,7 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: alice.address.bech32(), receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", - gasLimit: 10000000, + gasLimit: 10000000n, data: Buffer.from("getContractConfig"), chainID: config.chainID, }); @@ -29,7 +28,7 @@ describe("test relayed v1 transaction builder", function () { "The inner transaction is not signed"; }); - innerTransaction.gasLimit = 0; + innerTransaction.gasLimit = 0n; innerTransaction.signature = Buffer.from("invalidsignature"); assert.throws(() => { @@ -42,10 +41,10 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", - gasLimit: 60000000, + gasLimit: 60000000n, data: Buffer.from("getContractConfig"), chainID: config.chainID, - nonce: 198, + nonce: 198n, }); const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); @@ -55,7 +54,7 @@ describe("test relayed v1 transaction builder", function () { innerTransaction: innerTransaction, relayerAddress: alice.address, }); - relayedTransaction.nonce = 2627; + relayedTransaction.nonce = 2627n; const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); @@ -74,12 +73,12 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: carol.address.bech32(), receiver: alice.address.bech32(), - gasLimit: 50000, + gasLimit: 50000n, chainID: config.chainID, - nonce: 208, + nonce: 208n, senderUsername: "carol", receiverUsername: "alice", - value: new BigNumber("1000000000000000000"), + value: 1000000000000000000n, }); const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); @@ -89,7 +88,7 @@ describe("test relayed v1 transaction builder", function () { innerTransaction: innerTransaction, relayerAddress: frank.address, }); - relayedTransaction.nonce = 715; + relayedTransaction.nonce = 715n; const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); relayedTransaction.signature = await frank.signer.sign(Buffer.from(serializedRelayedTransaction)); @@ -108,12 +107,12 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: carol.address.bech32(), receiver: alice.address.bech32(), - gasLimit: 50000, + gasLimit: 50000n, chainID: config.chainID, - nonce: 208, + nonce: 208n, senderUsername: "carol", receiverUsername: "alice", - value: new BigNumber("1999999000000000000000000"), + value: 1999999000000000000000000n, }); const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); @@ -123,7 +122,7 @@ describe("test relayed v1 transaction builder", function () { innerTransaction: innerTransaction, relayerAddress: frank.address, }); - relayedTransaction.nonce = 715; + relayedTransaction.nonce = 715n; const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); relayedTransaction.signature = await frank.signer.sign(Buffer.from(serializedRelayedTransaction)); @@ -142,10 +141,10 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", - gasLimit: 60000000, + gasLimit: 60000000n, chainID: config.chainID, data: Buffer.from("getContractConfig"), - nonce: 198, + nonce: 198n, version: 2, options: 2, guardian: grace.address.bech32(), @@ -159,7 +158,7 @@ describe("test relayed v1 transaction builder", function () { innerTransaction: innerTransaction, relayerAddress: alice.address, }); - relayedTransaction.nonce = 2627; + relayedTransaction.nonce = 2627n; const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); @@ -178,10 +177,10 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", - gasLimit: 60000000, + gasLimit: 60000000n, chainID: config.chainID, data: Buffer.from("addNumber"), - nonce: 198, + nonce: 198n, version: 2, options: 2, guardian: grace.address.bech32(), @@ -195,7 +194,7 @@ describe("test relayed v1 transaction builder", function () { innerTransaction: innerTransaction, relayerAddress: alice.address, }); - relayedTransaction.nonce = 2627; + relayedTransaction.nonce = 2627n; relayedTransaction.options = 2; relayedTransaction.guardian = frank.address.bech32(); @@ -217,25 +216,25 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), receiver: bob.address.bech32(), - gasLimit: 50000, + gasLimit: 50000n, chainID: config.chainID, }); assert.throws(() => { factory.createRelayedV2Transaction({ innerTransaction: innerTransaction, - innerTransactionGasLimit: 50000, + innerTransactionGasLimit: 50000n, relayerAddress: carol.address, }), "The gas limit should not be set for the inner transaction"; }); - innerTransaction.gasLimit = 0; + innerTransaction.gasLimit = 0n; assert.throws(() => { factory.createRelayedV2Transaction({ innerTransaction: innerTransaction, - innerTransactionGasLimit: 50000, + innerTransactionGasLimit: 50000n, relayerAddress: carol.address, }), "The inner transaction is not signed"; @@ -246,10 +245,10 @@ describe("test relayed v1 transaction builder", function () { let innerTransaction = new TransactionNext({ sender: bob.address.bech32(), receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", - gasLimit: 0, + gasLimit: 0n, chainID: config.chainID, data: Buffer.from("getContractConfig"), - nonce: 15, + nonce: 15n, version: 2, options: 0, }); @@ -259,10 +258,10 @@ describe("test relayed v1 transaction builder", function () { const relayedTransaction = factory.createRelayedV2Transaction({ innerTransaction: innerTransaction, - innerTransactionGasLimit: new BigNumber("60000000"), + innerTransactionGasLimit: 60000000n, relayerAddress: alice.address, }); - relayedTransaction.nonce = 37; + relayedTransaction.nonce = 37n; const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); relayedTransaction.signature = await alice.signer.sign(Buffer.from(serializedRelayedTransaction)); diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 8893ccd3..5a58e1ab 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -9,10 +9,13 @@ const JSONbig = require("json-bigint"); interface IConfig { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; } +/** + * Use this class to create both RelayedV1 and RelayedV2 transactions. + */ export class RelayedTransactionsFactory { private readonly config: IConfig; @@ -35,12 +38,8 @@ export class RelayedTransactionsFactory { const serializedTransaction = this.prepareInnerTransactionForRelayedV1(options.innerTransaction); const data = `relayedTx@${Buffer.from(serializedTransaction).toString("hex")}`; - const additionalGasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy( - new BigNumber(data.length), - ); - const gasLimit = new BigNumber(this.config.minGasLimit) - .plus(additionalGasForDataLength) - .plus(new BigNumber(options.innerTransaction.gasLimit)); + const additionalGasForDataLength = this.config.gasLimitPerByte * BigInt(data.length); + const gasLimit = this.config.minGasLimit + additionalGasForDataLength + options.innerTransaction.gasLimit; return new TransactionNext({ chainID: this.config.chainID, @@ -53,7 +52,7 @@ export class RelayedTransactionsFactory { createRelayedV2Transaction(options: { innerTransaction: ITransactionNext; - innerTransactionGasLimit: BigNumber.Value; + innerTransactionGasLimit: bigint; relayerAddress: IAddress; }): TransactionNext { if (options.innerTransaction.gasLimit) { @@ -66,24 +65,20 @@ export class RelayedTransactionsFactory { const { argumentsString } = new ArgSerializer().valuesToString([ new AddressValue(Address.fromBech32(options.innerTransaction.receiver)), - new U64Value(options.innerTransaction.nonce), + new U64Value(new BigNumber(options.innerTransaction.nonce.toString())), new BytesValue(Buffer.from(options.innerTransaction.data)), new BytesValue(Buffer.from(options.innerTransaction.signature)), ]); const data = `relayedTxV2@${argumentsString}`; - const additionalGasForDataLength = new BigNumber(this.config.gasLimitPerByte).multipliedBy( - new BigNumber(data.length), - ); - const gasLimit = new BigNumber(options.innerTransactionGasLimit) - .plus(new BigNumber(this.config.minGasLimit)) - .plus(additionalGasForDataLength); + const additionalGasForDataLength = this.config.gasLimitPerByte * BigInt(data.length); + const gasLimit = options.innerTransactionGasLimit + this.config.minGasLimit + additionalGasForDataLength; return new TransactionNext({ sender: options.relayerAddress.bech32(), receiver: options.innerTransaction.sender, - value: 0, + value: 0n, gasLimit: gasLimit, chainID: this.config.chainID, data: Buffer.from(data), @@ -94,12 +89,12 @@ export class RelayedTransactionsFactory { private prepareInnerTransactionForRelayedV1(innerTransaction: TransactionNext): string { const txObject = { - nonce: new BigNumber(innerTransaction.nonce).toNumber(), + nonce: innerTransaction.nonce, sender: Address.fromBech32(innerTransaction.sender).pubkey().toString("base64"), receiver: Address.fromBech32(innerTransaction.receiver).pubkey().toString("base64"), - value: BigInt(new BigNumber(innerTransaction.value).toFixed(0)), - gasPrice: new BigNumber(innerTransaction.gasPrice).toNumber(), - gasLimit: new BigNumber(innerTransaction.gasLimit).toNumber(), + value: innerTransaction.value, + gasPrice: innerTransaction.gasPrice, + gasLimit: innerTransaction.gasLimit, data: Buffer.from(innerTransaction.data).toString("base64"), signature: Buffer.from(innerTransaction.signature).toString("base64"), chainID: Buffer.from(innerTransaction.chainID).toString("base64"), diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts index d4ab3c62..eabe4664 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -1,15 +1,14 @@ import { assert, expect } from "chai"; -import { SmartContractTransactionsFactory } from "./smartContractTransactionsFactory"; import { Address } from "../address"; -import { Code } from "../smartcontracts/code"; -import { AbiRegistry } from "../smartcontracts/typesystem/abiRegistry"; -import { U32Value } from "../smartcontracts"; import { CONTRACT_DEPLOY_ADDRESS } from "../constants"; -import { loadContractCode, loadAbiRegistry } from "../testutils/utils"; import { Err } from "../errors"; +import { U32Value } from "../smartcontracts"; +import { Code } from "../smartcontracts/code"; +import { AbiRegistry } from "../smartcontracts/typesystem/abiRegistry"; +import { loadAbiRegistry, loadContractCode } from "../testutils/utils"; +import { NextTokenTransfer, Token, TokenComputer } from "../tokens"; +import { SmartContractTransactionsFactory } from "./smartContractTransactionsFactory"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; -import BigNumber from "bignumber.js"; -import { Token, NextTokenTransfer, TokenComputer } from "../tokens"; describe("test smart contract transactions factory", function () { const config = new TransactionsFactoryConfig("D"); @@ -36,7 +35,7 @@ describe("test smart contract transactions factory", function () { it("should throw error when args are not of type 'TypedValue'", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [0]; assert.throws( @@ -54,7 +53,7 @@ describe("test smart contract transactions factory", function () { it("should create 'TransactionNext' for deploy", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(0)]; const transaction = smartContractFactory.createTransactionForDeploy({ @@ -74,7 +73,7 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.receiver, CONTRACT_DEPLOY_ADDRESS); expect(transaction.data.length).to.be.greaterThan(0); assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiDeployNext); }); @@ -83,7 +82,7 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const func = "add"; - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(7)]; const transaction = smartContractFactory.createTransactionForExecute({ @@ -104,8 +103,8 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); assert.deepEqual(transaction.data, Buffer.from("add@07")); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value, 0); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -114,8 +113,8 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const func = "add"; - const gasLimit = 6000000; - const egldAmount = new BigNumber("1000000000000000000"); + const gasLimit = 6000000n; + const egldAmount = 1000000000000000000n; const transaction = smartContractFactory.createTransactionForExecute({ sender: sender, @@ -137,8 +136,8 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); assert.deepEqual(transaction.data, Buffer.from("add@07")); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value.valueOf(), "1000000000000000000"); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 1000000000000000000n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -147,10 +146,10 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const func = "add"; - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(7)]; - const token = new Token("FOO-6ce17b", 0); - const transfer = new NextTokenTransfer(token, 10); + const token = new Token("FOO-6ce17b", 0n); + const transfer = new NextTokenTransfer(token, 10n); const transaction = smartContractFactory.createTransactionForExecute({ sender: sender, @@ -172,8 +171,8 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); assert.deepEqual(transaction.data, Buffer.from("ESDTTransfer@464f4f2d366365313762@0a@616464@07")); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value.valueOf(), "0"); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -182,13 +181,13 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); const func = "add"; - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(7)]; - const fooToken = new Token("FOO-6ce17b", 0); - const fooTransfer = new NextTokenTransfer(fooToken, 10); - const barToken = new Token("BAR-5bc08f", 0); - const barTransfer = new NextTokenTransfer(barToken, 3140); + const fooToken = new Token("FOO-6ce17b", 0n); + const fooTransfer = new NextTokenTransfer(fooToken, 10n); + const barToken = new Token("BAR-5bc08f", 0n); + const barTransfer = new NextTokenTransfer(barToken, 3140n); const transaction = smartContractFactory.createTransactionForExecute({ sender: sender, @@ -217,8 +216,8 @@ describe("test smart contract transactions factory", function () { ), ); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value.valueOf(), "0"); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -227,11 +226,11 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const func = "add"; - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(7)]; - const token = new Token("NFT-123456", 1); - const transfer = new NextTokenTransfer(token, 1); + const token = new Token("NFT-123456", 1n); + const transfer = new NextTokenTransfer(token, 1n); const transaction = smartContractFactory.createTransactionForExecute({ sender: sender, @@ -261,8 +260,8 @@ describe("test smart contract transactions factory", function () { ), ); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value.valueOf(), "0"); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -271,13 +270,13 @@ describe("test smart contract transactions factory", function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); const func = "add"; - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(7)]; - const firstToken = new Token("NFT-123456", 1); - const firstTransfer = new NextTokenTransfer(firstToken, 1); - const secondToken = new Token("NFT-123456", 42); - const secondTransfer = new NextTokenTransfer(secondToken, 1); + const firstToken = new Token("NFT-123456", 1n); + const firstTransfer = new NextTokenTransfer(firstToken, 1n); + const secondToken = new Token("NFT-123456", 42n); + const secondTransfer = new NextTokenTransfer(secondToken, 1n); const transaction = smartContractFactory.createTransactionForExecute({ sender: sender, @@ -307,8 +306,8 @@ describe("test smart contract transactions factory", function () { ), ); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value.valueOf(), "0"); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiExecuteNext); }); @@ -316,7 +315,7 @@ describe("test smart contract transactions factory", function () { it("should create 'TransactionNext' for upgrade", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); - const gasLimit = 6000000; + const gasLimit = 6000000n; const args = [new U32Value(0)]; const transaction = smartContractFactory.createTransactionForUpgrade({ @@ -338,8 +337,8 @@ describe("test smart contract transactions factory", function () { assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); assert.isTrue(Buffer.from(transaction.data!).toString().startsWith("upgradeContract@")); - assert.equal(transaction.gasLimit.valueOf(), gasLimit); - assert.equal(transaction.value, 0); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); assert.deepEqual(transaction, abiUpgradeNext); }); diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index e64e2e77..329634de 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -1,4 +1,3 @@ -import { BigNumber } from "bignumber.js"; import { IAddress } from "../interface"; import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts"; import { byteArrayToHex, utf8ToHex } from "../utils.codec"; @@ -13,8 +12,8 @@ import { TransactionNext } from "../transaction"; interface Config { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; } interface Abi { @@ -27,6 +26,9 @@ interface TokenComputer { isFungible(token: Token): boolean; } +/** + * Use this class to create transactions to deploy, call or upgrade a smart contract. + */ export class SmartContractTransactionsFactory { private readonly config: Config; private readonly abiRegistry?: Abi; @@ -43,15 +45,15 @@ export class SmartContractTransactionsFactory { createTransactionForDeploy(options: { sender: IAddress; bytecode: Uint8Array; - gasLimit: BigNumber.Value; + gasLimit: bigint; args?: any[]; - nativeTransferAmount?: BigNumber.Value; + nativeTransferAmount?: bigint; isUpgradeable?: boolean; isReadable?: boolean; isPayable?: boolean; isPayableBySmartContract?: boolean; }): TransactionNext { - const nativeTransferAmount = options.nativeTransferAmount ?? 0; + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; const isUpgradeable = options.isUpgradeable ?? true; const isReadable = options.isReadable ?? true; const isPayable = options.isPayable ?? false; @@ -77,14 +79,14 @@ export class SmartContractTransactionsFactory { sender: IAddress; contract: IAddress; functionName: string; - gasLimit: BigNumber.Value; + gasLimit: bigint; args?: any[]; - nativeTransferAmount?: BigNumber.Value; + nativeTransferAmount?: bigint; tokenTransfers?: NextTokenTransfer[]; }): TransactionNext { const args = options.args || []; const tokenTransfer = options.tokenTransfers || []; - const nativeTransferAmount = options.nativeTransferAmount ?? 0; + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; const numberOfTokens = tokenTransfer.length; if (nativeTransferAmount && numberOfTokens) { @@ -126,15 +128,15 @@ export class SmartContractTransactionsFactory { sender: IAddress; contract: IAddress; bytecode: Uint8Array; - gasLimit: BigNumber.Value; + gasLimit: bigint; args?: any[]; - nativeTransferAmount?: BigNumber.Value; + nativeTransferAmount?: bigint; isUpgradeable?: boolean; isReadable?: boolean; isPayable?: boolean; isPayableBySmartContract?: boolean; }): TransactionNext { - const nativeTransferAmount = options.nativeTransferAmount ?? 0; + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; const isUpgradeable = options.isUpgradeable ?? true; const isReadable = options.isReadable ?? true; diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index b675e0ce..c6ad6169 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -1,4 +1,3 @@ -import BigNumber from "bignumber.js"; import { assert } from "chai"; import { ESDT_CONTRACT_ADDRESS } from "../constants"; import { loadTestWallets, TestWallet } from "../testutils"; @@ -22,14 +21,14 @@ describe("test token management transactions factory", () => { tokenName: "TEST", tokenTicker: "TEST", tokenType: "FNG", - numDecimals: 2, + numDecimals: 2n, }); assert.deepEqual(transaction.data, Buffer.from("registerAndSetAllRoles@54455354@54455354@464e47@02")); assert.equal(transaction.sender, frank.address.toString()); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"); assert.deepEqual(transaction.value, config.issueCost); - assert.deepEqual(transaction.gasLimit, new BigNumber("60125000")); + assert.deepEqual(transaction.gasLimit, 60125000n); }); it("should create 'TransactionNext' for issuing fungible token", () => { @@ -37,8 +36,8 @@ describe("test token management transactions factory", () => { sender: frank.address, tokenName: "FRANK", tokenTicker: "FRANK", - initialSupply: 100, - numDecimals: 0, + initialSupply: 100n, + numDecimals: 0n, canFreeze: true, canWipe: true, canPause: true, @@ -50,7 +49,7 @@ describe("test token management transactions factory", () => { assert.deepEqual( next.data, Buffer.from( - "issue@4652414e4b@4652414e4b@64@@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", + "issue@4652414e4b@4652414e4b@64@00@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", ), ); assert.equal(next.sender, frank.address.toString()); @@ -113,7 +112,7 @@ describe("test token management transactions factory", () => { sender: frank.address, tokenName: "FRANK", tokenTicker: "FRANK", - numDecimals: 10, + numDecimals: 10n, canFreeze: true, canWipe: true, canPause: true, @@ -154,14 +153,14 @@ describe("test token management transactions factory", () => { ); assert.equal(transaction.sender, frank.address.toString()); assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); it("should create 'TransactionNext' for creating nft", () => { const transaction = tokenManagementFactory.createTransactionForCreatingNFT({ sender: grace.address, tokenIdentifier: "FRANK-aa9e8d", - initialQuantity: 1, + initialQuantity: 1n, name: "test", royalties: 1000, hash: "abba", @@ -175,6 +174,6 @@ describe("test token management transactions factory", () => { ); assert.equal(transaction.sender, grace.address.toString()); assert.equal(transaction.receiver, grace.address.toString()); - assert.equal(transaction.value, 0); + assert.equal(transaction.value, 0n); }); }); diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index 508c1e28..c2c17a89 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -1,34 +1,36 @@ -import BigNumber from "bignumber.js"; import { Address } from "../address"; import { ESDT_CONTRACT_ADDRESS } from "../constants"; import { IAddress } from "../interface"; import { Logger } from "../logger"; -import { addressToHex, bigIntToHex, byteArrayToHex, utf8ToHex } from "../utils.codec"; +import { addressToHex, byteArrayToHex, utf8ToHex, numberToPaddedHex } from "../utils.codec"; import { TransactionNextBuilder } from "./transactionNextBuilder"; import { TransactionNext } from "../transaction"; interface Config { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; - gasLimitIssue: BigNumber.Value; - gasLimitToggleBurnRoleGlobally: BigNumber.Value; - gasLimitEsdtLocalMint: BigNumber.Value; - gasLimitEsdtLocalBurn: BigNumber.Value; - gasLimitSetSpecialRole: BigNumber.Value; - gasLimitPausing: BigNumber.Value; - gasLimitFreezing: BigNumber.Value; - gasLimitWiping: BigNumber.Value; - gasLimitEsdtNftCreate: BigNumber.Value; - gasLimitEsdtNftUpdateAttributes: BigNumber.Value; - gasLimitEsdtNftAddQuantity: BigNumber.Value; - gasLimitEsdtNftBurn: BigNumber.Value; - gasLimitStorePerByte: BigNumber.Value; - issueCost: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitIssue: bigint; + gasLimitToggleBurnRoleGlobally: bigint; + gasLimitEsdtLocalMint: bigint; + gasLimitEsdtLocalBurn: bigint; + gasLimitSetSpecialRole: bigint; + gasLimitPausing: bigint; + gasLimitFreezing: bigint; + gasLimitWiping: bigint; + gasLimitEsdtNftCreate: bigint; + gasLimitEsdtNftUpdateAttributes: bigint; + gasLimitEsdtNftAddQuantity: bigint; + gasLimitEsdtNftBurn: bigint; + gasLimitStorePerByte: bigint; + issueCost: bigint; } type RegisterAndSetAllRolesTokenType = "NFT" | "SFT" | "META" | "FNG"; +/** + * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. + */ export class TokenManagementTransactionsFactory { private readonly config: Config; private readonly trueAsHex: string; @@ -44,8 +46,8 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenName: string; tokenTicker: string; - initialSupply: BigNumber.Value; - numDecimals: BigNumber.Value; + initialSupply: bigint; + numDecimals: bigint; canFreeze: boolean; canWipe: boolean; canPause: boolean; @@ -59,8 +61,8 @@ export class TokenManagementTransactionsFactory { "issue", utf8ToHex(options.tokenName), utf8ToHex(options.tokenTicker), - bigIntToHex(options.initialSupply), - bigIntToHex(options.numDecimals), + numberToPaddedHex(options.initialSupply), + numberToPaddedHex(options.numDecimals), utf8ToHex("canFreeze"), options.canFreeze ? this.trueAsHex : this.falseAsHex, utf8ToHex("canWipe"), @@ -180,7 +182,7 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenName: string; tokenTicker: string; - numDecimals: BigNumber.Value; + numDecimals: bigint; canFreeze: boolean; canWipe: boolean; canPause: boolean; @@ -195,7 +197,7 @@ export class TokenManagementTransactionsFactory { "registerMetaESDT", utf8ToHex(options.tokenName), utf8ToHex(options.tokenTicker), - bigIntToHex(options.numDecimals), + numberToPaddedHex(options.numDecimals), utf8ToHex("canFreeze"), options.canFreeze ? this.trueAsHex : this.falseAsHex, utf8ToHex("canWipe"), @@ -228,7 +230,7 @@ export class TokenManagementTransactionsFactory { tokenName: string; tokenTicker: string; tokenType: RegisterAndSetAllRolesTokenType; - numDecimals: BigNumber.Value; + numDecimals: bigint; }): TransactionNext { this.notifyAboutUnsettingBurnRoleGlobally(); @@ -237,7 +239,7 @@ export class TokenManagementTransactionsFactory { utf8ToHex(options.tokenName), utf8ToHex(options.tokenTicker), utf8ToHex(options.tokenType), - bigIntToHex(options.numDecimals), + numberToPaddedHex(options.numDecimals), ]; return new TransactionNextBuilder({ @@ -383,7 +385,7 @@ export class TokenManagementTransactionsFactory { createTransactionForCreatingNFT(options: { sender: IAddress; tokenIdentifier: string; - initialQuantity: BigNumber.Value; + initialQuantity: bigint; name: string; royalties: number; hash: string; @@ -393,9 +395,9 @@ export class TokenManagementTransactionsFactory { const dataParts = [ "ESDTNFTCreate", utf8ToHex(options.tokenIdentifier), - bigIntToHex(options.initialQuantity), + numberToPaddedHex(options.initialQuantity), utf8ToHex(options.name), - bigIntToHex(options.royalties), + numberToPaddedHex(options.royalties), utf8ToHex(options.hash), byteArrayToHex(options.attributes), ...options.uris.map(utf8ToHex), @@ -403,14 +405,14 @@ export class TokenManagementTransactionsFactory { // Note that the following is an approximation (a reasonable one): const nftData = options.name + options.hash + options.attributes + options.uris.join(""); - const storageGasLimit = new BigNumber(this.config.gasLimitPerByte).multipliedBy(nftData.length); + const storageGasLimit = this.config.gasLimitPerByte + BigInt(nftData.length); return new TransactionNextBuilder({ config: this.config, sender: options.sender, receiver: options.sender, dataParts: dataParts, - gasLimit: new BigNumber(this.config.gasLimitEsdtNftCreate).plus(storageGasLimit), + gasLimit: this.config.gasLimitEsdtNftCreate + storageGasLimit, addDataMovementGas: true, }).build(); } @@ -495,9 +497,13 @@ export class TokenManagementTransactionsFactory { createTransactionForLocalMint(options: { sender: IAddress; tokenIdentifier: string; - supplyToMint: BigNumber.Value; + supplyToMint: bigint; }): TransactionNext { - const dataParts = ["ESDTLocalMint", utf8ToHex(options.tokenIdentifier), bigIntToHex(options.supplyToMint)]; + const dataParts = [ + "ESDTLocalMint", + utf8ToHex(options.tokenIdentifier), + numberToPaddedHex(options.supplyToMint), + ]; return new TransactionNextBuilder({ config: this.config, @@ -512,9 +518,13 @@ export class TokenManagementTransactionsFactory { createTransactionForLocalBurning(options: { sender: IAddress; tokenIdentifier: string; - supplyToBurn: BigNumber.Value; + supplyToBurn: bigint; }): TransactionNext { - const dataParts = ["ESDTLocalBurn", utf8ToHex(options.tokenIdentifier), bigIntToHex(options.supplyToBurn)]; + const dataParts = [ + "ESDTLocalBurn", + utf8ToHex(options.tokenIdentifier), + numberToPaddedHex(options.supplyToBurn), + ]; return new TransactionNextBuilder({ config: this.config, @@ -529,13 +539,13 @@ export class TokenManagementTransactionsFactory { createTransactionForUpdatingAttributes(options: { sender: IAddress; tokenIdentifier: string; - tokenNonce: BigNumber.Value; + tokenNonce: bigint; attributes: Uint8Array; }): TransactionNext { const dataParts = [ "ESDTNFTUpdateAttributes", utf8ToHex(options.tokenIdentifier), - bigIntToHex(options.tokenNonce), + numberToPaddedHex(options.tokenNonce), byteArrayToHex(options.attributes), ]; @@ -552,14 +562,14 @@ export class TokenManagementTransactionsFactory { createTransactionForAddingQuantity(options: { sender: IAddress; tokenIdentifier: string; - tokenNonce: BigNumber.Value; - quantityToAdd: BigNumber.Value; + tokenNonce: bigint; + quantityToAdd: bigint; }): TransactionNext { const dataParts = [ "ESDTNFTAddQuantity", utf8ToHex(options.tokenIdentifier), - bigIntToHex(options.tokenNonce), - bigIntToHex(options.quantityToAdd), + numberToPaddedHex(options.tokenNonce), + numberToPaddedHex(options.quantityToAdd), ]; return new TransactionNextBuilder({ @@ -575,14 +585,14 @@ export class TokenManagementTransactionsFactory { createTransactionForBurningQuantity(options: { sender: IAddress; tokenIdentifier: string; - tokenNonce: BigNumber.Value; - quantityToBurn: BigNumber.Value; + tokenNonce: bigint; + quantityToBurn: bigint; }): TransactionNext { const dataParts = [ "ESDTNFTBurn", utf8ToHex(options.tokenIdentifier), - bigIntToHex(options.tokenNonce), - bigIntToHex(options.quantityToBurn), + numberToPaddedHex(options.tokenNonce), + numberToPaddedHex(options.quantityToBurn), ]; return new TransactionNextBuilder({ diff --git a/src/transactionsFactories/tokenTransfersDataBuilder.ts b/src/transactionsFactories/tokenTransfersDataBuilder.ts index 7804e7bd..593f9275 100644 --- a/src/transactionsFactories/tokenTransfersDataBuilder.ts +++ b/src/transactionsFactories/tokenTransfersDataBuilder.ts @@ -1,6 +1,6 @@ import { IAddress } from "../interface"; import { NextTokenTransfer, TokenComputer } from "../tokens"; -import { numberToPaddedHex, utf8ToHex, addressToHex } from "../utils.codec"; +import { addressToHex, numberToPaddedHex, utf8ToHex } from "../utils.codec"; export class TokenTransfersDataBuilder { private tokenComputer: TokenComputer; diff --git a/src/transactionsFactories/transactionNextBuilder.ts b/src/transactionsFactories/transactionNextBuilder.ts index c4b440f6..e1c8b049 100644 --- a/src/transactionsFactories/transactionNextBuilder.ts +++ b/src/transactionsFactories/transactionNextBuilder.ts @@ -1,4 +1,3 @@ -import { BigNumber } from "bignumber.js"; import { IAddress, ITransactionPayload } from "../interface"; import { ARGUMENTS_SEPARATOR } from "../constants"; import { TransactionPayload } from "../transactionPayload"; @@ -6,8 +5,8 @@ import { TransactionNext } from "../transaction"; interface Config { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; } export class TransactionNextBuilder { @@ -15,37 +14,35 @@ export class TransactionNextBuilder { private sender: IAddress; private receiver: IAddress; private dataParts: string[]; - private providedGasLimit: BigNumber; + private providedGasLimit: bigint; private addDataMovementGas: boolean; - private amount?: BigNumber.Value; + private amount?: bigint; constructor(options: { config: Config; sender: IAddress; receiver: IAddress; dataParts: string[]; - gasLimit: BigNumber.Value; + gasLimit: bigint; addDataMovementGas: boolean; - amount?: BigNumber.Value; + amount?: bigint; }) { this.config = options.config; this.sender = options.sender; this.receiver = options.receiver; this.dataParts = options.dataParts; - this.providedGasLimit = new BigNumber(options.gasLimit); + this.providedGasLimit = options.gasLimit; this.addDataMovementGas = options.addDataMovementGas; this.amount = options.amount; } - private computeGasLimit(payload: ITransactionPayload): BigNumber.Value { + private computeGasLimit(payload: ITransactionPayload): bigint { if (!this.addDataMovementGas) { return this.providedGasLimit; } - const dataMovementGas = new BigNumber(this.config.minGasLimit).plus( - new BigNumber(this.config.gasLimitPerByte).multipliedBy(payload.length()), - ); - const gasLimit = dataMovementGas.plus(this.providedGasLimit); + const dataMovementGas = this.config.minGasLimit + this.config.gasLimitPerByte * BigInt(payload.length()); + const gasLimit = dataMovementGas + this.providedGasLimit; return gasLimit; } @@ -62,7 +59,7 @@ export class TransactionNextBuilder { sender: this.sender.bech32(), receiver: this.receiver.bech32(), gasLimit: gasLimit, - value: this.amount || 0, + value: this.amount || 0n, data: data.valueOf(), chainID: this.config.toString(), }); diff --git a/src/transactionsFactories/transactionsFactoryConfig.ts b/src/transactionsFactories/transactionsFactoryConfig.ts index c0727670..1115fff9 100644 --- a/src/transactionsFactories/transactionsFactoryConfig.ts +++ b/src/transactionsFactories/transactionsFactoryConfig.ts @@ -1,71 +1,70 @@ -import { BigNumber } from "bignumber.js"; import { DEFAULT_HRP } from "../constants"; export class TransactionsFactoryConfig { chainID: string; addressHrp: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; - gasLimitIssue: BigNumber.Value; - gasLimitToggleBurnRoleGlobally: BigNumber.Value; - gasLimitEsdtLocalMint: BigNumber.Value; - gasLimitEsdtLocalBurn: BigNumber.Value; - gasLimitSetSpecialRole: BigNumber.Value; - gasLimitPausing: BigNumber.Value; - gasLimitFreezing: BigNumber.Value; - gasLimitWiping: BigNumber.Value; - gasLimitEsdtNftCreate: BigNumber.Value; - gasLimitEsdtNftUpdateAttributes: BigNumber.Value; - gasLimitEsdtNftAddQuantity: BigNumber.Value; - gasLimitEsdtNftBurn: BigNumber.Value; - gasLimitStorePerByte: BigNumber.Value; - issueCost: BigNumber.Value; - gasLimitStake: BigNumber.Value; - gasLimitUnstake: BigNumber.Value; - gasLimitUnbond: BigNumber.Value; - gasLimitCreateDelegationContract: BigNumber.Value; - gasLimitDelegationOperations: BigNumber.Value; - additionalGasLimitPerValidatorNode: BigNumber.Value; - additionalGasLimitForDelegationOperations: BigNumber.Value; - gasLimitESDTTransfer: BigNumber.Value; - gasLimitESDTNFTTransfer: BigNumber.Value; - gasLimitMultiESDTNFTTransfer: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitIssue: bigint; + gasLimitToggleBurnRoleGlobally: bigint; + gasLimitEsdtLocalMint: bigint; + gasLimitEsdtLocalBurn: bigint; + gasLimitSetSpecialRole: bigint; + gasLimitPausing: bigint; + gasLimitFreezing: bigint; + gasLimitWiping: bigint; + gasLimitEsdtNftCreate: bigint; + gasLimitEsdtNftUpdateAttributes: bigint; + gasLimitEsdtNftAddQuantity: bigint; + gasLimitEsdtNftBurn: bigint; + gasLimitStorePerByte: bigint; + issueCost: bigint; + gasLimitStake: bigint; + gasLimitUnstake: bigint; + gasLimitUnbond: bigint; + gasLimitCreateDelegationContract: bigint; + gasLimitDelegationOperations: bigint; + additionalGasLimitPerValidatorNode: bigint; + additionalGasLimitForDelegationOperations: bigint; + gasLimitESDTTransfer: bigint; + gasLimitESDTNFTTransfer: bigint; + gasLimitMultiESDTNFTTransfer: bigint; constructor(chainId: string) { // General-purpose configuration this.chainID = chainId; this.addressHrp = DEFAULT_HRP; - this.minGasLimit = new BigNumber(50000); - this.gasLimitPerByte = new BigNumber(1500); + this.minGasLimit = 50000n; + this.gasLimitPerByte = 1500n; // Configuration for token operations - this.gasLimitIssue = new BigNumber(60000000); - this.gasLimitToggleBurnRoleGlobally = new BigNumber(60000000); - this.gasLimitEsdtLocalMint = new BigNumber(300000); - this.gasLimitEsdtLocalBurn = new BigNumber(300000); - this.gasLimitSetSpecialRole = new BigNumber(60000000); - this.gasLimitPausing = new BigNumber(60000000); - this.gasLimitFreezing = new BigNumber(60000000); - this.gasLimitWiping = new BigNumber(60000000); - this.gasLimitEsdtNftCreate = new BigNumber(3000000); - this.gasLimitEsdtNftUpdateAttributes = new BigNumber(1000000); - this.gasLimitEsdtNftAddQuantity = new BigNumber(1000000); - this.gasLimitEsdtNftBurn = new BigNumber(1000000); - this.gasLimitStorePerByte = new BigNumber(50000); - this.issueCost = new BigNumber("50000000000000000"); + this.gasLimitIssue = 60000000n; + this.gasLimitToggleBurnRoleGlobally = 60000000n; + this.gasLimitEsdtLocalMint = 300000n; + this.gasLimitEsdtLocalBurn = 300000n; + this.gasLimitSetSpecialRole = 60000000n; + this.gasLimitPausing = 60000000n; + this.gasLimitFreezing = 60000000n; + this.gasLimitWiping = 60000000n; + this.gasLimitEsdtNftCreate = 3000000n; + this.gasLimitEsdtNftUpdateAttributes = 1000000n; + this.gasLimitEsdtNftAddQuantity = 1000000n; + this.gasLimitEsdtNftBurn = 1000000n; + this.gasLimitStorePerByte = 50000n; + this.issueCost = 50000000000000000n; // Configuration for delegation operations - this.gasLimitStake = new BigNumber(5000000); - this.gasLimitUnstake = new BigNumber(5000000); - this.gasLimitUnbond = new BigNumber(5000000); - this.gasLimitCreateDelegationContract = new BigNumber(50000000); - this.gasLimitDelegationOperations = new BigNumber(1000000); - this.additionalGasLimitPerValidatorNode = new BigNumber(6000000); - this.additionalGasLimitForDelegationOperations = new BigNumber(10000000); + this.gasLimitStake = 5000000n; + this.gasLimitUnstake = 5000000n; + this.gasLimitUnbond = 5000000n; + this.gasLimitCreateDelegationContract = 50000000n; + this.gasLimitDelegationOperations = 1000000n; + this.additionalGasLimitPerValidatorNode = 6000000n; + this.additionalGasLimitForDelegationOperations = 10000000n; // Configuration for token transfers - this.gasLimitESDTTransfer = new BigNumber(200000); - this.gasLimitESDTNFTTransfer = new BigNumber(200000); - this.gasLimitMultiESDTNFTTransfer = new BigNumber(200000); + this.gasLimitESDTTransfer = 200000n; + this.gasLimitESDTNFTTransfer = 200000n; + this.gasLimitMultiESDTNFTTransfer = 200000n; } } diff --git a/src/transactionsFactories/transferTransactionsFactory.spec.ts b/src/transactionsFactories/transferTransactionsFactory.spec.ts index 2f609159..fcf09b09 100644 --- a/src/transactionsFactories/transferTransactionsFactory.spec.ts +++ b/src/transactionsFactories/transferTransactionsFactory.spec.ts @@ -1,9 +1,9 @@ import { assert } from "chai"; import { Address } from "../address"; -import { Token, NextTokenTransfer, TokenComputer } from "../tokens"; +import { ErrBadUsage } from "../errors"; +import { NextTokenTransfer, Token, TokenComputer } from "../tokens"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; import { NextTransferTransactionsFactory } from "./transferTransactionsFactory"; -import { ErrBadUsage } from "../errors"; describe("test transfer transcations factory", function () { const config = new TransactionsFactoryConfig("D"); @@ -32,13 +32,13 @@ describe("test transfer transcations factory", function () { const transaction = nextTransferFactory.createTransactionForNativeTokenTransfer({ sender: alice, receiver: bob, - nativeAmount: "1000000000000000000", + nativeAmount: 1000000000000000000n, }); assert.equal(transaction.sender, alice.bech32()); assert.equal(transaction.receiver, bob.bech32()); - assert.equal(transaction.value.valueOf(), "1000000000000000000"); - assert.equal(transaction.gasLimit.valueOf(), "50000"); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 50000n); assert.deepEqual(transaction.data, new Uint8Array()); }); @@ -46,20 +46,20 @@ describe("test transfer transcations factory", function () { const transaction = nextTransferFactory.createTransactionForNativeTokenTransfer({ sender: alice, receiver: bob, - nativeAmount: "1000000000000000000", + nativeAmount: 1000000000000000000n, data: "test data", }); assert.equal(transaction.sender, alice.bech32()); assert.equal(transaction.receiver, bob.bech32()); - assert.equal(transaction.value.valueOf(), "1000000000000000000"); - assert.equal(transaction.gasLimit.valueOf(), "63500"); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 63500n); assert.deepEqual(transaction.data, Buffer.from("test data")); }); it("should create 'TransactionNext' for esdt transfer", async () => { - const fooToken = new Token("FOO-123456", 0); - const transfer = new NextTokenTransfer(fooToken, 1000000); + const fooToken = new Token("FOO-123456", 0n); + const transfer = new NextTokenTransfer(fooToken, 1000000n); const transaction = nextTransferFactory.createTransactionForESDTTokenTransfer({ sender: alice, @@ -69,14 +69,14 @@ describe("test transfer transcations factory", function () { assert.equal(transaction.sender, alice.bech32()); assert.equal(transaction.receiver, bob.bech32()); - assert.equal(transaction.value.valueOf(), "0"); - assert.equal(transaction.gasLimit.valueOf(), "410000"); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 410000n); assert.deepEqual(transaction.data.toString(), "ESDTTransfer@464f4f2d313233343536@0f4240"); }); it("should create 'TransactionNext' for nft transfer", async () => { - const nft = new Token("NFT-123456", 10); - const transfer = new NextTokenTransfer(nft, 1); + const nft = new Token("NFT-123456", 10n); + const transfer = new NextTokenTransfer(nft, 1n); const transaction = nextTransferFactory.createTransactionForESDTTokenTransfer({ sender: alice, @@ -86,8 +86,8 @@ describe("test transfer transcations factory", function () { assert.equal(transaction.sender, alice.bech32()); assert.equal(transaction.receiver, alice.bech32()); - assert.equal(transaction.value.valueOf(), "0"); - assert.equal(transaction.gasLimit.valueOf(), "1210500"); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 1210500n); assert.deepEqual( transaction.data.toString(), "ESDTNFTTransfer@4e46542d313233343536@0a@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", @@ -95,11 +95,11 @@ describe("test transfer transcations factory", function () { }); it("should create 'TransactionNext' for multiple nft transfers", async () => { - const firstNft = new Token("NFT-123456", 10); - const firstTransfer = new NextTokenTransfer(firstNft, 1); + const firstNft = new Token("NFT-123456", 10n); + const firstTransfer = new NextTokenTransfer(firstNft, 1n); - const secondNft = new Token("TEST-987654", 1); - const secondTransfer = new NextTokenTransfer(secondNft, 1); + const secondNft = new Token("TEST-987654", 1n); + const secondTransfer = new NextTokenTransfer(secondNft, 1n); const transaction = nextTransferFactory.createTransactionForESDTTokenTransfer({ sender: alice, @@ -109,8 +109,8 @@ describe("test transfer transcations factory", function () { assert.equal(transaction.sender, alice.bech32()); assert.equal(transaction.receiver, alice.bech32()); - assert.equal(transaction.value.valueOf(), "0"); - assert.equal(transaction.gasLimit.valueOf(), "1466000"); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 1466000n); assert.deepEqual( transaction.data.toString(), "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@4e46542d313233343536@0a@01@544553542d393837363534@01@01", diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index ad606c65..70d1d4a8 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -1,4 +1,3 @@ -import BigNumber from "bignumber.js"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; import { IAddress } from "../interface"; import { NextTokenTransfer, Token } from "../tokens"; @@ -11,17 +10,20 @@ const ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 800000; interface IConfig { chainID: string; - minGasLimit: BigNumber.Value; - gasLimitPerByte: BigNumber.Value; - gasLimitESDTTransfer: BigNumber.Value; - gasLimitESDTNFTTransfer: BigNumber.Value; - gasLimitMultiESDTNFTTransfer: BigNumber.Value; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitESDTTransfer: bigint; + gasLimitESDTNFTTransfer: bigint; + gasLimitMultiESDTNFTTransfer: bigint; } interface TokenComputer { isFungible(token: Token): boolean; } +/** + * Use this class to create transactions for native token transfers (EGLD) or custom tokens transfers (ESDT/NTF/MetaESDT). + */ export class NextTransferTransactionsFactory { private readonly config: IConfig; private readonly dataArgsBuilder: TokenTransfersDataBuilder; @@ -36,7 +38,7 @@ export class NextTransferTransactionsFactory { createTransactionForNativeTokenTransfer(options: { sender: IAddress; receiver: IAddress; - nativeAmount: BigNumber.Value; + nativeAmount: bigint; data?: string; }): TransactionNext { const data = options.data || ""; @@ -46,7 +48,7 @@ export class NextTransferTransactionsFactory { sender: options.sender, receiver: options.receiver, dataParts: [data], - gasLimit: 0, + gasLimit: 0n, addDataMovementGas: true, amount: options.nativeAmount, }).build(); @@ -72,9 +74,9 @@ export class NextTransferTransactionsFactory { options.tokenTransfers, ); - const extraGasForTransfer = new BigNumber(this.config.gasLimitMultiESDTNFTTransfer) - .multipliedBy(new BigNumber(numberOfTransfers)) - .plus(new BigNumber(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER)); + const extraGasForTransfer = + this.config.gasLimitMultiESDTNFTTransfer * BigInt(numberOfTransfers) + + BigInt(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER); return new TransactionNextBuilder({ config: this.config, @@ -93,19 +95,15 @@ export class NextTransferTransactionsFactory { }): TransactionNext { let transferArgs: string[] = []; const transfer = options.tokenTransfers[0]; - let extraGasForTransfer = new BigNumber(0); + let extraGasForTransfer = 0n; let receiver = options.receiver; if (this.tokenComputer.isFungible(transfer.token)) { transferArgs = this.dataArgsBuilder.buildArgsForESDTTransfer(transfer); - extraGasForTransfer = new BigNumber(this.config.gasLimitESDTTransfer).plus( - new BigNumber(ADDITIONAL_GAS_FOR_ESDT_TRANSFER), - ); + extraGasForTransfer = this.config.gasLimitESDTTransfer + BigInt(ADDITIONAL_GAS_FOR_ESDT_TRANSFER); } else { transferArgs = this.dataArgsBuilder.buildArgsForSingleESDTNFTTransfer(transfer, receiver); - extraGasForTransfer = new BigNumber(this.config.gasLimitESDTNFTTransfer).plus( - new BigNumber(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER), - ); + extraGasForTransfer = this.config.gasLimitESDTNFTTransfer + BigInt(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER); receiver = options.sender; } diff --git a/src/utils.codec.spec.ts b/src/utils.codec.spec.ts index ba38f632..0da21c14 100644 --- a/src/utils.codec.spec.ts +++ b/src/utils.codec.spec.ts @@ -1,11 +1,22 @@ import { assert } from "chai"; -import { isPaddedHex, numberToPaddedHex, zeroPadStringIfOddLength, byteArrayToHex, utf8ToHex } from "./utils.codec"; +import { byteArrayToHex, isPaddedHex, numberToPaddedHex, utf8ToHex, zeroPadStringIfOddLength } from "./utils.codec"; describe("test codec utils", () => { it("should convert numberToPaddedHex", () => { + assert.equal(numberToPaddedHex(0), "00"); assert.equal(numberToPaddedHex(1), "01"); assert.equal(numberToPaddedHex(10), "0a"); assert.equal(numberToPaddedHex(256), "0100"); + + assert.equal(numberToPaddedHex(0n), "00"); + assert.equal(numberToPaddedHex(1n), "01"); + assert.equal(numberToPaddedHex(10n), "0a"); + assert.equal(numberToPaddedHex(256n), "0100"); + + assert.equal(numberToPaddedHex("0"), "00"); + assert.equal(numberToPaddedHex("1"), "01"); + assert.equal(numberToPaddedHex("10"), "0a"); + assert.equal(numberToPaddedHex("256"), "0100"); }); it("should check if isPaddedHex", () => { diff --git a/src/utils.codec.ts b/src/utils.codec.ts index 6d326412..0e03b658 100644 --- a/src/utils.codec.ts +++ b/src/utils.codec.ts @@ -1,10 +1,18 @@ import BigNumber from "bignumber.js"; -import * as contractsCodecUtils from "./smartcontracts/codec/utils"; import { Address } from "./address"; import { IAddress } from "./interface"; +import * as contractsCodecUtils from "./smartcontracts/codec/utils"; + +export function numberToPaddedHex(value: bigint | number | BigNumber.Value) { + let hexableNumber: { toString(radix?: number): string }; + + if (typeof value === "bigint" || typeof value === "number") { + hexableNumber = value; + } else { + hexableNumber = new BigNumber(value); + } -export function numberToPaddedHex(value: BigNumber.Value) { - let hex = new BigNumber(value).toString(16); + const hex = hexableNumber.toString(16); return zeroPadStringIfOddLength(hex); } diff --git a/tsconfig.json b/tsconfig.json index 716261e6..089ef306 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "CommonJS", - "target": "es2015", + "target": "ES2020", "outDir": "out", "lib": [ - "ES2015", + "ES2020", "DOM" ], "sourceMap": true, diff --git a/tsconfig.tests.json b/tsconfig.tests.json index 29ec228e..52a987db 100644 --- a/tsconfig.tests.json +++ b/tsconfig.tests.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "CommonJS", - "target": "ES2015", + "target": "ES2020", "outDir": "out-tests", "lib": [ - "ES2015", + "ES2020", "DOM" ], "sourceMap": true,