Skip to content

Commit

Permalink
add support for sending both native and esdts using MutiESDTNFTTransfer
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Jun 28, 2024
1 parent 152b90a commit e979e03
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 34 deletions.
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
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
23 changes: 14 additions & 9 deletions src/transactionsFactories/smartContractTransactionsFactory.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down
67 changes: 56 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,58 @@ 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",
);
});

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",
);
});
});
68 changes: 54 additions & 14 deletions src/transactionsFactories/transferTransactionsFactory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "../constants";
import { Err, ErrBadUsage } from "../errors";
import {
IAddress,
Expand All @@ -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";
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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: {
Expand All @@ -102,7 +95,7 @@ export class TransferTransactionsFactory {
nativeAmount: bigint;
data?: Uint8Array;
}): Transaction {
this.ensureMembersAreDefined();
this.ensureConfigIsDefined();

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

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

const numberOfTransfers = options.tokenTransfers.length;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -336,7 +376,7 @@ export class TransferTransactionsFactory {
receiver: IAddress;
tokenTransfers: TokenTransfer[];
}): Transaction {
this.ensureMembersAreDefined();
this.ensureConfigIsDefined();

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

0 comments on commit e979e03

Please sign in to comment.