Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/tidy-minting-tracks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/core": patch
---

fix(core): keep TokensCollection minting metadata attached to the minting token when other tokens are inserted or removed, instead of reporting another token's amount, reporting a minting token that was removed, or throwing a TypeError
46 changes: 46 additions & 0 deletions packages/core/src/models/collections/tokensCollection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,52 @@ describe("Tokens collection", () => {
}).toThrow(NotFoundError);
});

it("Should keep tracking the minting token after a preceding token is removed", () => {
const collection = new TokensCollection();
collection.add({ tokenId: tokenA, amount: 50n });
collection.mint({ amount: 100n, name: "test" });
collection.add({ tokenId: tokenB, amount: 10n });

collection.remove(tokenA);

expect(collection.minting?.amount).toEqual(100n);
expect(collection.minting?.name).toEqual("test");
});

it("Should keep tracking the minting token after a token is inserted before it", () => {
const collection = new TokensCollection();
collection.add({ tokenId: tokenA, amount: 50n });
collection.mint({ amount: 100n, name: "test" });

collection.add({ tokenId: tokenB, amount: 10n }, { index: 0 });

expect(collection.minting?.amount).toEqual(100n);
expect(collection.minting?.name).toEqual("test");
});

it("Should not report a minting token after the minting token is removed", () => {
const collection = new TokensCollection();
collection.mint({ amount: 100n, name: "test" });
collection.add({ tokenId: tokenA, amount: 50n });

collection.remove(0);

expect(collection.minting).toBeUndefined();
expect(collection.toArray()).toEqual([{ tokenId: tokenA, amount: 50n }]);
expect(() => collection.mint({ amount: 1n, name: "test_2" })).not.toThrow();
expect(collection.minting?.name).toEqual("test_2");
});

it("Should not throw when reading minting after the last token is removed", () => {
const collection = new TokensCollection();
collection.add({ tokenId: tokenA, amount: 50n });
collection.mint({ amount: 100n, name: "test" });

collection.remove(tokenA);

expect(collection.minting?.amount).toEqual(100n);
});

it("Should throw when trying to remove by an out of bounds index", () => {
const collection = new TokensCollection();
collection.add([
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/models/collections/tokensCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type OutputToken<T extends Amount = Amount> = {
amount: T;
};

type MintingData = { index: number; metadata: NewToken<Amount> };
type MintingData = { item: OutputToken<bigint>; metadata: NewToken<Amount> };

export class TokensCollection extends Collection<OutputToken<bigint>, OutputToken> {
#minting: MintingData | undefined;
Expand All @@ -38,10 +38,10 @@ export class TokensCollection extends Collection<OutputToken<bigint>, OutputToke
}

public get minting(): NewToken<bigint> | undefined {
if (!this.#minting) return undefined;
if (!this.#minting || !this._items.includes(this.#minting.item)) return undefined;
return {
...this.#minting.metadata,
amount: this._items[this.#minting.index].amount
amount: this.#minting.item.amount
};
}

Expand Down Expand Up @@ -71,12 +71,21 @@ export class TokensCollection extends Collection<OutputToken<bigint>, OutputToke
}

public mint(token: NewToken<Amount>): number {
if (isDefined(this.#minting)) {
if (isDefined(this.minting)) {
throw new FleetError("Only one minting token is allowed per transaction.");
}

const previousLength = this.length;
const len = super.add({ tokenId: token.tokenId, amount: token.amount });
this.#minting = { index: len - 1, metadata: token };
// If the amount was summed into an existing item, the minting item is that
// item; otherwise it is the newly appended one. Track the item itself, not
// its index, so later insertions and removals cannot detach the minting
// metadata from the token it belongs to.
const item =
len > previousLength
? this._items[len - 1]
: (this._items.find((x) => x.tokenId === token.tokenId) ?? this._items[len - 1]);
this.#minting = { item, metadata: token };
return this.length;
}

Expand Down