diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ff7b20b..d6bf80181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes will be documented in this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. ## Unreleased - - [Breaking changes: cleanup and minor improvements prior release (step 1)](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/190) + - [TokenPayment instead of Balance, where applicable](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/193) ## 10.0.0-alpha.5 - [Breaking change: adjustements to transaction awaitening and completion, transaction watcher](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/173) @@ -52,6 +52,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - Removed not used interfaces (e.g. `ISignable` is not needed in erdjs, but in walletcore). - Removed `interaction.getContract()`. Add `interaction.getContractAddress()`. - Remove `interaction.withGasLimitComponents()`. Not so helpful anymore (since `NetworkConfig` isn't a singleton anymore). + - `TokenPayment` should be used instead of `Balance`, where applicable. ## [10.0.0-beta.3] - [Extract dapp / signing providers to separate repositories](https://github.com/ElrondNetwork/elrond-sdk-erdjs/pull/170) diff --git a/package-lock.json b/package-lock.json index c2a043fee..244023b39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1533,9 +1533,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001327", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", - "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", + "version": "1.0.30001328", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz", + "integrity": "sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==", "dev": true, "funding": [ { @@ -2065,9 +2065,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.106", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.106.tgz", - "integrity": "sha512-ZYfpVLULm67K7CaaGP7DmjyeMY4naxsbTy+syVVxT6QHI1Ww8XbJjmr9fDckrhq44WzCrcC5kH3zGpdusxwwqg==", + "version": "1.4.107", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.107.tgz", + "integrity": "sha512-Huen6taaVrUrSy8o7mGStByba8PfOWWluHNxSHGBrCgEdFVLtvdQDBr9LBCF9Uci8SYxh28QNNMO0oC17wbGAg==", "dev": true }, "node_modules/elliptic": { @@ -2107,9 +2107,9 @@ } }, "node_modules/es-abstract": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", - "integrity": "sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.3.tgz", + "integrity": "sha512-4axXLNovnMYf0+csS5rVnS5hLmV1ek+ecx9MuCjByL1E5Nn54avf6CHQxIjgQIHBnfX9AMxTRIy0q+Yu5J/fXA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", @@ -2123,7 +2123,7 @@ "is-callable": "^1.2.4", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", "is-weakref": "^1.0.2", "object-inspect": "^1.12.0", @@ -4012,9 +4012,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", + "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", "dev": true }, "node_modules/normalize-path": { @@ -6940,9 +6940,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001327", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", - "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", + "version": "1.0.30001328", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz", + "integrity": "sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==", "dev": true }, "chai": { @@ -7397,9 +7397,9 @@ } }, "electron-to-chromium": { - "version": "1.4.106", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.106.tgz", - "integrity": "sha512-ZYfpVLULm67K7CaaGP7DmjyeMY4naxsbTy+syVVxT6QHI1Ww8XbJjmr9fDckrhq44WzCrcC5kH3zGpdusxwwqg==", + "version": "1.4.107", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.107.tgz", + "integrity": "sha512-Huen6taaVrUrSy8o7mGStByba8PfOWWluHNxSHGBrCgEdFVLtvdQDBr9LBCF9Uci8SYxh28QNNMO0oC17wbGAg==", "dev": true }, "elliptic": { @@ -7441,9 +7441,9 @@ } }, "es-abstract": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", - "integrity": "sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.3.tgz", + "integrity": "sha512-4axXLNovnMYf0+csS5rVnS5hLmV1ek+ecx9MuCjByL1E5Nn54avf6CHQxIjgQIHBnfX9AMxTRIy0q+Yu5J/fXA==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -7457,7 +7457,7 @@ "is-callable": "^1.2.4", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", "is-weakref": "^1.0.2", "object-inspect": "^1.12.0", @@ -8912,9 +8912,9 @@ "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", + "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", "dev": true }, "normalize-path": { diff --git a/src/account.ts b/src/account.ts index dd8054ce8..37236db71 100644 --- a/src/account.ts +++ b/src/account.ts @@ -21,7 +21,7 @@ export class Account { /** * The balance of the account. */ - balance: Balance = Egld("0"); + balance: IAccountBalance = Egld("0"); /** * Creates an account object from an address diff --git a/src/balanceBuilder.ts b/src/balanceBuilder.ts index b81024d8d..27f7ab0f8 100644 --- a/src/balanceBuilder.ts +++ b/src/balanceBuilder.ts @@ -5,6 +5,7 @@ import { Token, TokenType } from "./token"; /** * Creates balances for ESDTs (Fungible, Semi-Fungible (SFT) or Non-Fungible Tokens). + * @deprecated: when preparing token transfers, one should use the class "TokenPayment" instead. */ export interface BalanceBuilder { @@ -60,6 +61,9 @@ export interface BalanceBuilder { one(): Balance; } +/** + * @deprecated: when preparing token transfers, one should use the class "TokenPayment" instead. + */ class BalanceBuilderImpl { readonly token: Token; nonce_: BigNumber | null; @@ -114,6 +118,9 @@ class BalanceBuilderImpl { } } +/** + * @deprecated: when preparing token transfers, one should use the class "TokenPayment" instead. + */ export function createBalanceBuilder(token: Token): BalanceBuilder { let impl = new BalanceBuilderImpl(token); let denominated = impl.value.bind(impl); diff --git a/src/errors.ts b/src/errors.ts index 55298341f..9cdcb0229 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -63,21 +63,8 @@ export class Err extends Error { * Signals invalid arguments for a function, for an operation. */ export class ErrInvalidArgument extends Err { - public constructor( - name: string, - value?: any, - reason: string = "not specified", - inner?: Error - ) { - super(ErrInvalidArgument.getMessage(name, value, reason), inner); - } - - static getMessage(name: string, value?: any, reason?: string): string { - if (value) { - return `Invalid argument "${name}": ${value}. Reason: ${reason}`; - } - - return `Invalid argument "${name}"`; + public constructor(message: string, inner?: Error) { + super(`Invalid argument: ${message}`, inner); } } diff --git a/src/index.ts b/src/index.ts index 045b14e6e..8429affb7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,4 +18,5 @@ export * from "./utils"; export * from "./scArgumentsParser"; export * from "./esdtHelpers"; export * from "./token"; +export * from "./tokenPayment"; export * from "./smartcontracts"; diff --git a/src/interface.ts b/src/interface.ts index 07cbe8fa0..1d368143b 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,3 +1,4 @@ +import BigNumber from "bignumber.js"; import { ITransactionOnNetwork } from "./interfaceOfNetwork"; export interface ITransactionFetcher { @@ -15,3 +16,9 @@ export interface INonce { valueOf(): number; } export interface IChainID { valueOf(): string; } export interface IGasLimit { valueOf(): number; } export interface IGasPrice { valueOf(): number; } + +export interface ITokenPayment { + readonly tokenIdentifier: string; + readonly nonce: number; + readonly amountAsBigInteger: BigNumber; +} diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index 2f841eb0c..b83685c93 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -1,10 +1,11 @@ import * as errors from "../errors"; -import { Balance } from "../balance"; import { bigIntToBuffer } from "../smartcontracts/codec/utils"; import { Transaction } from "../transaction"; import { proto } from "./compiled"; import {TRANSACTION_OPTIONS_DEFAULT} from "../constants"; import { Address } from "../address"; +import { ITransactionValue } from "../interface"; +import BigNumber from "bignumber.js"; /** * Hides away the serialization complexity, for each type of object (e.g. transactions). @@ -22,7 +23,7 @@ export class ProtoSerializer { let protoTransaction = new proto.Transaction({ // elrond-go's serializer handles nonce == 0 differently, thus we treat 0 as "undefined". Nonce: transaction.getNonce().valueOf() ? transaction.getNonce().valueOf() : undefined, - Value: this.serializeBalance(transaction.getValue()), + Value: this.serializeTransactionValue(transaction.getValue()), RcvAddr: receiverPubkey, RcvUserName: null, SndAddr: senderPubkey, @@ -47,8 +48,8 @@ export class ProtoSerializer { /** * Custom serialization, compatible with elrond-go. */ - private serializeBalance(balance: Balance): Buffer { - let value = balance.valueOf(); + private serializeTransactionValue(transactionValue: ITransactionValue): Buffer { + let value = new BigNumber(transactionValue.toString()); if (value.isZero()) { return Buffer.from([0, 0]); } diff --git a/src/smartcontracts/codec/boolean.ts b/src/smartcontracts/codec/boolean.ts index d1b2c1b50..8499bfc1e 100644 --- a/src/smartcontracts/codec/boolean.ts +++ b/src/smartcontracts/codec/boolean.ts @@ -18,7 +18,7 @@ export class BooleanBinaryCodec { decodeTopLevel(buffer: Buffer): BooleanValue { if (buffer.length > 1) { - throw new errors.ErrInvalidArgument("buffer", buffer, "should be a buffer of size <= 1"); + throw new errors.ErrInvalidArgument("buffer should be of size <= 1"); } let firstByte = buffer[0]; diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 468fb2d97..47c3cfd4e 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -16,11 +16,10 @@ import { ChainID, GasLimit } from "../networkParams"; import { ContractFunction } from "./function"; import { Nonce } from "../nonce"; import { ReturnCode } from "./returnCode"; -import { Balance } from "../balance"; import BigNumber from "bignumber.js"; import { BytesValue } from "./typesystem/bytes"; import { Token, TokenType } from "../token"; -import { createBalanceBuilder } from "../balanceBuilder"; +import { TokenPayment } from "../tokenPayment"; import { ContractQueryResponse } from "@elrondnetwork/erdjs-network-providers"; describe("test smart contract interactor", function() { @@ -39,13 +38,13 @@ describe("test smart contract interactor", function() { let transaction = interaction .withNonce(new Nonce(7)) - .withValue(Balance.egld(1)) + .withValue(TokenPayment.egldFromAmount(1)) .withGasLimit(new GasLimit(20000000)) .buildTransaction(); assert.deepEqual(transaction.getReceiver(), dummyAddress); - assert.deepEqual(transaction.getValue(), Balance.egld(1)); - assert.deepEqual(transaction.getNonce(), new Nonce(7)); + assert.equal(transaction.getValue().toString(), "1000000000000000000"); + assert.equal(transaction.getNonce(), 7); assert.equal(transaction.getGasLimit().valueOf(), 20000000); }); @@ -54,10 +53,10 @@ describe("test smart contract interactor", function() { let dummyFunction = new ContractFunction("dummy"); let alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - const TokenFoo = createBalanceBuilder(new Token({ identifier: "FOO-6ce17b", decimals: 0, type: TokenType.Fungible })); - const TokenBar = createBalanceBuilder(new Token({ identifier: "BAR-5bc08f", decimals: 3, type: TokenType.Fungible })); - const LKMEX = createBalanceBuilder(new Token({ identifier: "LKMEX-aab910", decimals: 18, type: TokenType.Semifungible })); - const Strămoși = createBalanceBuilder(new Token({ identifier: "MOS-b9b4b2", decimals: 0, type: TokenType.Nonfungible })); + const TokenFoo = (amount: BigNumber.Value) => TokenPayment.fungibleFromAmount("FOO-6ce17b", amount, 0); + const TokenBar = (amount: BigNumber.Value) => TokenPayment.fungibleFromAmount("BAR-5bc08f", amount, 3); + const LKMEX = (nonce: number, amount: BigNumber.Value) => TokenPayment.metaEsdtFromAmount("LKMEX-aab910", nonce, amount, 18); + const Strămoși = (nonce: number) => TokenPayment.nonFungible("MOS-b9b4b2", nonce); const hexFoo = "464f4f2d366365313762"; const hexBar = "4241522d356263303866"; @@ -68,21 +67,21 @@ describe("test smart contract interactor", function() { // ESDT, single let transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTTransfer(TokenFoo("10")) + .withSingleESDTTransfer(TokenFoo(10)) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTTransfer@${hexFoo}@0a@${hexDummyFunction}`); // Meta ESDT (special SFT), single transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTNFTTransfer(LKMEX.nonce(123456).value(123.456), alice) + .withSingleESDTNFTTransfer(LKMEX(123456, 123.456), alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexLKMEX}@01e240@06b14bd1e6eea00000@${hexContractAddress}@${hexDummyFunction}`); // NFT, single transaction = new Interaction(contract, dummyFunction, []) - .withSingleESDTNFTTransfer(Strămoși.nonce(1).one(), alice) + .withSingleESDTNFTTransfer(Strămoși(1), alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `ESDTNFTTransfer@${hexStrămoși}@01@01@${hexContractAddress}@${hexDummyFunction}`); @@ -96,7 +95,7 @@ describe("test smart contract interactor", function() { // NFT, multiple transaction = new Interaction(contract, dummyFunction, []) - .withMultiESDTNFTTransfer([Strămoși.nonce(1).one(), Strămoși.nonce(42).one()], alice) + .withMultiESDTNFTTransfer([Strămoși(1), Strămoși(42)], alice) .buildTransaction(); assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexStrămoși}@01@01@${hexStrămoși}@2a@01@${hexDummyFunction}`); diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index a5adb5d0d..e3ab6792c 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -1,15 +1,13 @@ -import { Balance } from "../balance"; -import { ChainID, GasLimit } from "../networkParams"; +import { ChainID } from "../networkParams"; import { Transaction } from "../transaction"; import { Query } from "./query"; import { ContractFunction } from "./function"; import { Address } from "../address"; import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; -import { Nonce } from "../nonce"; import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; import { Account } from "../account"; import { CallArguments } from "./interface"; -import { IAddress, IChainID, IGasLimit, IGasPrice, INonce } from "../interface"; +import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ITokenPayment, ITransactionValue } from "../interface"; import { InteractionChecker } from "./interactionChecker"; /** @@ -32,9 +30,9 @@ export class Interaction { private readonly function: ContractFunction; private readonly args: TypedValue[]; - private nonce: INonce = new Nonce(0); - private value: Balance = Balance.Zero(); - private gasLimit: IGasLimit = new GasLimit(0); + private nonce: INonce = 0; + private value: ITransactionValue = "0"; + private gasLimit: IGasLimit = 0; private gasPrice: IGasPrice | undefined = undefined; private chainID: IChainID = ChainID.unspecified(); private querent: IAddress = new Address(); @@ -72,11 +70,11 @@ export class Interaction { return this.args; } - getValue(): Balance { + getValue(): ITransactionValue { return this.value; } - getTokenTransfers(): Balance[] { + getTokenTransfers(): ITokenPayment[] { return this.tokenTransfers.getTransfers(); } @@ -131,25 +129,25 @@ export class Interaction { }); } - withValue(value: Balance): Interaction { + withValue(value: ITransactionValue): Interaction { this.value = value; return this; } - withSingleESDTTransfer(transfer: Balance): Interaction { + withSingleESDTTransfer(transfer: ITokenPayment): Interaction { this.isWithSingleESDTTransfer = true; this.tokenTransfers = new TokenTransfersWithinInteraction([transfer], this); return this; } - withSingleESDTNFTTransfer(transfer: Balance, sender: IAddress) { + withSingleESDTNFTTransfer(transfer: ITokenPayment, sender: IAddress) { this.isWithSingleESDTNFTTransfer = true; this.tokenTransfers = new TokenTransfersWithinInteraction([transfer], this); this.tokenTransfersSender = sender; return this; } - withMultiESDTNFTTransfer(transfers: Balance[], sender: IAddress) { + withMultiESDTNFTTransfer(transfers: ITokenPayment[], sender: IAddress) { this.isWithMultiESDTNFTTransfer = true; this.tokenTransfers = new TokenTransfersWithinInteraction(transfers, this); this.tokenTransfersSender = sender; @@ -198,10 +196,10 @@ export class Interaction { } class TokenTransfersWithinInteraction { - private readonly transfers: Balance[]; + private readonly transfers: ITokenPayment[]; private readonly interaction: Interaction; - constructor(transfers: Balance[], interaction: Interaction) { + constructor(transfers: ITokenPayment[], interaction: Interaction) { this.transfers = transfers; this.interaction = interaction; } @@ -256,20 +254,20 @@ class TokenTransfersWithinInteraction { return new U8Value(this.transfers.length); } - private getTypedTokenIdentifier(transfer: Balance): TypedValue { + private getTypedTokenIdentifier(transfer: ITokenPayment): TypedValue { // Important: for NFTs, this has to be the "collection" name, actually. // We will reconsider adding the field "collection" on "Token" upon merging "ApiProvider" and "ProxyProvider". - return BytesValue.fromUTF8(transfer.token.getTokenIdentifier()); + return BytesValue.fromUTF8(transfer.tokenIdentifier); } - private getTypedTokenNonce(transfer: Balance): TypedValue { + private getTypedTokenNonce(transfer: ITokenPayment): TypedValue { // The token nonce (creation nonce) - return new U64Value(transfer.getNonce()) + return new U64Value(transfer.nonce); } - private getTypedTokenQuantity(transfer: Balance): TypedValue { + private getTypedTokenQuantity(transfer: ITokenPayment): TypedValue { // For NFTs, this will be 1. - return new BigUIntValue(transfer.valueOf()); + return new BigUIntValue(transfer.amountAsBigInteger); } private getTypedTokensReceiver(): TypedValue { diff --git a/src/smartcontracts/interactionChecker.ts b/src/smartcontracts/interactionChecker.ts index a2efd2be2..d383d54fa 100644 --- a/src/smartcontracts/interactionChecker.ts +++ b/src/smartcontracts/interactionChecker.ts @@ -1,6 +1,7 @@ import * as errors from "../errors"; import { EndpointDefinition } from "./typesystem"; import { Interaction } from "./interaction"; +import BigNumber from "bignumber.js"; /** * An interaction checker that aims to be as strict as possible. @@ -16,7 +17,7 @@ export class InteractionChecker { } private checkPayable(interaction: Interaction, definition: EndpointDefinition) { - let hasValue = interaction.getValue().isSet(); + let hasValue = !new BigNumber(interaction.getValue().toString()).isZero(); let isPayableInEGLD = definition.modifiers.isPayableInEGLD(); if (hasValue && !isPayableInEGLD) { diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 800e79add..53985cd53 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -1,5 +1,4 @@ -import { Balance } from "../balance"; -import { IAddress, IChainID, IGasLimit, IGasPrice } from "../interface"; +import { IAddress, IChainID, IGasLimit, IGasPrice, ITransactionValue } from "../interface"; import { Transaction } from "../transaction"; import { Code } from "./code"; import { CodeMetadata } from "./codeMetadata"; @@ -36,7 +35,7 @@ export interface DeployArguments { code: Code; codeMetadata?: CodeMetadata; initArguments?: TypedValue[]; - value?: Balance; + value?: ITransactionValue; gasLimit: IGasLimit; gasPrice?: IGasPrice; chainID: IChainID; @@ -46,7 +45,7 @@ export interface UpgradeArguments { code: Code; codeMetadata?: CodeMetadata; initArguments?: TypedValue[]; - value?: Balance; + value?: ITransactionValue; gasLimit: IGasLimit; gasPrice?: IGasPrice; chainID: IChainID; @@ -55,7 +54,7 @@ export interface UpgradeArguments { export interface CallArguments { func: ContractFunction; args?: TypedValue[]; - value?: Balance; + value?: ITransactionValue; gasLimit: IGasLimit; receiver?: IAddress; gasPrice?: IGasPrice; @@ -65,7 +64,7 @@ export interface CallArguments { export interface QueryArguments { func: ContractFunction; args?: TypedValue[]; - value?: Balance; + value?: ITransactionValue; caller?: IAddress } diff --git a/src/smartcontracts/typesystem/numerical.ts b/src/smartcontracts/typesystem/numerical.ts index b77ece44d..e7222e167 100644 --- a/src/smartcontracts/typesystem/numerical.ts +++ b/src/smartcontracts/typesystem/numerical.ts @@ -164,11 +164,11 @@ export class NumericalValue extends PrimitiveValue { this.withSign = type.withSign; if (this.value.isNaN()) { - throw new errors.ErrInvalidArgument("value", value, "not a number"); + throw new errors.ErrInvalidArgument(`not a number: ${value}`); } if (!this.withSign && this.value.isNegative()) { - throw new errors.ErrInvalidArgument("value", value.toString(10), "negative, but type is unsigned"); + throw new errors.ErrInvalidArgument(`negative, but type is unsigned: ${value}`); } } diff --git a/src/tokenPayment.spec.ts b/src/tokenPayment.spec.ts new file mode 100644 index 000000000..f686572b0 --- /dev/null +++ b/src/tokenPayment.spec.ts @@ -0,0 +1,60 @@ +import { assert } from "chai"; +import { TokenPayment } from "./tokenPayment"; + +describe("test token payment", () => { + it("should work with EGLD", () => { + assert.equal(TokenPayment.egldFromAmount("1").toString(), "1000000000000000000"); + assert.equal(TokenPayment.egldFromAmount("10").toString(), "10000000000000000000"); + assert.equal(TokenPayment.egldFromAmount("100").toString(), "100000000000000000000"); + assert.equal(TokenPayment.egldFromAmount("1000").toString(), "1000000000000000000000"); + assert.equal(TokenPayment.egldFromAmount("0.1").toString(), "100000000000000000"); + assert.equal(TokenPayment.egldFromAmount("0.123456789").toString(), "123456789000000000"); + assert.equal(TokenPayment.egldFromAmount("0.123456789123456789").toString(), "123456789123456789"); + assert.equal(TokenPayment.egldFromAmount("0.123456789123456789777").toString(), "123456789123456789"); + assert.equal(TokenPayment.egldFromAmount("0.123456789123456789777777888888").toString(), "123456789123456789"); + + assert.equal(TokenPayment.egldFromAmount(0.1).toPrettyString(), "0.100000000000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount(1).toPrettyString(), "1.000000000000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount(10).toPrettyString(), "10.000000000000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount(100).toPrettyString(), "100.000000000000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount(1000).toPrettyString(), "1000.000000000000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount("0.123456789").toPrettyString(), "0.123456789000000000 EGLD"); + assert.equal(TokenPayment.egldFromAmount("0.123456789123456789777777888888").toPrettyString(), "0.123456789123456789 EGLD"); + + assert.equal(TokenPayment.egldFromBigInteger("1").toString(), "1"); + assert.equal(TokenPayment.egldFromBigInteger("1").toPrettyString(), "0.000000000000000001 EGLD"); + assert.isTrue(TokenPayment.egldFromAmount("1").isEgld()); + }); + + it("should work with USDC", () => { + let identifier = "USDC-c76f1f"; + let numDecimals = 6; + + assert.equal(TokenPayment.fungibleFromAmount(identifier, "1", numDecimals).toString(), "1000000"); + assert.equal(TokenPayment.fungibleFromAmount(identifier, "0.1", numDecimals).toString(), "100000"); + assert.equal(TokenPayment.fungibleFromAmount(identifier, "0.123456789", numDecimals).toString(), "123456"); + assert.equal(TokenPayment.fungibleFromBigInteger(identifier, "1000000", numDecimals).toString(), "1000000"); + assert.equal(TokenPayment.fungibleFromBigInteger(identifier, "1000000", numDecimals).toPrettyString(), "1.000000 USDC-c76f1f"); + }); + + it("should work with MetaESDT", () => { + let identifier = "MEXFARML-28d646"; + let numDecimals = 18; + let nonce = 12345678; + + let tokenPayment = TokenPayment.metaEsdtFromAmount(identifier, nonce, "0.1", numDecimals) + assert.equal(tokenPayment.tokenIdentifier, identifier); + assert.equal(tokenPayment.nonce, nonce); + assert.equal(tokenPayment.toString(), "100000000000000000"); + }); + + it("should work with NFTs", () => { + let identifier = "ERDJS-38f249"; + let nonce = 1; + + let tokenPayment = TokenPayment.nonFungible(identifier, nonce) + assert.equal(tokenPayment.tokenIdentifier, identifier); + assert.equal(tokenPayment.nonce, nonce); + assert.equal(tokenPayment.toPrettyString(), "1 ERDJS-38f249"); + }); +}); diff --git a/src/tokenPayment.ts b/src/tokenPayment.ts new file mode 100644 index 000000000..ae03597ca --- /dev/null +++ b/src/tokenPayment.ts @@ -0,0 +1,79 @@ +import BigNumber from "bignumber.js"; +import { ErrInvalidArgument } from "./errors"; + +const EGLDTokenIdentifier = "EGLD"; +const EGLDNumDecimals = 18; + +export class TokenPayment { + readonly tokenIdentifier: string; + readonly nonce: number; + readonly amountAsBigInteger: BigNumber; + private readonly numDecimals: number; + + constructor(tokenIdentifier: string, nonce: number, amountAsBigInteger: BigNumber.Value, numDecimals: number) { + let amount = new BigNumber(amountAsBigInteger); + if (!amount.isInteger() || amount.isNegative()) { + throw new ErrInvalidArgument(`bad amountAsBigInteger: ${amountAsBigInteger}`); + } + + this.tokenIdentifier = tokenIdentifier; + this.nonce = nonce; + this.amountAsBigInteger = amount; + this.numDecimals = numDecimals; + } + + static egldFromAmount(amount: BigNumber.Value) { + let amountAsBigInteger = new BigNumber(amount).shiftedBy(EGLDNumDecimals).decimalPlaces(0); + return this.egldFromBigInteger(amountAsBigInteger); + } + + static egldFromBigInteger(amountAsBigInteger: BigNumber.Value) { + return new TokenPayment(EGLDTokenIdentifier, 0, amountAsBigInteger, EGLDNumDecimals); + } + + static fungibleFromAmount(tokenIdentifier: string, amount: BigNumber.Value, numDecimals: number = 0): TokenPayment { + let amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); + return this.fungibleFromBigInteger(tokenIdentifier, amountAsBigInteger, numDecimals); + } + + static fungibleFromBigInteger(tokenIdentifier: string, amountAsBigInteger: BigNumber.Value, numDecimals: number = 0): TokenPayment { + return new TokenPayment(tokenIdentifier, 0, amountAsBigInteger, numDecimals); + } + + static nonFungible(tokenIdentifier: string, nonce: number) { + return new TokenPayment(tokenIdentifier, nonce, 1, 0); + } + + static semiFungible(tokenIdentifier: string, nonce: number, quantity: number) { + return new TokenPayment(tokenIdentifier, nonce, quantity, 0); + } + + static metaEsdtFromAmount(tokenIdentifier: string, nonce: number, amount: BigNumber.Value, numDecimals = 0) { + let amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); + return this.metaEsdtFromBigInteger(tokenIdentifier, nonce, amountAsBigInteger, numDecimals); + } + + static metaEsdtFromBigInteger(tokenIdentifier: string, nonce: number, amountAsBigInteger: BigNumber.Value, numDecimals = 0) { + return new TokenPayment(tokenIdentifier, nonce, amountAsBigInteger, numDecimals); + } + + toString() { + return this.amountAsBigInteger.toFixed(0); + } + + toPrettyString() { + return `${this.toRationalNumber()} ${this.tokenIdentifier}`; + } + + toRationalNumber() { + return this.amountAsBigInteger.shiftedBy(-this.numDecimals).toFixed(this.numDecimals); + } + + isEgld() { + return this.tokenIdentifier == EGLDTokenIdentifier; + } + + isFungible() { + return this.nonce == 0; + } +} diff --git a/src/transaction.local.net.spec.ts b/src/transaction.local.net.spec.ts index 47c2bf20e..0fda96610 100644 --- a/src/transaction.local.net.spec.ts +++ b/src/transaction.local.net.spec.ts @@ -25,7 +25,7 @@ describe("test transaction", function () { await alice.sync(provider); await bob.sync(provider); - let initialBalanceOfBob = bob.account.balance; + let initialBalanceOfBob = Balance.fromString(bob.account.balance.toString()); let transactionOne = new Transaction({ receiver: bob.address, @@ -55,7 +55,7 @@ describe("test transaction", function () { await watcher.awaitCompleted(transactionTwo); await bob.sync(provider); - let newBalanceOfBob = bob.account.balance; + let newBalanceOfBob = Balance.fromString(bob.account.balance.toString()); assert.deepEqual(Balance.egld(85).valueOf(), newBalanceOfBob.valueOf().minus(initialBalanceOfBob.valueOf())); }); diff --git a/src/transaction.ts b/src/transaction.ts index 8bd857f4f..24ebbd701 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,5 +1,5 @@ import { BigNumber } from "bignumber.js"; -import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ISignature } from "./interface"; +import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ISignature, ITransactionValue } from "./interface"; import { Address } from "./address"; import { Balance } from "./balance"; import { @@ -33,7 +33,7 @@ export class Transaction { /** * The value to transfer. */ - private value: Balance; + private value: ITransactionValue; /** * The address of the sender. @@ -101,7 +101,7 @@ export class Transaction { options, }: { nonce?: Nonce; - value?: Balance; + value?: ITransactionValue; receiver: IAddress; sender?: IAddress; gasPrice?: IGasPrice; @@ -150,11 +150,11 @@ export class Transaction { this.nonce = nonce; } - getValue(): Balance { + getValue(): ITransactionValue { return this.value; } - setValue(value: Balance) { + setValue(value: ITransactionValue) { this.value = value; }