From eb8d4fd0ec0980759ba8650e74335ea2a91bb5ce Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jun 2024 11:24:45 +0300 Subject: [PATCH 01/16] implement relayed v3 in relayed transactions factory --- src/converters/transactionsConverter.ts | 8 ++ src/converters/transactionsConverters.spec.ts | 79 +++++++++++++++++++ src/interface.ts | 4 + src/proto/compiled.js | 77 ++++++++++++++++++ src/proto/serializer.spec.ts | 39 +++++++++ src/proto/serializer.ts | 73 +++++++++++------ src/proto/transaction.proto | 32 ++++---- src/transaction.ts | 16 ++++ src/transactionComputer.ts | 31 ++++++++ .../relayedTransactionsFactory.spec.ts | 65 +++++++++++++++ .../relayedTransactionsFactory.ts | 36 ++++++++- 11 files changed, 416 insertions(+), 44 deletions(-) diff --git a/src/converters/transactionsConverter.ts b/src/converters/transactionsConverter.ts index 2f0df2813..c6001c5d6 100644 --- a/src/converters/transactionsConverter.ts +++ b/src/converters/transactionsConverter.ts @@ -28,6 +28,10 @@ export class TransactionsConverter { guardian: transaction.guardian ? transaction.guardian : undefined, signature: this.toHexOrUndefined(transaction.signature), guardianSignature: this.toHexOrUndefined(transaction.guardianSignature), + relayer: transaction.relayer ? transaction.relayer : undefined, + innerTransactions: transaction.innerTransactions.length + ? transaction.innerTransactions.map((tx) => this.transactionToPlainObject(tx)) + : undefined, }; return plainObject; @@ -58,6 +62,10 @@ export class TransactionsConverter { options: Number(object.options), signature: this.bufferFromHex(object.signature), guardianSignature: this.bufferFromHex(object.guardianSignature), + relayer: object.relayer, + innerTransactions: object.innerTransactions + ? object.innerTransactions.map((tx) => this.plainObjectToTransaction(tx)) + : undefined, }); return transaction; diff --git a/src/converters/transactionsConverters.spec.ts b/src/converters/transactionsConverters.spec.ts index dd44b173f..a61dd3bc8 100644 --- a/src/converters/transactionsConverters.spec.ts +++ b/src/converters/transactionsConverters.spec.ts @@ -59,6 +59,85 @@ describe("test transactions converter", async () => { guardian: undefined, signature: undefined, guardianSignature: undefined, + relayer: undefined, + innerTransactions: undefined, + }); + }); + + it("converts relayedV3 transaction to plain object and back", () => { + const converter = new TransactionsConverter(); + + const innerTx = new Transaction({ + nonce: 90, + value: BigInt("123456789000000000000000000000"), + sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + senderUsername: "alice", + receiverUsername: "bob", + gasPrice: 1000000000, + gasLimit: 80000, + data: Buffer.from("hello"), + chainID: "localnet", + version: 2, + relayer: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + }); + + const relayedTx = new Transaction({ + nonce: 77, + value: BigInt("0"), + sender: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + receiver: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + gasPrice: 1000000000, + gasLimit: 50000, + chainID: "localnet", + version: 2, + innerTransactions: [innerTx], + }); + + const plainObject = converter.transactionToPlainObject(relayedTx); + const restoredTransaction = converter.plainObjectToTransaction(plainObject); + + assert.deepEqual(plainObject, relayedTx.toPlainObject()); + assert.deepEqual(restoredTransaction, Transaction.fromPlainObject(plainObject)); + assert.deepEqual(restoredTransaction, relayedTx); + assert.deepEqual(plainObject, { + nonce: 77, + value: "0", + sender: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + receiver: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000, + data: undefined, + chainID: "localnet", + version: 2, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + relayer: undefined, + innerTransactions: [ + { + nonce: 90, + value: "123456789000000000000000000000", + sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + senderUsername: "YWxpY2U=", + receiverUsername: "Ym9i", + gasPrice: 1000000000, + gasLimit: 80000, + data: "aGVsbG8=", + chainID: "localnet", + version: 2, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + relayer: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + innerTransactions: undefined, + }, + ], }); }); diff --git a/src/interface.ts b/src/interface.ts index 97c86a54f..1a1bc6324 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -24,6 +24,8 @@ export interface IPlainTransactionObject { options?: number; signature?: string; guardianSignature?: string; + relayer?: string; + innerTransactions?: IPlainTransactionObject[]; } export interface ISignature { @@ -104,4 +106,6 @@ export interface ITransaction { guardian: string; signature: Uint8Array; guardianSignature: Uint8Array; + relayer: string; + innerTransactions: ITransaction[]; } diff --git a/src/proto/compiled.js b/src/proto/compiled.js index feed796f9..2d02a2512 100644 --- a/src/proto/compiled.js +++ b/src/proto/compiled.js @@ -46,6 +46,8 @@ * @property {number|null} [Options] Transaction Options * @property {Uint8Array|null} [GuardianAddr] Transaction GuardianAddr * @property {Uint8Array|null} [GuardianSignature] Transaction GuardianSignature + * @property {Uint8Array|null} [Relayer] Transaction Relayer + * @property {Array.|null} [InnerTransactions] Transaction InnerTransactions */ /** @@ -57,6 +59,7 @@ * @param {proto.ITransaction=} [properties] Properties to set */ function Transaction(properties) { + this.InnerTransactions = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -183,6 +186,22 @@ */ Transaction.prototype.GuardianSignature = $util.newBuffer([]); + /** + * Transaction Relayer. + * @member {Uint8Array} Relayer + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Relayer = $util.newBuffer([]); + + /** + * Transaction InnerTransactions. + * @member {Array.} InnerTransactions + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.InnerTransactions = $util.emptyArray; + /** * Creates a new Transaction instance using the specified properties. * @function create @@ -237,6 +256,11 @@ writer.uint32(/* id 14, wireType 2 =*/114).bytes(message.GuardianAddr); if (message.GuardianSignature != null && Object.hasOwnProperty.call(message, "GuardianSignature")) writer.uint32(/* id 15, wireType 2 =*/122).bytes(message.GuardianSignature); + if (message.Relayer != null && Object.hasOwnProperty.call(message, "Relayer")) + writer.uint32(/* id 16, wireType 2 =*/130).bytes(message.Relayer); + if (message.InnerTransactions != null && message.InnerTransactions.length) + for (var i = 0; i < message.InnerTransactions.length; ++i) + $root.proto.Transaction.encode(message.InnerTransactions[i], writer.uint32(/* id 17, wireType 2 =*/138).fork()).ldelim(); return writer; }; @@ -331,6 +355,16 @@ message.GuardianSignature = reader.bytes(); break; } + case 16: { + message.Relayer = reader.bytes(); + break; + } + case 17: { + if (!(message.InnerTransactions && message.InnerTransactions.length)) + message.InnerTransactions = []; + message.InnerTransactions.push($root.proto.Transaction.decode(reader, reader.uint32())); + break; + } default: reader.skipType(tag & 7); break; @@ -411,6 +445,18 @@ if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) if (!(message.GuardianSignature && typeof message.GuardianSignature.length === "number" || $util.isString(message.GuardianSignature))) return "GuardianSignature: buffer expected"; + if (message.Relayer != null && message.hasOwnProperty("Relayer")) + if (!(message.Relayer && typeof message.Relayer.length === "number" || $util.isString(message.Relayer))) + return "Relayer: buffer expected"; + if (message.InnerTransactions != null && message.hasOwnProperty("InnerTransactions")) { + if (!Array.isArray(message.InnerTransactions)) + return "InnerTransactions: array expected"; + for (var i = 0; i < message.InnerTransactions.length; ++i) { + var error = $root.proto.Transaction.verify(message.InnerTransactions[i]); + if (error) + return "InnerTransactions." + error; + } + } return null; }; @@ -507,6 +553,21 @@ $util.base64.decode(object.GuardianSignature, message.GuardianSignature = $util.newBuffer($util.base64.length(object.GuardianSignature)), 0); else if (object.GuardianSignature.length >= 0) message.GuardianSignature = object.GuardianSignature; + if (object.Relayer != null) + if (typeof object.Relayer === "string") + $util.base64.decode(object.Relayer, message.Relayer = $util.newBuffer($util.base64.length(object.Relayer)), 0); + else if (object.Relayer.length >= 0) + message.Relayer = object.Relayer; + if (object.InnerTransactions) { + if (!Array.isArray(object.InnerTransactions)) + throw TypeError(".proto.Transaction.InnerTransactions: array expected"); + message.InnerTransactions = []; + for (var i = 0; i < object.InnerTransactions.length; ++i) { + if (typeof object.InnerTransactions[i] !== "object") + throw TypeError(".proto.Transaction.InnerTransactions: object expected"); + message.InnerTransactions[i] = $root.proto.Transaction.fromObject(object.InnerTransactions[i]); + } + } return message; }; @@ -523,6 +584,8 @@ if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.InnerTransactions = []; if (options.defaults) { if ($util.Long) { var long = new $util.Long(0, 0, true); @@ -611,6 +674,13 @@ if (options.bytes !== Array) object.GuardianSignature = $util.newBuffer(object.GuardianSignature); } + if (options.bytes === String) + object.Relayer = ""; + else { + object.Relayer = []; + if (options.bytes !== Array) + object.Relayer = $util.newBuffer(object.Relayer); + } } if (message.Nonce != null && message.hasOwnProperty("Nonce")) if (typeof message.Nonce === "number") @@ -651,6 +721,13 @@ object.GuardianAddr = options.bytes === String ? $util.base64.encode(message.GuardianAddr, 0, message.GuardianAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianAddr) : message.GuardianAddr; if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) object.GuardianSignature = options.bytes === String ? $util.base64.encode(message.GuardianSignature, 0, message.GuardianSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianSignature) : message.GuardianSignature; + if (message.Relayer != null && message.hasOwnProperty("Relayer")) + object.Relayer = options.bytes === String ? $util.base64.encode(message.Relayer, 0, message.Relayer.length) : options.bytes === Array ? Array.prototype.slice.call(message.Relayer) : message.Relayer; + if (message.InnerTransactions && message.InnerTransactions.length) { + object.InnerTransactions = []; + for (var j = 0; j < message.InnerTransactions.length; ++j) + object.InnerTransactions[j] = $root.proto.Transaction.toObject(message.InnerTransactions[j], options); + } return object; }; diff --git a/src/proto/serializer.spec.ts b/src/proto/serializer.spec.ts index 97a88b5ca..53ecfbb68 100644 --- a/src/proto/serializer.spec.ts +++ b/src/proto/serializer.spec.ts @@ -7,6 +7,7 @@ import { TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { TransactionPayload } from "../transactionPayload"; import { ProtoSerializer } from "./serializer"; +import { TransactionComputer } from "../transactionComputer"; describe("serialize transactions", () => { let wallets: Record; @@ -145,4 +146,42 @@ describe("serialize transactions", () => { "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d086035201545802624051e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06", ); }); + + it("serialize with inner transactions", async () => { + const innerTransaction = new Transaction({ + nonce: 204, + value: "1000000000000000000", + sender: Address.fromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + receiver: Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + senderUsername: "carol", + receiverUsername: "alice", + gasLimit: 50000, + chainID: "T", + }); + + const signer = wallets.carol.signer; + const txComputer = new TransactionComputer(); + innerTransaction.signature = await signer.sign(txComputer.computeBytesForSigning(innerTransaction)); + + const relayedTransaction = new Transaction({ + nonce: 204, + value: "1000000000000000000", + sender: Address.fromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + receiver: Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + senderUsername: "carol", + receiverUsername: "alice", + gasLimit: 50000, + chainID: "T", + relayer: wallets["carol"].address.toBech32(), + innerTransactions: [innerTransaction], + }); + + relayedTransaction.signature = await signer.sign(txComputer.computeBytesForSigning(relayedTransaction)); + + const serializedTransaction = serializer.serializeTransaction(relayedTransaction); + assert.equal( + serializedTransaction.toString("hex"), + "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d0860352015458026240901a6a974d6ab36546e7881c6e0364ec4c61a891aa70e5eb60f818d6c92a39cfa0beac6fab73f503853cfe8fe6149b4be207ddb93788f8450d75a07fa8759d06820120b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba8a01b10108cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d086035201545802624051e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06", + ); + }); }); diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index 8852dcd7a..abe46ebf1 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -1,11 +1,13 @@ import BigNumber from "bignumber.js"; import { Address } from "../address"; -import { TRANSACTION_OPTIONS_DEFAULT } from "../constants"; +import { TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_GUARDED } from "../constants"; import * as errors from "../errors"; -import { ITransactionValue } from "../interface"; +import { ITransaction, ITransactionValue } from "../interface"; import { bigIntToBuffer } from "../smartcontracts/codec/utils"; import { Transaction } from "../transaction"; +const proto = require("./compiled").proto; + /** * Hides away the serialization complexity, for each type of object (e.g. transactions). @@ -16,44 +18,53 @@ export class ProtoSerializer { * Serializes a Transaction object to a Buffer. Handles low-level conversion logic and field-mappings as well. */ serializeTransaction(transaction: Transaction): Buffer { - const proto = require("./compiled").proto; - const receiverPubkey = new Address(transaction.getReceiver().bech32()).pubkey(); - const senderPubkey = new Address(transaction.getSender().bech32()).pubkey(); + const protoTransaction = this.convertToProtoMessage(transaction); + const encoded = proto.Transaction.encode(protoTransaction).finish(); + const buffer = Buffer.from(encoded); + + return buffer; + } + + private convertToProtoMessage(transaction: ITransaction) { + const receiverPubkey = new Address(transaction.receiver).getPublicKey(); + const senderPubkey = new Address(transaction.sender).getPublicKey(); let protoTransaction = new proto.Transaction({ // mx-chain-go's serializer handles nonce == 0 differently, thus we treat 0 as "undefined". - Nonce: transaction.getNonce().valueOf() ? transaction.getNonce().valueOf() : undefined, - Value: this.serializeTransactionValue(transaction.getValue()), + Nonce: Number(transaction.nonce) ? Number(transaction.nonce) : undefined, + Value: this.serializeTransactionValue(transaction.value), RcvAddr: receiverPubkey, - RcvUserName: transaction.getReceiverUsername() - ? Buffer.from(transaction.getReceiverUsername()).toString("base64") + RcvUserName: transaction.receiverUsername + ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined, SndAddr: senderPubkey, - SndUserName: transaction.getSenderUsername() - ? Buffer.from(transaction.getSenderUsername()).toString("base64") + SndUserName: transaction.senderUsername + ? Buffer.from(transaction.senderUsername).toString("base64") : undefined, - GasPrice: transaction.getGasPrice().valueOf(), - GasLimit: transaction.getGasLimit().valueOf(), - Data: transaction.getData().length() == 0 ? null : transaction.getData().valueOf(), - ChainID: Buffer.from(transaction.getChainID().valueOf()), - Version: transaction.getVersion().valueOf(), - Signature: transaction.getSignature(), + GasPrice: Number(transaction.gasPrice), + GasLimit: Number(transaction.gasLimit), + Data: transaction.data.length == 0 ? null : transaction.data, + ChainID: Buffer.from(transaction.chainID), + Version: transaction.version, + Signature: transaction.signature, }); - if (transaction.getOptions().valueOf() !== TRANSACTION_OPTIONS_DEFAULT) { - protoTransaction.Options = transaction.getOptions().valueOf(); + if (transaction.options !== TRANSACTION_OPTIONS_DEFAULT) { + protoTransaction.Options = transaction.options; } - if (transaction.isGuardedTransaction()) { - const guardianAddress = transaction.getGuardian(); - protoTransaction.GuardianAddr = new Address(guardianAddress.bech32()).pubkey(); - protoTransaction.GuardianSignature = transaction.getGuardianSignature(); + if (this.isGuardedTransaction(transaction)) { + protoTransaction.GuardianAddr = new Address(transaction.guardian).getPublicKey(); + protoTransaction.GuardianSignature = transaction.guardianSignature; } - const encoded = proto.Transaction.encode(protoTransaction).finish(); - const buffer = Buffer.from(encoded); + if (transaction.relayer) { + protoTransaction.Relayer = new Address(transaction.relayer).getPublicKey(); + } - return buffer; + protoTransaction.InnerTransactions = transaction.innerTransactions.map((tx) => this.convertToProtoMessage(tx)); + + return protoTransaction; } /** @@ -72,6 +83,16 @@ export class ProtoSerializer { return buffer; } + private isGuardedTransaction(transaction: ITransaction): boolean { + const hasGuardian = transaction.guardian.length > 0; + const hasGuardianSignature = transaction.guardianSignature.length > 0; + return this.isWithGuardian(transaction) && hasGuardian && hasGuardianSignature; + } + + private isWithGuardian(transaction: ITransaction): boolean { + return (transaction.options & TRANSACTION_OPTIONS_TX_GUARDED) == TRANSACTION_OPTIONS_TX_GUARDED; + } + deserializeTransaction(_buffer: Buffer): Transaction { // Not needed (yet). throw new errors.ErrUnsupportedOperation("deserializeTransaction"); diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index c3e46e993..9dd372768 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -10,19 +10,21 @@ package proto; // Transaction holds all the data needed for a value transfer or SC call message Transaction { - uint64 Nonce = 1; - bytes Value = 2; - bytes RcvAddr = 3; - bytes RcvUserName = 4; - bytes SndAddr = 5; - bytes SndUserName = 6; - uint64 GasPrice = 7; - uint64 GasLimit = 8; - bytes Data = 9; - bytes ChainID = 10; - uint32 Version = 11; - bytes Signature = 12; - uint32 Options = 13; - bytes GuardianAddr = 14; - bytes GuardianSignature = 15; + uint64 Nonce = 1; + bytes Value = 2; + bytes RcvAddr = 3; + bytes RcvUserName = 4; + bytes SndAddr = 5; + bytes SndUserName = 6; + uint64 GasPrice = 7; + uint64 GasLimit = 8; + bytes Data = 9; + bytes ChainID = 10; + uint32 Version = 11; + bytes Signature = 12; + uint32 Options = 13; + bytes GuardianAddr = 14; + bytes GuardianSignature = 15; + bytes Relayer = 16; + repeated Transaction InnerTransactions = 17; } diff --git a/src/transaction.ts b/src/transaction.ts index 9678fa25f..a602cca17 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -11,6 +11,7 @@ import { INonce, IPlainTransactionObject, ISignature, + ITransaction, ITransactionOptions, ITransactionPayload, ITransactionValue, @@ -101,6 +102,16 @@ export class Transaction { */ public guardianSignature: Uint8Array; + /** + * The relayer in case it is a relayedV3 Transaction. + */ + public relayer: string; + + /** + * The inner transactions in case it is a relayedV3 Transaction. + */ + public innerTransactions: ITransaction[]; + /** * Creates a new Transaction object. */ @@ -120,6 +131,8 @@ export class Transaction { guardian?: IAddress | string; signature?: Uint8Array; guardianSignature?: Uint8Array; + relayer?: string; + innerTransactions?: ITransaction[]; }) { this.nonce = BigInt(options.nonce?.valueOf() || 0n); // We still rely on "bigNumber" for value, because client code might be passing a BigNumber object as a legacy "ITransactionValue", @@ -139,6 +152,9 @@ export class Transaction { this.signature = options.signature || Buffer.from([]); this.guardianSignature = options.guardianSignature || Buffer.from([]); + + this.relayer = options.relayer || ""; + this.innerTransactions = options.innerTransactions || []; } private addressAsBech32(address: IAddress | string): string { diff --git a/src/transactionComputer.ts b/src/transactionComputer.ts index 8ff65ff40..f3cc21013 100644 --- a/src/transactionComputer.ts +++ b/src/transactionComputer.ts @@ -116,9 +116,40 @@ export class TransactionComputer { version: transaction.version, options: transaction.options ? transaction.options : undefined, guardian: transaction.guardian ? transaction.guardian : undefined, + relayer: transaction.relayer ? transaction.relayer : undefined, + innerTransactions: transaction.innerTransactions.length + ? transaction.innerTransactions.map((tx) => this.toPlainObject(tx)) + : undefined, }; } + private toPlainObject(transaction: ITransaction): any { + return { + nonce: Number(transaction.nonce), + value: transaction.value.toString(), + receiver: transaction.receiver, + sender: transaction.sender, + senderUsername: this.toBase64OrUndefined(transaction.senderUsername), + receiverUsername: this.toBase64OrUndefined(transaction.receiverUsername), + gasPrice: Number(transaction.gasPrice), + gasLimit: Number(transaction.gasLimit), + data: this.toBase64OrUndefined(transaction.data), + signature: this.toHexOrUndefined(transaction.signature), + chainID: transaction.chainID.valueOf(), + version: transaction.version, + options: transaction.options == 0 ? undefined : transaction.options, + guardian: transaction.guardian ? transaction.guardian : undefined, + relayer: transaction.relayer ? transaction.relayer : undefined, + innerTransactions: transaction.innerTransactions.length + ? transaction.innerTransactions.map((tx) => this.toPlainObject(tx)) + : undefined, + }; + } + + private toHexOrUndefined(value?: Uint8Array) { + return value && value.length ? Buffer.from(value).toString("hex") : undefined; + } + private toBase64OrUndefined(value?: string | Uint8Array) { return value && value.length ? Buffer.from(value).toString("base64") : undefined; } diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index e48c995d5..d8df46ad7 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -275,4 +275,69 @@ describe("test relayed v1 transaction builder", function () { "relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c", ); }); + + it("should create relayed v3 transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.toBech32(), + receiver: bob.address.toBech32(), + gasLimit: 50000n, + chainID: config.chainID, + nonce: 0n, + version: 2, + relayer: alice.address.toBech32(), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV3Transaction({ + relayerAddress: alice.address, + innerTransactions: [innerTransaction], + }); + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "6bd446e1f531db190de97adeab7bae3ed332a83d93e47dc29299a0a6868b966b002d0f4395eee450fc89c7677516d7448c6d01245a3fc5c6c65e0bf8dca9540e", + ); + assert.equal(relayedTransaction.gasLimit, 150000n); + }); + + it("should fail to create relayed v3 transaction", async function () { + assert.throws(() => { + factory.createRelayedV3Transaction({ + relayerAddress: alice.address, + innerTransactions: [], + }); + }, "No inner transctions provided"); + + const innerTransaction = new Transaction({ + sender: bob.address.toBech32(), + receiver: bob.address.toBech32(), + gasLimit: 50000n, + chainID: config.chainID, + nonce: 0n, + version: 2, + relayer: carol.address.toBech32(), + }); + + assert.throws(() => { + factory.createRelayedV3Transaction({ + relayerAddress: alice.address, + innerTransactions: [innerTransaction], + }); + }, "Inner transaction is not signed"); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + + assert.throws(() => { + factory.createRelayedV3Transaction({ + relayerAddress: alice.address, + innerTransactions: [innerTransaction], + }); + }, "The inner transaction has an incorrect relayer address"); + }); }); diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index fdf12be61..1a8057851 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -84,11 +84,41 @@ export class RelayedTransactionsFactory { }); } + createRelayedV3Transaction(options: { relayerAddress: IAddress; innerTransactions: ITransaction[] }): Transaction { + if (!options.innerTransactions.length) { + throw new ErrInvalidInnerTransaction("No inner transctions provided"); + } + + let innerTransactionsGasLimit = 0n; + for (const innerTx of options.innerTransactions) { + if (!innerTx.signature.length) { + throw new ErrInvalidInnerTransaction("Inner transaction is not signed"); + } + + if (innerTx.relayer !== options.relayerAddress.bech32()) { + throw new ErrInvalidInnerTransaction("The inner transaction has an incorrect relayer address"); + } + + innerTransactionsGasLimit += innerTx.gasLimit; + } + + const moveBalanceGas = this.config.minGasLimit * BigInt(options.innerTransactions.length); + const gasLimit = this.config.minGasLimit + moveBalanceGas + innerTransactionsGasLimit; + + return new Transaction({ + sender: options.relayerAddress.bech32(), + receiver: options.relayerAddress.bech32(), + chainID: this.config.chainID, + gasLimit: gasLimit, + innerTransactions: options.innerTransactions, + }); + } + private prepareInnerTransactionForRelayedV1(innerTransaction: ITransaction): string { const txObject = { nonce: innerTransaction.nonce, - sender: Address.fromBech32(innerTransaction.sender).pubkey().toString("base64"), - receiver: Address.fromBech32(innerTransaction.receiver).pubkey().toString("base64"), + sender: Address.newFromBech32(innerTransaction.sender).getPublicKey().toString("base64"), + receiver: Address.newFromBech32(innerTransaction.receiver).getPublicKey().toString("base64"), value: innerTransaction.value, gasPrice: innerTransaction.gasPrice, gasLimit: innerTransaction.gasLimit, @@ -98,7 +128,7 @@ export class RelayedTransactionsFactory { version: innerTransaction.version, options: innerTransaction.options.valueOf() == 0 ? undefined : innerTransaction.options, guardian: innerTransaction.guardian - ? Address.fromBech32(innerTransaction.guardian).pubkey().toString("base64") + ? Address.newFromBech32(innerTransaction.guardian).getPublicKey().toString("base64") : undefined, guardianSignature: innerTransaction.guardianSignature.length ? Buffer.from(innerTransaction.guardianSignature).toString("base64") From b45f3a9851351a7d5dd6d7a2f39bfcf02b9b199c Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jun 2024 11:29:52 +0300 Subject: [PATCH 02/16] align proto schema --- src/proto/transaction.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index 9dd372768..1593b935f 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -26,5 +26,5 @@ message Transaction { bytes GuardianAddr = 14; bytes GuardianSignature = 15; bytes Relayer = 16; - repeated Transaction InnerTransactions = 17; + repeated Transaction InnerTransactions = 17; } From 4a013c2c63a07d04ae3d5d231cddbfd36b0c8f96 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jun 2024 11:31:21 +0300 Subject: [PATCH 03/16] align relayer field in proto schema --- src/proto/transaction.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index 1593b935f..90f4feecd 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -25,6 +25,6 @@ message Transaction { uint32 Options = 13; bytes GuardianAddr = 14; bytes GuardianSignature = 15; - bytes Relayer = 16; + bytes Relayer = 16; repeated Transaction InnerTransactions = 17; } From f4621b3787d5b51248f44e9033139b87fc2be3d2 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jun 2024 16:38:46 +0300 Subject: [PATCH 04/16] add amount parameter for unjailing --- .../delegationTransactionsFactory.spec.ts | 3 ++- src/transactionsFactories/delegationTransactionsFactory.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transactionsFactories/delegationTransactionsFactory.spec.ts b/src/transactionsFactories/delegationTransactionsFactory.spec.ts index c76d1917c..7d5591cc6 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.spec.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.spec.ts @@ -176,13 +176,14 @@ describe("test delegation transactions factory", function () { sender: sender, delegationContract: delegationContract, publicKeys: [publicKey], + amount: 25000000000000000000n, }); assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); assert.isDefined(transaction.data); assert.deepEqual(transaction.data, Buffer.from("unJailNodes@61626261")); - assert.equal(transaction.value, 0n); + assert.equal(transaction.value, 25000000000000000000n); }); it("should create 'Transaction' for changing service fee", async function () { diff --git a/src/transactionsFactories/delegationTransactionsFactory.ts b/src/transactionsFactories/delegationTransactionsFactory.ts index 040d9ac4f..0b8062ec2 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.ts @@ -205,6 +205,7 @@ export class DelegationTransactionsFactory { sender: IAddress; delegationContract: IAddress; publicKeys: IValidatorPublicKey[]; + amount: bigint; }): Transaction { const dataParts = ["unJailNodes"]; @@ -220,6 +221,7 @@ export class DelegationTransactionsFactory { dataParts: dataParts, gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), addDataMovementGas: true, + amount: options.amount, }).build(); } From 9b31e539b439039efa1be316f954455b32db2288 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 11 Jun 2024 14:24:57 +0300 Subject: [PATCH 05/16] fixes after review --- src/proto/serializer.ts | 6 ++- src/transactionComputer.ts | 52 +++++++------------ .../relayedTransactionsFactory.spec.ts | 6 +-- .../relayedTransactionsFactory.ts | 2 +- 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index abe46ebf1..2fff28ff1 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -6,8 +6,6 @@ import { ITransaction, ITransactionValue } from "../interface"; import { bigIntToBuffer } from "../smartcontracts/codec/utils"; import { Transaction } from "../transaction"; -const proto = require("./compiled").proto; - /** * Hides away the serialization complexity, for each type of object (e.g. transactions). @@ -18,6 +16,8 @@ export class ProtoSerializer { * Serializes a Transaction object to a Buffer. Handles low-level conversion logic and field-mappings as well. */ serializeTransaction(transaction: Transaction): Buffer { + const proto = require("./compiled").proto; + const protoTransaction = this.convertToProtoMessage(transaction); const encoded = proto.Transaction.encode(protoTransaction).finish(); const buffer = Buffer.from(encoded); @@ -26,6 +26,8 @@ export class ProtoSerializer { } private convertToProtoMessage(transaction: ITransaction) { + const proto = require("./compiled").proto; + const receiverPubkey = new Address(transaction.receiver).getPublicKey(); const senderPubkey = new Address(transaction.sender).getPublicKey(); diff --git a/src/transactionComputer.ts b/src/transactionComputer.ts index f3cc21013..1a835b04a 100644 --- a/src/transactionComputer.ts +++ b/src/transactionComputer.ts @@ -49,7 +49,7 @@ export class TransactionComputer { computeBytesForSigning(transaction: ITransaction): Uint8Array { this.ensureValidTransactionFields(transaction); - const plainTransaction = this.toPlainObjectForSigning(transaction); + const plainTransaction = this.toPlainObject(transaction); const serialized = JSON.stringify(plainTransaction); return new Uint8Array(Buffer.from(serialized)); } @@ -64,7 +64,7 @@ export class TransactionComputer { } computeHashForSigning(transaction: ITransaction): Uint8Array { - const plainTransaction = this.toPlainObjectForSigning(transaction); + const plainTransaction = this.toPlainObject(transaction); const signable = Buffer.from(JSON.stringify(plainTransaction)); return createKeccakHash("keccak256").update(signable).digest(); } @@ -101,8 +101,8 @@ export class TransactionComputer { transaction.options = transaction.options | TRANSACTION_OPTIONS_TX_HASH_SIGN; } - private toPlainObjectForSigning(transaction: ITransaction) { - return { + private toPlainObject(transaction: ITransaction, withSignature?: boolean) { + let obj: any = { nonce: Number(transaction.nonce), value: transaction.value.toString(), receiver: transaction.receiver, @@ -112,38 +112,22 @@ export class TransactionComputer { gasPrice: Number(transaction.gasPrice), gasLimit: Number(transaction.gasLimit), data: this.toBase64OrUndefined(transaction.data), - chainID: transaction.chainID, - version: transaction.version, - options: transaction.options ? transaction.options : undefined, - guardian: transaction.guardian ? transaction.guardian : undefined, - relayer: transaction.relayer ? transaction.relayer : undefined, - innerTransactions: transaction.innerTransactions.length - ? transaction.innerTransactions.map((tx) => this.toPlainObject(tx)) - : undefined, }; - } - private toPlainObject(transaction: ITransaction): any { - return { - nonce: Number(transaction.nonce), - value: transaction.value.toString(), - receiver: transaction.receiver, - sender: transaction.sender, - senderUsername: this.toBase64OrUndefined(transaction.senderUsername), - receiverUsername: this.toBase64OrUndefined(transaction.receiverUsername), - gasPrice: Number(transaction.gasPrice), - gasLimit: Number(transaction.gasLimit), - data: this.toBase64OrUndefined(transaction.data), - signature: this.toHexOrUndefined(transaction.signature), - chainID: transaction.chainID.valueOf(), - version: transaction.version, - options: transaction.options == 0 ? undefined : transaction.options, - guardian: transaction.guardian ? transaction.guardian : undefined, - relayer: transaction.relayer ? transaction.relayer : undefined, - innerTransactions: transaction.innerTransactions.length - ? transaction.innerTransactions.map((tx) => this.toPlainObject(tx)) - : undefined, - }; + if (withSignature) { + obj.signature = this.toHexOrUndefined(transaction.signature); + } + + obj.chainID = transaction.chainID; + obj.version = transaction.version; + obj.options = transaction.options ? transaction.options : undefined; + obj.guardian = transaction.guardian ? transaction.guardian : undefined; + obj.relayer = transaction.relayer ? transaction.relayer : undefined; + obj.innerTransactions = transaction.innerTransactions.length + ? transaction.innerTransactions.map((tx) => this.toPlainObject(tx, true)) + : undefined; + + return obj; } private toHexOrUndefined(value?: Uint8Array) { diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts index d8df46ad7..f45fac443 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.spec.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -5,7 +5,7 @@ import { TransactionComputer } from "../transactionComputer"; import { RelayedTransactionsFactory } from "./relayedTransactionsFactory"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; -describe("test relayed v1 transaction builder", function () { +describe("test relayed transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "T" }); const factory = new RelayedTransactionsFactory({ config: config }); const transactionComputer = new TransactionComputer(); @@ -300,9 +300,9 @@ describe("test relayed v1 transaction builder", function () { assert.equal( Buffer.from(relayedTransaction.signature).toString("hex"), - "6bd446e1f531db190de97adeab7bae3ed332a83d93e47dc29299a0a6868b966b002d0f4395eee450fc89c7677516d7448c6d01245a3fc5c6c65e0bf8dca9540e", + "88b9bce6fe62a641fca593f95c12ad09032a44b34c9e5cf16d070f0563b1695bf9d452a9df52bce3373fd5e10ed96c3d65cd189f5873e3a3184a89f4980c9e0c", ); - assert.equal(relayedTransaction.gasLimit, 150000n); + assert.equal(relayedTransaction.gasLimit, 100000n); }); it("should fail to create relayed v3 transaction", async function () { diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts index 1a8057851..7464fc259 100644 --- a/src/transactionsFactories/relayedTransactionsFactory.ts +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -103,7 +103,7 @@ export class RelayedTransactionsFactory { } const moveBalanceGas = this.config.minGasLimit * BigInt(options.innerTransactions.length); - const gasLimit = this.config.minGasLimit + moveBalanceGas + innerTransactionsGasLimit; + const gasLimit = moveBalanceGas + innerTransactionsGasLimit; return new Transaction({ sender: options.relayerAddress.bech32(), From e979e030e5f19e18d5761000980e49f30041c09b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 28 Jun 2024 13:18:31 +0300 Subject: [PATCH 06/16] add support for sending both native and esdts using MutiESDTNFTTransfer --- src/constants.ts | 2 + .../smartContractTransactionsFactory.spec.ts | 49 +++++++++++++ .../smartContractTransactionsFactory.ts | 23 ++++--- .../transferTransactionsFactory.spec.ts | 67 +++++++++++++++--- .../transferTransactionsFactory.ts | 68 +++++++++++++++---- 5 files changed, 175 insertions(+), 34 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index e19c22ef1..9300d9780 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,6 +25,8 @@ export const METACHAIN_ID = 4294967295; export const SDK_JS_SIGNER = "sdk-js"; export const UNKNOWN_SIGNER = "unknown"; +export const EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER = "EGLD-000000"; + /** * @deprecated */ diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts index d108c103d..096c37ea7 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -316,6 +316,55 @@ describe("test smart contract transactions factory", function () { assert.deepEqual(transaction, transactionAbiAware); }); + it("should create 'Transaction' for execute and transfer native and nfts", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + + const firstToken = new Token({ identifier: "NFT-123456", nonce: 1n }); + const firstTransfer = new TokenTransfer({ token: firstToken, amount: 1n }); + const secondToken = new Token({ identifier: "NFT-123456", nonce: 42n }); + const secondTransfer = new TokenTransfer({ token: secondToken, amount: 1n }); + + const transaction = factory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + nativeTransferAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + nativeTransferAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + assert.isDefined(transaction.data); + assert.deepEqual( + transaction.data, + Buffer.from( + "MultiESDTNFTTransfer@00000000000000000500b9353fe8407f87310c87e12fa1ac807f0485da39d152@03@4e46542d313233343536@01@01@4e46542d313233343536@2a@01@45474c442d303030303030@@0de0b6b3a7640000@616464@07", + ), + ); + + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + it("should create 'Transaction' for upgrade", async function () { const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index c439139f5..25a1d6ab2 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -1,12 +1,12 @@ import { Address } from "../address"; -import { CONTRACT_DEPLOY_ADDRESS_HEX, VM_TYPE_WASM_VM } from "../constants"; -import { Err, ErrBadUsage } from "../errors"; +import { CONTRACT_DEPLOY_ADDRESS_HEX, EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER, VM_TYPE_WASM_VM } from "../constants"; +import { Err } from "../errors"; import { IAddress } from "../interface"; import { Logger } from "../logger"; import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts"; import { NativeSerializer } from "../smartcontracts/nativeSerializer"; import { isTyped } from "../smartcontracts/typesystem"; -import { TokenComputer, TokenTransfer } from "../tokens"; +import { Token, TokenComputer, TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { byteArrayToHex, utf8ToHex } from "../utils.codec"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; @@ -91,19 +91,24 @@ export class SmartContractTransactionsFactory { tokenTransfers?: TokenTransfer[]; }): Transaction { const args = options.arguments || []; - const tokenTransfer = options.tokenTransfers || []; - const nativeTransferAmount = options.nativeTransferAmount ?? 0n; - const numberOfTokens = tokenTransfer.length; + let tokenTransfers = options.tokenTransfers || []; + let nativeTransferAmount = options.nativeTransferAmount ?? 0n; + let numberOfTokens = tokenTransfers.length; if (nativeTransferAmount && numberOfTokens) { - throw new ErrBadUsage("Can't send both native tokens and custom tokens(ESDT/NFT)"); + const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); + const nativeTransfer = new TokenTransfer({ token: nativeToken, amount: nativeTransferAmount }); + tokenTransfers.push(nativeTransfer); + + nativeTransferAmount = 0n; + numberOfTokens++; } let receiver = options.contract; let dataParts: string[] = []; if (numberOfTokens === 1) { - const transfer = tokenTransfer[0]; + const transfer = tokenTransfers[0]; if (this.tokenComputer.isFungible(transfer.token)) { dataParts = this.dataArgsBuilder.buildDataPartsForESDTTransfer(transfer); @@ -112,7 +117,7 @@ export class SmartContractTransactionsFactory { receiver = options.sender; } } else if (numberOfTokens > 1) { - dataParts = this.dataArgsBuilder.buildDataPartsForMultiESDTNFTTransfer(receiver, tokenTransfer); + dataParts = this.dataArgsBuilder.buildDataPartsForMultiESDTNFTTransfer(receiver, tokenTransfers); receiver = options.sender; } diff --git a/src/transactionsFactories/transferTransactionsFactory.spec.ts b/src/transactionsFactories/transferTransactionsFactory.spec.ts index 7251b9c42..d949f38f3 100644 --- a/src/transactionsFactories/transferTransactionsFactory.spec.ts +++ b/src/transactionsFactories/transferTransactionsFactory.spec.ts @@ -5,7 +5,7 @@ import { Token, TokenTransfer } from "../tokens"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; import { TransferTransactionsFactory } from "./transferTransactionsFactory"; -describe("test transfer transcations factory", function () { +describe("test transfer transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "D" }); const transferFactory = new TransferTransactionsFactory({ config: config, @@ -37,8 +37,8 @@ describe("test transfer transcations factory", function () { nativeAmount: 1000000000000000000n, }); - assert.equal(transaction.sender, alice.bech32()); - assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); assert.equal(transaction.value.valueOf(), 1000000000000000000n); assert.equal(transaction.gasLimit.valueOf(), 50000n); assert.deepEqual(transaction.data, new Uint8Array()); @@ -52,8 +52,8 @@ describe("test transfer transcations factory", function () { data: Buffer.from("test data"), }); - assert.equal(transaction.sender, alice.bech32()); - assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); assert.equal(transaction.value.valueOf(), 1000000000000000000n); assert.equal(transaction.gasLimit.valueOf(), 63500n); assert.deepEqual(transaction.data, Buffer.from("test data")); @@ -69,8 +69,8 @@ describe("test transfer transcations factory", function () { tokenTransfers: [transfer], }); - assert.equal(transaction.sender, alice.bech32()); - assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); assert.equal(transaction.value.valueOf(), 0n); assert.equal(transaction.gasLimit.valueOf(), 410000n); assert.deepEqual(transaction.data.toString(), "ESDTTransfer@464f4f2d313233343536@0f4240"); @@ -86,8 +86,8 @@ describe("test transfer transcations factory", function () { tokenTransfers: [transfer], }); - assert.equal(transaction.sender, alice.bech32()); - assert.equal(transaction.receiver, alice.bech32()); + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, alice.toBech32()); assert.equal(transaction.value.valueOf(), 0n); assert.equal(transaction.gasLimit.valueOf(), 1210500n); assert.deepEqual( @@ -109,8 +109,8 @@ describe("test transfer transcations factory", function () { tokenTransfers: [firstTransfer, secondTransfer], }); - assert.equal(transaction.sender, alice.bech32()); - assert.equal(transaction.receiver, alice.bech32()); + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, alice.toBech32()); assert.equal(transaction.value.valueOf(), 0n); assert.equal(transaction.gasLimit.valueOf(), 1466000n); assert.deepEqual( @@ -118,4 +118,49 @@ describe("test transfer transcations factory", function () { "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@4e46542d313233343536@0a@01@544553542d393837363534@01@01", ); }); + + it("should fail to create transaction for token transfers", async () => { + assert.throws(() => { + transferFactory.createTransactionForTokenTransfer({ + sender: alice, + receiver: bob, + }); + }, "No native token amount or token transfers provided"); + + assert.throws(() => { + const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); + const transfer = new TokenTransfer({ token: nft, amount: 1n }); + + transferFactory.createTransactionForTokenTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: [transfer], + data: Buffer.from("test"), + }); + }, "Can't set data field when sending esdt tokens"); + }); + + it("should create 'Transaction' for token transfers", async () => { + const firstNft = new Token({ identifier: "NFT-123456", nonce: 10n }); + const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n }); + + const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n }); + const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); + + const transaction = transferFactory.createTransactionForTokenTransfer({ + sender: alice, + receiver: bob, + nativeAmount: 1000000000000000000n, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, alice.toBech32()); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 1727500n); + assert.deepEqual( + transaction.data.toString(), + "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@03@4e46542d313233343536@0a@01@544553542d393837363534@01@01@45474c442d303030303030@@0de0b6b3a7640000", + ); + }); }); diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 4a431bea7..dcccdf2e2 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -1,3 +1,4 @@ +import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "../constants"; import { Err, ErrBadUsage } from "../errors"; import { IAddress, @@ -18,7 +19,7 @@ import { U16Value, U64Value, } from "../smartcontracts"; -import { TokenComputer, TokenTransfer } from "../tokens"; +import { Token, TokenComputer, TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { TransactionPayload } from "../transactionPayload"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; @@ -53,7 +54,7 @@ export class TransferTransactionsFactory { private readonly gasEstimator?: IGasEstimator; /** - * Should be instantiated using `Config` and `TokenComputer`. + * Should be instantiated using `Config`. * Instantiating this class using GasEstimator represents the legacy version of this class. * The legacy version contains methods like `createEGLDTransfer`, `createESDTTransfer`, `createESDTNFTTransfer` and `createMultiESDTNFTTransfer`. * This was done in order to minimize breaking changes in client code. @@ -82,18 +83,10 @@ export class TransferTransactionsFactory { return this.gasEstimator !== undefined; } - private ensureMembersAreDefined() { + private ensureConfigIsDefined() { if (this.config === undefined) { throw new Err("'config' is not defined"); } - - if (this.tokenTransfersDataBuilder === undefined) { - throw new Err("`dataArgsBuilder is not defined`"); - } - - if (this.tokenComputer === undefined) { - throw new Err("`tokenComputer is not defined`"); - } } createTransactionForNativeTokenTransfer(options: { @@ -102,7 +95,7 @@ export class TransferTransactionsFactory { nativeAmount: bigint; data?: Uint8Array; }): Transaction { - this.ensureMembersAreDefined(); + this.ensureConfigIsDefined(); const data = options.data || new Uint8Array(); @@ -121,7 +114,7 @@ export class TransferTransactionsFactory { receiver: IAddress; tokenTransfers: TokenTransfer[]; }): Transaction { - this.ensureMembersAreDefined(); + this.ensureConfigIsDefined(); const numberOfTransfers = options.tokenTransfers.length; @@ -152,6 +145,53 @@ export class TransferTransactionsFactory { }).build(); } + createTransactionForTokenTransfer(options: { + sender: IAddress; + receiver: IAddress; + nativeAmount?: bigint; + tokenTransfers?: TokenTransfer[]; + data?: Uint8Array; + }): Transaction { + if (!options.nativeAmount && !options.tokenTransfers?.length) { + throw new ErrBadUsage("No native token amount or token transfers provided"); + } + + if (options.tokenTransfers?.length && options.data?.length) { + throw new ErrBadUsage("Can't set data field when sending esdt tokens"); + } + + if (options.nativeAmount && !options.tokenTransfers) { + return this.createTransactionForNativeTokenTransfer({ + sender: options.sender, + receiver: options.receiver, + nativeAmount: options.nativeAmount, + data: options.data, + }); + } + + if (options.tokenTransfers?.length && !options.nativeAmount) { + return this.createTransactionForESDTTokenTransfer({ + sender: options.sender, + receiver: options.receiver, + tokenTransfers: options.tokenTransfers, + }); + } + + // if the method does not return until here it means both nativeAmount and tokenTransfers have been provided + const nativeAmount = options.nativeAmount || 0n; + let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; + + const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); + const nativeTransfer = new TokenTransfer({ token: nativeToken, amount: nativeAmount }); + + tokenTransfers.push(nativeTransfer); + return this.createTransactionForESDTTokenTransfer({ + sender: options.sender, + receiver: options.receiver, + tokenTransfers: tokenTransfers, + }); + } + /** * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. * Use {@link createTransactionForNativeTokenTransfer} instead. @@ -336,7 +376,7 @@ export class TransferTransactionsFactory { receiver: IAddress; tokenTransfers: TokenTransfer[]; }): Transaction { - this.ensureMembersAreDefined(); + this.ensureConfigIsDefined(); let dataParts: string[] = []; const transfer = options.tokenTransfers[0]; From f08ed7ae692731bc4efb53103834353ae8bcde4f Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 1 Jul 2024 10:48:38 +0300 Subject: [PATCH 07/16] small refactoring --- .../transferTransactionsFactory.spec.ts | 6 ++--- .../transferTransactionsFactory.ts | 26 +++++++++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/transactionsFactories/transferTransactionsFactory.spec.ts b/src/transactionsFactories/transferTransactionsFactory.spec.ts index d949f38f3..3e354b136 100644 --- a/src/transactionsFactories/transferTransactionsFactory.spec.ts +++ b/src/transactionsFactories/transferTransactionsFactory.spec.ts @@ -121,7 +121,7 @@ describe("test transfer transactions factory", function () { it("should fail to create transaction for token transfers", async () => { assert.throws(() => { - transferFactory.createTransactionForTokenTransfer({ + transferFactory.createTransactionForTransfer({ sender: alice, receiver: bob, }); @@ -131,7 +131,7 @@ describe("test transfer transactions factory", function () { const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); const transfer = new TokenTransfer({ token: nft, amount: 1n }); - transferFactory.createTransactionForTokenTransfer({ + transferFactory.createTransactionForTransfer({ sender: alice, receiver: bob, tokenTransfers: [transfer], @@ -147,7 +147,7 @@ describe("test transfer transactions factory", function () { const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n }); const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); - const transaction = transferFactory.createTransactionForTokenTransfer({ + const transaction = transferFactory.createTransactionForTransfer({ sender: alice, receiver: bob, nativeAmount: 1000000000000000000n, diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index dcccdf2e2..3e1d42ba7 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -145,7 +145,7 @@ export class TransferTransactionsFactory { }).build(); } - createTransactionForTokenTransfer(options: { + createTransactionForTransfer(options: { sender: IAddress; receiver: IAddress; nativeAmount?: bigint; @@ -169,22 +169,11 @@ export class TransferTransactionsFactory { }); } - if (options.tokenTransfers?.length && !options.nativeAmount) { - return this.createTransactionForESDTTokenTransfer({ - sender: options.sender, - receiver: options.receiver, - tokenTransfers: options.tokenTransfers, - }); - } - - // if the method does not return until here it means both nativeAmount and tokenTransfers have been provided - const nativeAmount = options.nativeAmount || 0n; let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; - const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); - const nativeTransfer = new TokenTransfer({ token: nativeToken, amount: nativeAmount }); + const nativeTransfer = this.createNativeTransfer(options.nativeAmount); + nativeTransfer ? tokenTransfers.push(nativeTransfer) : null; - tokenTransfers.push(nativeTransfer); return this.createTransactionForESDTTokenTransfer({ sender: options.sender, receiver: options.receiver, @@ -192,6 +181,15 @@ export class TransferTransactionsFactory { }); } + private createNativeTransfer(nativeAmount?: bigint): TokenTransfer | undefined { + if (!nativeAmount) { + return undefined; + } + + const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); + return new TokenTransfer({ token: nativeToken, amount: nativeAmount }); + } + /** * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. * Use {@link createTransactionForNativeTokenTransfer} instead. From 2f5530c09b852ade7c25b4c96522bccd628a5062 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 1 Jul 2024 12:10:30 +0300 Subject: [PATCH 08/16] more refactoring --- src/tokens.spec.ts | 8 ++++++++ src/tokens.ts | 6 ++++++ .../smartContractTransactionsFactory.ts | 3 +-- .../transferTransactionsFactory.ts | 11 +---------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/tokens.spec.ts b/src/tokens.spec.ts index 0228a2f7c..9a415815b 100644 --- a/src/tokens.spec.ts +++ b/src/tokens.spec.ts @@ -95,4 +95,12 @@ describe("test token transfer (legacy)", () => { assert.equal(transfer.nonce, nonce); assert.equal(transfer.toPrettyString(), "1 TEST-38f249"); }); + + it("should create TokenTransfer from native token amount", () => { + const transfer = TokenTransfer.newFromEgldAmount(1000000000000000000n); + + assert.equal(transfer.token.identifier, "EGLD-000000"); + assert.equal(transfer.token.nonce, 0n); + assert.equal(transfer.amount, 1000000000000000000n); + }); }); diff --git a/src/tokens.ts b/src/tokens.ts index f6b29bc91..62b22a04e 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -1,5 +1,6 @@ import BigNumber from "bignumber.js"; import { ErrInvalidArgument, ErrInvalidTokenIdentifier } from "./errors"; +import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "./constants"; // Legacy constants: const EGLDTokenIdentifier = "EGLD"; @@ -84,6 +85,11 @@ export class TokenTransfer { } } + static newFromEgldAmount(amount: bigint): TokenTransfer { + const token = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); + return new TokenTransfer({ token, amount }); + } + private isLegacyTokenTransferOptions(options: any): options is ILegacyTokenTransferOptions { return options.tokenIdentifier !== undefined; } diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index 25a1d6ab2..385f1b924 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -96,8 +96,7 @@ export class SmartContractTransactionsFactory { let numberOfTokens = tokenTransfers.length; if (nativeTransferAmount && numberOfTokens) { - const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); - const nativeTransfer = new TokenTransfer({ token: nativeToken, amount: nativeTransferAmount }); + const nativeTransfer = TokenTransfer.newFromEgldAmount(nativeTransferAmount); tokenTransfers.push(nativeTransfer); nativeTransferAmount = 0n; diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 3e1d42ba7..112707174 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -171,7 +171,7 @@ export class TransferTransactionsFactory { let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; - const nativeTransfer = this.createNativeTransfer(options.nativeAmount); + const nativeTransfer = options.nativeAmount ? TokenTransfer.newFromEgldAmount(options.nativeAmount) : undefined; nativeTransfer ? tokenTransfers.push(nativeTransfer) : null; return this.createTransactionForESDTTokenTransfer({ @@ -181,15 +181,6 @@ export class TransferTransactionsFactory { }); } - private createNativeTransfer(nativeAmount?: bigint): TokenTransfer | undefined { - if (!nativeAmount) { - return undefined; - } - - const nativeToken = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER }); - return new TokenTransfer({ token: nativeToken, amount: nativeAmount }); - } - /** * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. * Use {@link createTransactionForNativeTokenTransfer} instead. From 3c321597330e8f291283ee1905e80628b78dffab Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 2 Jul 2024 10:20:03 +0300 Subject: [PATCH 09/16] remove unused imports --- src/transactionsFactories/smartContractTransactionsFactory.ts | 4 ++-- src/transactionsFactories/transferTransactionsFactory.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index 385f1b924..a9cc73f70 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -1,12 +1,12 @@ import { Address } from "../address"; -import { CONTRACT_DEPLOY_ADDRESS_HEX, EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER, VM_TYPE_WASM_VM } from "../constants"; +import { CONTRACT_DEPLOY_ADDRESS_HEX, VM_TYPE_WASM_VM } from "../constants"; import { Err } from "../errors"; import { IAddress } from "../interface"; import { Logger } from "../logger"; import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts"; import { NativeSerializer } from "../smartcontracts/nativeSerializer"; import { isTyped } from "../smartcontracts/typesystem"; -import { Token, TokenComputer, TokenTransfer } from "../tokens"; +import { TokenComputer, TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { byteArrayToHex, utf8ToHex } from "../utils.codec"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 112707174..73bd8b42b 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -1,4 +1,3 @@ -import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "../constants"; import { Err, ErrBadUsage } from "../errors"; import { IAddress, @@ -19,7 +18,7 @@ import { U16Value, U64Value, } from "../smartcontracts"; -import { Token, TokenComputer, TokenTransfer } from "../tokens"; +import { TokenComputer, TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { TransactionPayload } from "../transactionPayload"; import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; From 969d0df76a1c74ee617904b434ec7dd945b7727a Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 2 Jul 2024 11:18:45 +0300 Subject: [PATCH 10/16] fix modify token transfers list inside method --- .../smartContractTransactionsFactory.ts | 2 +- .../transferTransactionsFactory.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index a9cc73f70..fad68afdb 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -91,7 +91,7 @@ export class SmartContractTransactionsFactory { tokenTransfers?: TokenTransfer[]; }): Transaction { const args = options.arguments || []; - let tokenTransfers = options.tokenTransfers || []; + let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; let nativeTransferAmount = options.nativeTransferAmount ?? 0n; let numberOfTokens = tokenTransfers.length; diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 73bd8b42b..74249aa34 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -151,26 +151,28 @@ export class TransferTransactionsFactory { tokenTransfers?: TokenTransfer[]; data?: Uint8Array; }): Transaction { - if (!options.nativeAmount && !options.tokenTransfers?.length) { + const nativeAmount = options.nativeAmount ?? 0n; + let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; + const numberOfTokens = tokenTransfers.length; + + if (!nativeAmount && !numberOfTokens) { throw new ErrBadUsage("No native token amount or token transfers provided"); } - if (options.tokenTransfers?.length && options.data?.length) { + if (numberOfTokens && options.data?.length) { throw new ErrBadUsage("Can't set data field when sending esdt tokens"); } - if (options.nativeAmount && !options.tokenTransfers) { + if (nativeAmount && !tokenTransfers) { return this.createTransactionForNativeTokenTransfer({ sender: options.sender, receiver: options.receiver, - nativeAmount: options.nativeAmount, + nativeAmount: nativeAmount, data: options.data, }); } - let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; - - const nativeTransfer = options.nativeAmount ? TokenTransfer.newFromEgldAmount(options.nativeAmount) : undefined; + const nativeTransfer = nativeAmount ? TokenTransfer.newFromEgldAmount(nativeAmount) : undefined; nativeTransfer ? tokenTransfers.push(nativeTransfer) : null; return this.createTransactionForESDTTokenTransfer({ From 497af62e27f537a4c0d43b9019eacb9dbd9d2f42 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 3 Jul 2024 12:11:22 +0300 Subject: [PATCH 11/16] bump version for beta release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6302b49f..26a47d361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.2.1", + "version": "13.3.0-beta.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.2.1", + "version": "13.3.0-beta.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 9a8f16d74..6240f8e56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.2.1", + "version": "13.3.0-beta.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", From 2a46dafb5875df942232feb78e5a38316cf6c9ec Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 4 Jul 2024 10:35:27 +0300 Subject: [PATCH 12/16] very small refactoring --- .../smartContractTransactionsFactory.ts | 4 +--- src/transactionsFactories/transferTransactionsFactory.ts | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index fad68afdb..6102ebac5 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -96,9 +96,7 @@ export class SmartContractTransactionsFactory { let numberOfTokens = tokenTransfers.length; if (nativeTransferAmount && numberOfTokens) { - const nativeTransfer = TokenTransfer.newFromEgldAmount(nativeTransferAmount); - tokenTransfers.push(nativeTransfer); - + tokenTransfers.push(TokenTransfer.newFromEgldAmount(nativeTransferAmount)); nativeTransferAmount = 0n; numberOfTokens++; } diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 74249aa34..68eacac9f 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -155,7 +155,7 @@ export class TransferTransactionsFactory { let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; const numberOfTokens = tokenTransfers.length; - if (!nativeAmount && !numberOfTokens) { + if (!nativeAmount && numberOfTokens === 0) { throw new ErrBadUsage("No native token amount or token transfers provided"); } @@ -163,7 +163,7 @@ export class TransferTransactionsFactory { throw new ErrBadUsage("Can't set data field when sending esdt tokens"); } - if (nativeAmount && !tokenTransfers) { + if (nativeAmount && numberOfTokens === 0) { return this.createTransactionForNativeTokenTransfer({ sender: options.sender, receiver: options.receiver, @@ -173,7 +173,9 @@ export class TransferTransactionsFactory { } const nativeTransfer = nativeAmount ? TokenTransfer.newFromEgldAmount(nativeAmount) : undefined; - nativeTransfer ? tokenTransfers.push(nativeTransfer) : null; + if (nativeTransfer) { + tokenTransfers.push(nativeTransfer); + } return this.createTransactionForESDTTokenTransfer({ sender: options.sender, From 96e79d566dbf1bb976e6f8e5b1235c2d64da996b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 9 Jul 2024 10:55:46 +0300 Subject: [PATCH 13/16] support notarizing transactions for token transfers factory --- .../transferTransactionsFactory.spec.ts | 58 ++++++++++++++++--- .../transferTransactionsFactory.ts | 6 +- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/transactionsFactories/transferTransactionsFactory.spec.ts b/src/transactionsFactories/transferTransactionsFactory.spec.ts index 3e354b136..1296be14f 100644 --- a/src/transactionsFactories/transferTransactionsFactory.spec.ts +++ b/src/transactionsFactories/transferTransactionsFactory.spec.ts @@ -117,16 +117,17 @@ describe("test transfer transactions factory", function () { transaction.data.toString(), "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@4e46542d313233343536@0a@01@544553542d393837363534@01@01", ); + + const secondTransaction = transferFactory.createTransactionForTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + assert.deepEqual(transaction, secondTransaction); }); it("should fail to create transaction for token transfers", async () => { - assert.throws(() => { - transferFactory.createTransactionForTransfer({ - sender: alice, - receiver: bob, - }); - }, "No native token amount or token transfers provided"); - assert.throws(() => { const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); const transfer = new TokenTransfer({ token: nft, amount: 1n }); @@ -140,7 +141,48 @@ describe("test transfer transactions factory", function () { }, "Can't set data field when sending esdt tokens"); }); - it("should create 'Transaction' for token transfers", async () => { + it("should create transaction for native transfers", async () => { + const transaction = transferFactory.createTransactionForTransfer({ + sender: alice, + receiver: bob, + nativeAmount: 1000000000000000000n, + }); + + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 50000n); + }); + + it("should create transaction for native transfers and set data field", async () => { + const transaction = transferFactory.createTransactionForTransfer({ + sender: alice, + receiver: bob, + nativeAmount: 1000000000000000000n, + data: Buffer.from("hello"), + }); + + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 57500n); + assert.deepEqual(transaction.data, Buffer.from("hello")); + }); + + it("should create transaction for notarizing", async () => { + const transaction = transferFactory.createTransactionForTransfer({ + sender: alice, + receiver: bob, + data: Buffer.from("hello"), + }); + + assert.equal(transaction.sender, alice.toBech32()); + assert.equal(transaction.receiver, bob.toBech32()); + assert.equal(transaction.gasLimit.valueOf(), 57500n); + assert.deepEqual(transaction.data, Buffer.from("hello")); + }); + + it("should create transaction for token transfers", async () => { const firstNft = new Token({ identifier: "NFT-123456", nonce: 10n }); const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n }); diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts index 68eacac9f..834fa6dd8 100644 --- a/src/transactionsFactories/transferTransactionsFactory.ts +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -155,15 +155,11 @@ export class TransferTransactionsFactory { let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : []; const numberOfTokens = tokenTransfers.length; - if (!nativeAmount && numberOfTokens === 0) { - throw new ErrBadUsage("No native token amount or token transfers provided"); - } - if (numberOfTokens && options.data?.length) { throw new ErrBadUsage("Can't set data field when sending esdt tokens"); } - if (nativeAmount && numberOfTokens === 0) { + if ((nativeAmount && numberOfTokens === 0) || options.data) { return this.createTransactionForNativeTokenTransfer({ sender: options.sender, receiver: options.receiver, From 1fc466160de027332ede7efc43255912929aab97 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 18 Jul 2024 16:52:28 +0300 Subject: [PATCH 14/16] add methods for dynmic nfts --- ...anagementTransactionIntentsFactory.spec.ts | 171 +++++++++++- .../tokenManagementTransactionsFactory.ts | 245 ++++++++++++++++++ .../transactionsFactoryConfig.ts | 16 ++ 3 files changed, 424 insertions(+), 8 deletions(-) diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index 6dee64169..a9fa53911 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -1,8 +1,9 @@ import { assert } from "chai"; -import { ESDT_CONTRACT_ADDRESS } from "../constants"; +import { ESDT_CONTRACT_ADDRESS_HEX } from "../constants"; import { loadTestWallets, TestWallet } from "../testutils"; import { TokenManagementTransactionsFactory } from "./tokenManagementTransactionsFactory"; import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; +import { Address } from "../address"; describe("test token management transactions factory", () => { let frank: TestWallet, grace: TestWallet; @@ -53,7 +54,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.deepEqual(transaction.value, config.issueCost); }); @@ -78,7 +79,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.deepEqual(transaction.value, config.issueCost); }); @@ -103,7 +104,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.deepEqual(transaction.value, config.issueCost); }); @@ -129,7 +130,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.deepEqual(transaction.value, config.issueCost); }); @@ -150,7 +151,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.equal(transaction.value, 0n); }); @@ -171,7 +172,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.equal(transaction.value, 0n); }); @@ -194,7 +195,7 @@ describe("test token management transactions factory", () => { ), ); assert.equal(transaction.sender, frank.address.toString()); - assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); assert.equal(transaction.value, 0n); }); @@ -218,4 +219,158 @@ describe("test token management transactions factory", () => { assert.equal(transaction.receiver, grace.address.toString()); assert.equal(transaction.value, 0n); }); + + it("should create 'Transaction' for modifying royalties", () => { + const transaction = tokenManagementFactory.createTransactionForModifyingRoyalties({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + tokenNonce: 1n, + royalties: 1234n, + }); + + assert.deepEqual(transaction.data, Buffer.from("ESDTModifyRoyalties@544553542d313233343536@01@04d2")); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60125000n); + }); + + it("should create 'Transaction' for setting new uris", () => { + const transaction = tokenManagementFactory.createTransactionForSettingNewUris({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + tokenNonce: 1n, + uris: ["firstURI", "secondURI"], + }); + + assert.deepEqual( + transaction.data, + Buffer.from("ESDTSetNewURIs@544553542d313233343536@01@6669727374555249@7365636f6e64555249"), + ); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60164000n); + }); + + it("should create 'Transaction' for modifying creator", () => { + const transaction = tokenManagementFactory.createTransactionForModifyingCreator({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + tokenNonce: 1n, + }); + + assert.deepEqual(transaction.data, Buffer.from("ESDTModifyCreator@544553542d313233343536@01")); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60114500n); + }); + + it("should create 'Transaction' for updating metadata", () => { + const transaction = tokenManagementFactory.createTransactionForUpdatingMetadata({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + tokenNonce: 1n, + tokenName: "Test", + royalties: 1234n, + hash: "abba", + attributes: Buffer.from("test"), + uris: ["firstURI", "secondURI"], + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "ESDTMetaDataUpdate@544553542d313233343536@01@54657374@04d2@61626261@74657374@6669727374555249@7365636f6e64555249", + ), + ); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60218000n); + }); + + it("should create 'Transaction' for recreating metadata", () => { + const transaction = tokenManagementFactory.createTransactionForNftMetadataRecreate({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + tokenNonce: 1n, + tokenName: "Test", + royalties: 1234n, + hash: "abba", + attributes: Buffer.from("test"), + uris: ["firstURI", "secondURI"], + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "ESDTMetaDataRecreate@544553542d313233343536@01@54657374@04d2@61626261@74657374@6669727374555249@7365636f6e64555249", + ), + ); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60221000n); + }); + + it("should create 'Transaction' for changing to dynamic", () => { + const transaction = tokenManagementFactory.createTransactionForMakingTokenDynamic({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + }); + + assert.deepEqual(transaction.data, Buffer.from("changeToDynamic@544553542d313233343536")); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60107000n); + }); + + it("should create 'Transaction' for updating token id", () => { + const transaction = tokenManagementFactory.createTransactionForUpdatingTokenId({ + sender: grace.address, + tokenIdentifier: "TEST-123456", + }); + + assert.deepEqual(transaction.data, Buffer.from("updateTokenID@544553542d313233343536")); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60104000n); + }); + + it("should create 'Transaction' for registering dynamic", () => { + const transaction = tokenManagementFactory.createTransactionForRegisteringDynamicToken({ + sender: grace.address, + tokenName: "Test", + tokenTicker: "TEST-123456", + tokenType: "FNG", + }); + + assert.deepEqual(transaction.data, Buffer.from("registerDynamic@54657374@544553542d313233343536@464e47")); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60131000n); + }); + + it("should create 'Transaction' for registering and setting all roles", () => { + const transaction = tokenManagementFactory.createTransactionForRegisteringDynamicAndSettingRoles({ + sender: grace.address, + tokenName: "Test", + tokenTicker: "TEST-123456", + tokenType: "FNG", + }); + + assert.deepEqual( + transaction.data, + Buffer.from("registerAndSetAllRolesDynamic@54657374@544553542d313233343536@464e47"), + ); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, Address.newFromHex(ESDT_CONTRACT_ADDRESS_HEX, config.addressHrp).toBech32()); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 60152000n); + }); }); diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index 18c61f1e2..2ff2638bf 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -1,5 +1,6 @@ import { Address } from "../address"; import { ESDT_CONTRACT_ADDRESS_HEX } from "../constants"; +import { ErrBadUsage } from "../errors"; import { IAddress } from "../interface"; import { Logger } from "../logger"; import { AddressValue, ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../smartcontracts"; @@ -24,10 +25,19 @@ interface IConfig { gasLimitEsdtNftAddQuantity: bigint; gasLimitEsdtNftBurn: bigint; gasLimitStorePerByte: bigint; + gasLimitEsdtModifyRoyalties: bigint; + gasLimitEsdtModifyCreator: bigint; + gasLimitEsdtMetadataUpdate: bigint; + gasLimitSetNewUris: bigint; + gasLimitNftMetadataRecreate: bigint; + gasLimitNftChangeToDynamic: bigint; + gasLimitUpdateTokenId: bigint; + gasLimitRegisterDynamic: bigint; issueCost: bigint; } type RegisterAndSetAllRolesTokenType = "NFT" | "SFT" | "META" | "FNG"; +type TokenTypes = "NFT" | "SFT" | "META" | "FNG"; /** * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. @@ -330,6 +340,7 @@ export class TokenManagementTransactionsFactory { addRoleNFTBurn: boolean; addRoleNFTAddQuantity: boolean; addRoleESDTTransferRole: boolean; + addRoleESDTModifyCreator?: boolean; }): Transaction { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; @@ -337,6 +348,7 @@ export class TokenManagementTransactionsFactory { options.addRoleNFTBurn ? args.push(new StringValue("ESDTRoleNFTBurn")) : 0; options.addRoleNFTAddQuantity ? args.push(new StringValue("ESDTRoleNFTAddQuantity")) : 0; options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; + options.addRoleESDTModifyCreator ? args.push(new StringValue("ESDTRoleModifyCreator")) : 0; const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; @@ -371,6 +383,8 @@ export class TokenManagementTransactionsFactory { addRoleNFTUpdateAttributes: boolean; addRoleNFTAddURI: boolean; addRoleESDTTransferRole: boolean; + addRoleESDTModifyCreator?: boolean; + addRoleNFTRecreate?: boolean; }): Transaction { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; @@ -379,6 +393,8 @@ export class TokenManagementTransactionsFactory { options.addRoleNFTUpdateAttributes ? args.push(new StringValue("ESDTRoleNFTUpdateAttributes")) : 0; options.addRoleNFTAddURI ? args.push(new StringValue("ESDTRoleNFTAddURI")) : 0; options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; + options.addRoleESDTModifyCreator ? args.push(new StringValue("ESDTRoleModifyCreator")) : 0; + options.addRoleNFTRecreate ? args.push(new StringValue("ESDTRoleNFTRecreate")) : 0; const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; @@ -402,6 +418,10 @@ export class TokenManagementTransactionsFactory { attributes: Uint8Array; uris: string[]; }): Transaction { + if (!options.uris.length) { + throw new ErrBadUsage(""); + } + const dataParts = [ "ESDTNFTCreate", ...this.argSerializer.valuesToStrings([ @@ -640,6 +660,231 @@ export class TokenManagementTransactionsFactory { }).build(); } + createTransactionForModifyingRoyalties(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + royalties: bigint; + }): Transaction { + const dataParts = [ + "ESDTModifyRoyalties", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new BigUIntValue(options.royalties), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtModifyRoyalties, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingNewUris(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + uris: string[]; + }): Transaction { + if (!options.uris.length) { + throw new ErrBadUsage(""); + } + + const dataParts = [ + "ESDTSetNewURIs", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + ...options.uris.map((uri) => new StringValue(uri)), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitSetNewUris, + addDataMovementGas: true, + }).build(); + } + + createTransactionForModifyingCreator(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + }): Transaction { + const dataParts = [ + "ESDTModifyCreator", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtModifyCreator, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUpdatingMetadata(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + tokenName: string; + royalties: bigint; + hash: string; + attributes: Uint8Array; + uris: string[]; + }): Transaction { + const dataParts = [ + "ESDTMetaDataUpdate", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new StringValue(options.tokenName), + new BigUIntValue(options.royalties), + new StringValue(options.hash), + new BytesValue(Buffer.from(options.attributes)), + ...options.uris.map((uri) => new StringValue(uri)), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtMetadataUpdate, + addDataMovementGas: true, + }).build(); + } + + createTransactionForNftMetadataRecreate(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + tokenName: string; + royalties: bigint; + hash: string; + attributes: Uint8Array; + uris: string[]; + }): Transaction { + const dataParts = [ + "ESDTMetaDataRecreate", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new StringValue(options.tokenName), + new BigUIntValue(options.royalties), + new StringValue(options.hash), + new BytesValue(Buffer.from(options.attributes)), + ...options.uris.map((uri) => new StringValue(uri)), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitNftMetadataRecreate, + addDataMovementGas: true, + }).build(); + } + + createTransactionForMakingTokenDynamic(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "changeToDynamic", + ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: this.esdtContractAddress, + dataParts: dataParts, + gasLimit: this.config.gasLimitNftChangeToDynamic, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUpdatingTokenId(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "updateTokenID", + ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: this.esdtContractAddress, + dataParts: dataParts, + gasLimit: this.config.gasLimitUpdateTokenId, + addDataMovementGas: true, + }).build(); + } + + createTransactionForRegisteringDynamicToken(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + tokenType: TokenTypes; + }): Transaction { + const dataParts = [ + "registerDynamic", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new StringValue(options.tokenType), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: this.esdtContractAddress, + dataParts: dataParts, + gasLimit: this.config.gasLimitRegisterDynamic, + addDataMovementGas: true, + }).build(); + } + + createTransactionForRegisteringDynamicAndSettingRoles(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + tokenType: TokenTypes; + }): Transaction { + const dataParts = [ + "registerAndSetAllRolesDynamic", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new StringValue(options.tokenType), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: this.esdtContractAddress, + dataParts: dataParts, + gasLimit: this.config.gasLimitRegisterDynamic, + addDataMovementGas: true, + }).build(); + } + private notifyAboutUnsettingBurnRoleGlobally() { Logger.info(` ========== diff --git a/src/transactionsFactories/transactionsFactoryConfig.ts b/src/transactionsFactories/transactionsFactoryConfig.ts index a05c187bf..c1d21df92 100644 --- a/src/transactionsFactories/transactionsFactoryConfig.ts +++ b/src/transactionsFactories/transactionsFactoryConfig.ts @@ -36,6 +36,14 @@ export class TransactionsFactoryConfig { gasLimitUnguardAccount: bigint; gasLimitClaimDeveloperRewards: bigint; gasLimitChangeOwnerAddress: bigint; + gasLimitEsdtModifyRoyalties: bigint; + gasLimitEsdtModifyCreator: bigint; + gasLimitEsdtMetadataUpdate: bigint; + gasLimitSetNewUris: bigint; + gasLimitNftMetadataRecreate: bigint; + gasLimitNftChangeToDynamic: bigint; + gasLimitUpdateTokenId: bigint; + gasLimitRegisterDynamic: bigint; constructor(options: { chainID: string }) { // General-purpose configuration @@ -59,6 +67,14 @@ export class TransactionsFactoryConfig { this.gasLimitEsdtNftBurn = 1000000n; this.gasLimitStorePerByte = 10000n; this.issueCost = 50000000000000000n; + this.gasLimitEsdtModifyRoyalties = 60000000n; + this.gasLimitEsdtModifyCreator = 60000000n; + this.gasLimitEsdtMetadataUpdate = 60000000n; + this.gasLimitSetNewUris = 60000000n; + this.gasLimitNftMetadataRecreate = 60000000n; + this.gasLimitNftChangeToDynamic = 60000000n; + this.gasLimitUpdateTokenId = 60000000n; + this.gasLimitRegisterDynamic = 60000000n; // Configuration for delegation operations this.gasLimitStake = 5000000n; From 6c3139cdbdb733059f2bb9cf279e52ad997a2730 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 25 Jul 2024 14:53:54 +0300 Subject: [PATCH 15/16] fixes after review --- ...anagementTransactionIntentsFactory.spec.ts | 34 +++++---- .../tokenManagementTransactionsFactory.ts | 73 +++++++++---------- 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts index a9fa53911..9d7ada3e8 100644 --- a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -186,12 +186,14 @@ describe("test token management transactions factory", () => { addRoleNFTUpdateAttributes: true, addRoleNFTAddURI: true, addRoleESDTTransferRole: false, + addRoleESDTModifyCreator: true, + addRoleNFTRecreate: true, }); assert.deepEqual( transaction.data, Buffer.from( - "setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@45534454526f6c654e4654437265617465@45534454526f6c654e465455706461746541747472696275746573@45534454526f6c654e4654416464555249", + "setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@45534454526f6c654e4654437265617465@45534454526f6c654e465455706461746541747472696275746573@45534454526f6c654e4654416464555249@45534454526f6c654d6f6469667943726561746f72@45534454526f6c654e46545265637265617465", ), ); assert.equal(transaction.sender, frank.address.toString()); @@ -225,7 +227,7 @@ describe("test token management transactions factory", () => { sender: grace.address, tokenIdentifier: "TEST-123456", tokenNonce: 1n, - royalties: 1234n, + newRoyalties: 1234n, }); assert.deepEqual(transaction.data, Buffer.from("ESDTModifyRoyalties@544553542d313233343536@01@04d2")); @@ -235,12 +237,12 @@ describe("test token management transactions factory", () => { assert.equal(transaction.gasLimit, 60125000n); }); - it("should create 'Transaction' for setting new uris", () => { + it("should create 'Transaction' for setting new URIs", () => { const transaction = tokenManagementFactory.createTransactionForSettingNewUris({ sender: grace.address, tokenIdentifier: "TEST-123456", tokenNonce: 1n, - uris: ["firstURI", "secondURI"], + newUris: ["firstURI", "secondURI"], }); assert.deepEqual( @@ -272,11 +274,11 @@ describe("test token management transactions factory", () => { sender: grace.address, tokenIdentifier: "TEST-123456", tokenNonce: 1n, - tokenName: "Test", - royalties: 1234n, - hash: "abba", - attributes: Buffer.from("test"), - uris: ["firstURI", "secondURI"], + newTokenName: "Test", + newRoyalties: 1234n, + newHash: "abba", + newAttributes: Buffer.from("test"), + newUris: ["firstURI", "secondURI"], }); assert.deepEqual( @@ -292,15 +294,15 @@ describe("test token management transactions factory", () => { }); it("should create 'Transaction' for recreating metadata", () => { - const transaction = tokenManagementFactory.createTransactionForNftMetadataRecreate({ + const transaction = tokenManagementFactory.createTransactionForMetadataRecreate({ sender: grace.address, tokenIdentifier: "TEST-123456", tokenNonce: 1n, - tokenName: "Test", - royalties: 1234n, - hash: "abba", - attributes: Buffer.from("test"), - uris: ["firstURI", "secondURI"], + newTokenName: "Test", + newRoyalties: 1234n, + newHash: "abba", + newAttributes: Buffer.from("test"), + newUris: ["firstURI", "secondURI"], }); assert.deepEqual( @@ -316,7 +318,7 @@ describe("test token management transactions factory", () => { }); it("should create 'Transaction' for changing to dynamic", () => { - const transaction = tokenManagementFactory.createTransactionForMakingTokenDynamic({ + const transaction = tokenManagementFactory.createTransactionForChangingTokenToDynamic({ sender: grace.address, tokenIdentifier: "TEST-123456", }); diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts index 2ff2638bf..eef02df80 100644 --- a/src/transactionsFactories/tokenManagementTransactionsFactory.ts +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -36,8 +36,7 @@ interface IConfig { issueCost: bigint; } -type RegisterAndSetAllRolesTokenType = "NFT" | "SFT" | "META" | "FNG"; -type TokenTypes = "NFT" | "SFT" | "META" | "FNG"; +type TokenType = "NFT" | "SFT" | "META" | "FNG"; /** * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. @@ -248,7 +247,7 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenName: string; tokenTicker: string; - tokenType: RegisterAndSetAllRolesTokenType; + tokenType: TokenType; numDecimals: bigint; }): Transaction { this.notifyAboutUnsettingBurnRoleGlobally(); @@ -385,6 +384,8 @@ export class TokenManagementTransactionsFactory { addRoleESDTTransferRole: boolean; addRoleESDTModifyCreator?: boolean; addRoleNFTRecreate?: boolean; + addRoleESDTSetNewURI?: boolean; + addRoleESDTModifyRoyalties?: boolean; }): Transaction { const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; @@ -395,6 +396,8 @@ export class TokenManagementTransactionsFactory { options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; options.addRoleESDTModifyCreator ? args.push(new StringValue("ESDTRoleModifyCreator")) : 0; options.addRoleNFTRecreate ? args.push(new StringValue("ESDTRoleNFTRecreate")) : 0; + options.addRoleESDTSetNewURI ? args.push(new StringValue("ESDTRoleSetNewURI")) : 0; + options.addRoleESDTModifyRoyalties ? args.push(new StringValue("ESDTRoleModifyRoyalties")) : 0; const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; @@ -418,10 +421,6 @@ export class TokenManagementTransactionsFactory { attributes: Uint8Array; uris: string[]; }): Transaction { - if (!options.uris.length) { - throw new ErrBadUsage(""); - } - const dataParts = [ "ESDTNFTCreate", ...this.argSerializer.valuesToStrings([ @@ -664,14 +663,14 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenIdentifier: string; tokenNonce: bigint; - royalties: bigint; + newRoyalties: bigint; }): Transaction { const dataParts = [ "ESDTModifyRoyalties", ...this.argSerializer.valuesToStrings([ new StringValue(options.tokenIdentifier), new BigUIntValue(options.tokenNonce), - new BigUIntValue(options.royalties), + new BigUIntValue(options.newRoyalties), ]), ]; @@ -689,10 +688,10 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenIdentifier: string; tokenNonce: bigint; - uris: string[]; + newUris: string[]; }): Transaction { - if (!options.uris.length) { - throw new ErrBadUsage(""); + if (!options.newUris.length) { + throw new ErrBadUsage("No URIs provided"); } const dataParts = [ @@ -700,7 +699,7 @@ export class TokenManagementTransactionsFactory { ...this.argSerializer.valuesToStrings([ new StringValue(options.tokenIdentifier), new BigUIntValue(options.tokenNonce), - ...options.uris.map((uri) => new StringValue(uri)), + ...options.newUris.map((uri) => new StringValue(uri)), ]), ]; @@ -741,22 +740,22 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenIdentifier: string; tokenNonce: bigint; - tokenName: string; - royalties: bigint; - hash: string; - attributes: Uint8Array; - uris: string[]; + newTokenName?: string; + newRoyalties?: bigint; + newHash?: string; + newAttributes?: Uint8Array; + newUris?: string[]; }): Transaction { const dataParts = [ "ESDTMetaDataUpdate", ...this.argSerializer.valuesToStrings([ new StringValue(options.tokenIdentifier), new BigUIntValue(options.tokenNonce), - new StringValue(options.tokenName), - new BigUIntValue(options.royalties), - new StringValue(options.hash), - new BytesValue(Buffer.from(options.attributes)), - ...options.uris.map((uri) => new StringValue(uri)), + ...(options.newTokenName ? [new StringValue(options.newTokenName)] : []), + ...(options.newRoyalties ? [new BigUIntValue(options.newRoyalties)] : []), + ...(options.newHash ? [new StringValue(options.newHash)] : []), + ...(options.newAttributes ? [new BytesValue(Buffer.from(options.newAttributes))] : []), + ...(options.newUris ? options.newUris.map((uri) => new StringValue(uri)) : []), ]), ]; @@ -770,26 +769,26 @@ export class TokenManagementTransactionsFactory { }).build(); } - createTransactionForNftMetadataRecreate(options: { + createTransactionForMetadataRecreate(options: { sender: IAddress; tokenIdentifier: string; tokenNonce: bigint; - tokenName: string; - royalties: bigint; - hash: string; - attributes: Uint8Array; - uris: string[]; + newTokenName: string; + newRoyalties: bigint; + newHash: string; + newAttributes: Uint8Array; + newUris: string[]; }): Transaction { const dataParts = [ "ESDTMetaDataRecreate", ...this.argSerializer.valuesToStrings([ new StringValue(options.tokenIdentifier), new BigUIntValue(options.tokenNonce), - new StringValue(options.tokenName), - new BigUIntValue(options.royalties), - new StringValue(options.hash), - new BytesValue(Buffer.from(options.attributes)), - ...options.uris.map((uri) => new StringValue(uri)), + ...(options.newTokenName ? [new StringValue(options.newTokenName)] : []), + ...(options.newRoyalties ? [new BigUIntValue(options.newRoyalties)] : []), + ...(options.newHash ? [new StringValue(options.newHash)] : []), + ...(options.newAttributes ? [new BytesValue(Buffer.from(options.newAttributes))] : []), + ...(options.newUris ? options.newUris.map((uri) => new StringValue(uri)) : []), ]), ]; @@ -803,7 +802,7 @@ export class TokenManagementTransactionsFactory { }).build(); } - createTransactionForMakingTokenDynamic(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + createTransactionForChangingTokenToDynamic(options: { sender: IAddress; tokenIdentifier: string }): Transaction { const dataParts = [ "changeToDynamic", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), @@ -839,7 +838,7 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenName: string; tokenTicker: string; - tokenType: TokenTypes; + tokenType: TokenType; }): Transaction { const dataParts = [ "registerDynamic", @@ -864,7 +863,7 @@ export class TokenManagementTransactionsFactory { sender: IAddress; tokenName: string; tokenTicker: string; - tokenType: TokenTypes; + tokenType: TokenType; }): Transaction { const dataParts = [ "registerAndSetAllRolesDynamic", From b62225c2683e20342f10324a077257d8f5628c6e Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 12 Aug 2024 11:31:14 +0300 Subject: [PATCH 16/16] bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef17c5ef3..2a8292979 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.3.0", + "version": "13.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.3.0", + "version": "13.4.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 44a8feb4a..fd87ca9e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.3.0", + "version": "13.4.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js",