diff --git a/.changeset/tidy-selection-fails.md b/.changeset/tidy-selection-fails.md new file mode 100644 index 00000000..cfc3ec62 --- /dev/null +++ b/.changeset/tidy-selection-fails.md @@ -0,0 +1,5 @@ +--- +"@fleet-sdk/core": patch +--- + +fix(core): throw InsufficientInputs when a token target cannot be reached because no selected input holds the token, instead of crashing with "Empty array." or a TypeError on empty selections diff --git a/packages/core/src/builder/selector/boxSelector.spec.ts b/packages/core/src/builder/selector/boxSelector.spec.ts index 615a61e5..eb7c5af6 100644 --- a/packages/core/src/builder/selector/boxSelector.spec.ts +++ b/packages/core/src/builder/selector/boxSelector.spec.ts @@ -243,6 +243,30 @@ describe("Validations", () => { }).toThrow(InsufficientInputs); }); + it("Should fail if tokens target is unreached and no input holds the token", () => { + const tokenId = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const selector = new BoxSelector(regularBoxes); + + let error: unknown; + try { + selector.select({ tokens: [{ tokenId, amount: 100n }] }); + } catch (e) { + error = e; + } + + expect(error).toBeInstanceOf(InsufficientInputs); + expect((error as InsufficientInputs).unreached.tokens).toEqual([{ tokenId, amount: 100n }]); + }); + + it("Should fail if tokens target is unreached and there are no inputs at all", () => { + const tokenId = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const selector = new BoxSelector([]); + + expect(() => { + selector.select({ tokens: [{ tokenId, amount: 100n }] }); + }).toThrow(InsufficientInputs); + }); + it("Should fail if any target is unreached", () => { const tokenId = "0cd8c9f416e5b1ca9f986a7f10a84191dfb85941619e49e53c0dc30ebf83324b"; const selector = new BoxSelector(regularBoxes); diff --git a/packages/core/src/builder/selector/boxSelector.ts b/packages/core/src/builder/selector/boxSelector.ts index 86c85b49..13fde675 100644 --- a/packages/core/src/builder/selector/boxSelector.ts +++ b/packages/core/src/builder/selector/boxSelector.ts @@ -68,7 +68,7 @@ export class BoxSelector> { let inclusion = this._ensureInclusionBoxIds; // if the target has a token that is being minted, then the first input should be included - if (target.tokens?.some((x) => x.tokenId === unselected[0].boxId)) { + if (some(unselected) && target.tokens?.some((x) => x.tokenId === unselected[0].boxId)) { inclusion = new Set(this._ensureInclusionBoxIds).add(unselected[0].boxId); } @@ -139,7 +139,7 @@ export class BoxSelector> { for (const tokenTarget of target.tokens) { const totalSelected = utxoSum(inputs, tokenTarget.tokenId); if (tokenTarget.amount && tokenTarget.amount > totalSelected) { - if (tokenTarget.tokenId === first(inputs).boxId) { + if (some(inputs) && tokenTarget.tokenId === first(inputs).boxId) { // if the token is the same as the first input, then it is being minted continue; }