Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send Native token in MultiESDTNFTTransfer #462

Merged
merged 7 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
8 changes: 8 additions & 0 deletions src/tokens.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
6 changes: 6 additions & 0 deletions src/tokens.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down
49 changes: 49 additions & 0 deletions src/transactionsFactories/smartContractTransactionsFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
16 changes: 9 additions & 7 deletions src/transactionsFactories/smartContractTransactionsFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Address } from "../address";
import { CONTRACT_DEPLOY_ADDRESS_HEX, VM_TYPE_WASM_VM } from "../constants";
import { Err, ErrBadUsage } from "../errors";
import { Err } from "../errors";
import { IAddress } from "../interface";
import { Logger } from "../logger";
import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts";
Expand Down Expand Up @@ -91,19 +91,21 @@ 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 ? [...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)");
tokenTransfers.push(TokenTransfer.newFromEgldAmount(nativeTransferAmount));
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);
Expand All @@ -112,7 +114,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;
}

Expand Down
109 changes: 98 additions & 11 deletions src/transactionsFactories/transferTransactionsFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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());
Expand All @@ -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"));
Expand All @@ -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");
Expand All @@ -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(
Expand All @@ -109,13 +109,100 @@ 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(
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(() => {
const nft = new Token({ identifier: "NFT-123456", nonce: 10n });
const transfer = new TokenTransfer({ token: nft, amount: 1n });

transferFactory.createTransactionForTransfer({
sender: alice,
receiver: bob,
tokenTransfers: [transfer],
data: Buffer.from("test"),
});
}, "Can't set data field when sending esdt tokens");
});

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 });

const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n });
const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n });

const transaction = transferFactory.createTransactionForTransfer({
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",
);
});
});
54 changes: 41 additions & 13 deletions src/transactionsFactories/transferTransactionsFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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.
Expand Down Expand Up @@ -82,18 +82,10 @@ export class TransferTransactionsFactory {
return this.gasEstimator !== undefined;
}

private ensureMembersAreDefined() {
private ensureConfigIsDefined() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, the presence of this.config indicates the presence of tokenComputer and tokenTransfersDataBuilder, as well. Also, we can set this.tokenComputer and this.tokenTransfersDataBuilder in constructor no matter what (irrespective to isGasEstimator()).

In v14, this is the first legacy thing to be dropped :)

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: {
Expand All @@ -102,7 +94,7 @@ export class TransferTransactionsFactory {
nativeAmount: bigint;
data?: Uint8Array;
}): Transaction {
this.ensureMembersAreDefined();
this.ensureConfigIsDefined();

const data = options.data || new Uint8Array();

Expand All @@ -121,7 +113,7 @@ export class TransferTransactionsFactory {
receiver: IAddress;
tokenTransfers: TokenTransfer[];
}): Transaction {
this.ensureMembersAreDefined();
this.ensureConfigIsDefined();

const numberOfTransfers = options.tokenTransfers.length;

Expand Down Expand Up @@ -152,6 +144,42 @@ export class TransferTransactionsFactory {
}).build();
}

createTransactionForTransfer(options: {
sender: IAddress;
receiver: IAddress;
nativeAmount?: bigint;
tokenTransfers?: TokenTransfer[];
data?: Uint8Array;
}): Transaction {
const nativeAmount = options.nativeAmount ?? 0n;
let tokenTransfers = options.tokenTransfers ? [...options.tokenTransfers] : [];
const numberOfTokens = tokenTransfers.length;

if (numberOfTokens && options.data?.length) {
throw new ErrBadUsage("Can't set data field when sending esdt tokens");
}

if ((nativeAmount && numberOfTokens === 0) || options.data) {
return this.createTransactionForNativeTokenTransfer({
sender: options.sender,
receiver: options.receiver,
nativeAmount: nativeAmount,
data: options.data,
});
}

const nativeTransfer = nativeAmount ? TokenTransfer.newFromEgldAmount(nativeAmount) : undefined;
if (nativeTransfer) {
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.
Expand Down Expand Up @@ -336,7 +364,7 @@ export class TransferTransactionsFactory {
receiver: IAddress;
tokenTransfers: TokenTransfer[];
}): Transaction {
this.ensureMembersAreDefined();
this.ensureConfigIsDefined();

let dataParts: string[] = [];
const transfer = options.tokenTransfers[0];
Expand Down
Loading