From e2a6bd39795910d36dfbfafe0c049ac38ca84f0d Mon Sep 17 00:00:00 2001 From: Kyle Cox Date: Fri, 9 Oct 2026 00:45:12 +0000 Subject: [PATCH] fix(core): throw InsufficientInputs for token targets no input can reach BoxSelector.select() crashed instead of throwing the documented InsufficientInputs error in two empty-selection cases: - When no input holds the target token (and no ERG target pulls any input into the selection), _getUnreachedTargets called first(inputs).boxId on the empty selection to check the minting exemption, and first() asserts with a bare 'Empty array.' error. - When there are no inputs at all, the minting check at the top of select() dereferenced unselected[0].boxId and threw a TypeError. Both checks now guard on a non-empty list, so an unreached token target always surfaces as InsufficientInputs carrying the unreached token and amount. The minting exemption is unchanged when at least one input is selected. --- .changeset/tidy-selection-fails.md | 5 ++++ .../src/builder/selector/boxSelector.spec.ts | 24 +++++++++++++++++++ .../core/src/builder/selector/boxSelector.ts | 4 ++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 .changeset/tidy-selection-fails.md 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; }