Skip to content

Commit

Permalink
Merge pull request #526 from multiversx/TOOL-307-add-esdt-prefix-to-s…
Browse files Browse the repository at this point in the history
…upport-sovereign-integration

Add prefix handling for esdt prefix
  • Loading branch information
danielailie authored Nov 8, 2024
2 parents 6ca61fa + e87ae2b commit 0e17949
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 19 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-core",
"version": "13.13.1",
"version": "13.14.0",
"description": "MultiversX SDK for JavaScript and TypeScript",
"author": "MultiversX",
"homepage": "https://multiversx.com",
Expand Down
24 changes: 24 additions & 0 deletions src/tokens.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,44 @@ describe("test tokens and token computer", async () => {
let nonce = tokenComputer.extractNonceFromExtendedIdentifier(extendedIdentifier);
assert.equal(nonce, 10);

const extendedIdentifierWithPrefix = "test-TEST-123456-0a";
nonce = tokenComputer.extractNonceFromExtendedIdentifier(extendedIdentifierWithPrefix);
assert.equal(nonce, 10);

const fungibleTokenIdentifier = "FNG-123456";
nonce = tokenComputer.extractNonceFromExtendedIdentifier(fungibleTokenIdentifier);
assert.equal(nonce, 0);

const fungibleTokenIdentifierWithPrefix = "fun-FNG-123456";
nonce = tokenComputer.extractNonceFromExtendedIdentifier(fungibleTokenIdentifierWithPrefix);
assert.equal(nonce, 0);
});

it("should extract identifier from extended identifier", async () => {
const extendedIdentifier = "TEST-123456-0a";
let identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(extendedIdentifier);
assert.equal(identifier, "TEST-123456");

const extendedIdentifierWithPrefix = "t0-TEST-123456-0a";
identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(extendedIdentifierWithPrefix);
assert.equal(identifier, "t0-TEST-123456");

const extendedIdentifierWithPrefixWithoutNonce = "t0-TEST-123456";
identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(extendedIdentifierWithPrefixWithoutNonce);
assert.equal(identifier, "t0-TEST-123456");

const fungibleTokenIdentifier = "FNG-123456";
identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(fungibleTokenIdentifier);
assert.equal(identifier, "FNG-123456");
});

it("should fail if prefix longer than expected", async () => {
const nftIdentifier = "prefix-TEST-123456";
assert.throw(
() => tokenComputer.extractIdentifierFromExtendedIdentifier(nftIdentifier),
"The identifier is not valid. The prefix does not have the right length",
);
});
});

describe("test token transfer (legacy)", () => {
Expand Down
67 changes: 51 additions & 16 deletions src/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BigNumber from "bignumber.js";
import { ErrInvalidArgument, ErrInvalidTokenIdentifier } from "./errors";
import { EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER } from "./constants";
import { ErrInvalidArgument, ErrInvalidTokenIdentifier } from "./errors";

// Legacy constants:
const EGLDTokenIdentifier = "EGLD";
Expand Down Expand Up @@ -226,6 +226,7 @@ export class TokenTransfer {
}

export class TokenComputer {
TOKEN_RANDOM_SEQUENCE_LENGTH = 6;
constructor() {}

isFungible(token: Token): boolean {
Expand All @@ -235,33 +236,55 @@ export class TokenComputer {
extractNonceFromExtendedIdentifier(identifier: string): number {
const parts = identifier.split("-");

this.checkIfExtendedIdentifierWasProvided(parts);
this.checkLengthOfRandomSequence(parts[1]);
const { prefix, ticker, randomSequence } = this.splitIdentifierIntoComponents(parts);
this.validateExtendedIdentifier(prefix, ticker, randomSequence, parts);

// in case the identifier of a fungible token is provided
if (parts.length == 2) {
// If identifier is for a fungible token (2 parts or 3 with prefix), return 0
if (parts.length === 2 || (prefix && parts.length === 3)) {
return 0;
}

const hexNonce = Buffer.from(parts[2], "hex");
return decodeUnsignedNumber(hexNonce);
// Otherwise, decode the last part as an unsigned number
const hexNonce = parts[parts.length - 1];
return decodeUnsignedNumber(Buffer.from(hexNonce, "hex"));
}

extractIdentifierFromExtendedIdentifier(identifier: string): string {
const parts = identifier.split("-");
const { prefix, ticker, randomSequence } = this.splitIdentifierIntoComponents(parts);

this.validateExtendedIdentifier(prefix, ticker, randomSequence, parts);
if (prefix) {
this.checkLengthOfPrefix(prefix);
return prefix + "-" + ticker + "-" + randomSequence;
}
return ticker + "-" + randomSequence;
}

private validateExtendedIdentifier(
prefix: string | null,
ticker: string,
randomSequence: string,
parts: string[],
): void {
this.checkIfExtendedIdentifierWasProvided(prefix, parts);
this.ensureTokenTickerValidity(ticker);
this.checkLengthOfRandomSequence(randomSequence);
}

this.checkIfExtendedIdentifierWasProvided(parts);
this.ensureTokenTickerValidity(parts[0]);
this.checkLengthOfRandomSequence(parts[1]);
private splitIdentifierIntoComponents(parts: string[]): { prefix: any; ticker: any; randomSequence: any } {
if (this.isLowercaseAlphanumeric(parts[0])) {
return { prefix: parts[0], ticker: parts[1], randomSequence: parts[2] };
}

return parts[0] + "-" + parts[1];
return { prefix: null, ticker: parts[0], randomSequence: parts[1] };
}

private checkIfExtendedIdentifierWasProvided(tokenParts: string[]): void {
private checkIfExtendedIdentifierWasProvided(prefix: string | null, tokenParts: string[]): void {
// this is for the identifiers of fungible tokens
const MIN_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED = 2;
// this is for the identifiers of nft, sft and meta-esdt
const MAX_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED = 3;
const MAX_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED = prefix ? 4 : 3;

if (
tokenParts.length < MIN_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED ||
Expand All @@ -271,16 +294,28 @@ export class TokenComputer {
}
}

private checkLengthOfRandomSequence(randomSequence: string): void {
const TOKEN_RANDOM_SEQUENCE_LENGTH = 6;
private isLowercaseAlphanumeric(str: string): boolean {
return /^[a-z0-9]+$/.test(str);
}

if (randomSequence.length !== TOKEN_RANDOM_SEQUENCE_LENGTH) {
private checkLengthOfRandomSequence(randomSequence: string): void {
if (randomSequence.length !== this.TOKEN_RANDOM_SEQUENCE_LENGTH) {
throw new ErrInvalidTokenIdentifier(
"The identifier is not valid. The random sequence does not have the right length",
);
}
}

private checkLengthOfPrefix(prefix: string): void {
const MAX_TOKEN_PREFIX_LENGTH = 4;
const MIN_TOKEN_PREFIX_LENGTH = 1;
if (prefix.length < MIN_TOKEN_PREFIX_LENGTH || prefix.length > MAX_TOKEN_PREFIX_LENGTH) {
throw new ErrInvalidTokenIdentifier(
"The identifier is not valid. The prefix does not have the right length",
);
}
}

private ensureTokenTickerValidity(ticker: string) {
const MIN_TICKER_LENGTH = 3;
const MAX_TICKER_LENGTH = 10;
Expand Down
75 changes: 75 additions & 0 deletions src/transactionsFactories/transferTransactionsFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ describe("test transfer transactions factory", function () {
);
});

it("should create 'Transaction' for nft transfer with prefix", async () => {
const nft = new Token({ identifier: "t0-NFT-123456", nonce: 10n });
const transfer = new TokenTransfer({ token: nft, amount: 1n });

const transaction = transferFactory.createTransactionForESDTTokenTransfer({
sender: alice,
receiver: bob,
tokenTransfers: [transfer],
});

assert.equal(transaction.sender, alice.toBech32());
assert.equal(transaction.receiver, alice.toBech32());
assert.equal(transaction.value.valueOf(), 0n);
assert.equal(transaction.gasLimit.valueOf(), 1219500n);
assert.deepEqual(
transaction.data.toString(),
"ESDTNFTTransfer@74302d4e46542d313233343536@0a@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8",
);
});

it("should create 'Transaction' for multiple nft transfers", async () => {
const firstNft = new Token({ identifier: "NFT-123456", nonce: 10n });
const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n });
Expand Down Expand Up @@ -128,6 +148,37 @@ describe("test transfer transactions factory", function () {
assert.deepEqual(transaction, secondTransaction);
});

it("should create 'Transaction' for multiple nft transfers with prefix", async () => {
const firstNft = new Token({ identifier: "t0-NFT-123456", nonce: 10n });
const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n });

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

const transaction = transferFactory.createTransactionForESDTTokenTransfer({
sender: alice,
receiver: bob,
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(), 1484000n);
assert.deepEqual(
transaction.data.toString(),
"MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@74302d4e46542d313233343536@0a@01@74302d544553542d393837363534@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 });
Expand Down Expand Up @@ -207,6 +258,30 @@ describe("test transfer transactions factory", function () {
);
});

it("should create transaction for token transfers with prefix", async () => {
const firstNft = new Token({ identifier: "t0-NFT-123456", nonce: 10n });
const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n });

const secondNft = new Token({ identifier: "t0-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(), 1745500n);
assert.deepEqual(
transaction.data.toString(),
"MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@03@74302d4e46542d313233343536@0a@01@74302d544553542d393837363534@01@01@45474c442d303030303030@@0de0b6b3a7640000",
);
});

it("should create multi transfer for egld", async () => {
const firstNft = new Token({ identifier: EGLD_IDENTIFIER_FOR_MULTI_ESDTNFT_TRANSFER });
const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1000000000000000000n });
Expand Down

0 comments on commit 0e17949

Please sign in to comment.