diff --git a/.changeset/big-jobs-roll.md b/.changeset/big-jobs-roll.md new file mode 100644 index 00000000..13bda09d --- /dev/null +++ b/.changeset/big-jobs-roll.md @@ -0,0 +1,5 @@ +--- +"@blaze-cardano/tx": patch +--- + +patch: @AngelCastilloB Tx Builder wont select any inputs if withdrawal amount is enough to balance the transaction diff --git a/README.md b/README.md index 1006b565..1b2addb4 100644 --- a/README.md +++ b/README.md @@ -75,22 +75,22 @@ const tx = await blaze // Dump the transaction for you to submit securely console.log(`Please sign and submit this transaction: ${tx.toCbor()}`); ``` + ### Providers Blaze supports multiple providers for interacting with the Cardano blockchain. Below is a table of external and built-in providers: -| Provider | Type | Link | -|------------|----------|--------------------| -| Blockfrost | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/blockfrost.ts) | -| Kupmios | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/kupmios.ts) | -| Maestro | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/maestro.ts) | -| UTxORPC (U5C) | External | [Repository](https://github.com/utxorpc/blaze-provider) | +| Provider | Type | Link | +| ------------- | -------- | ------------------------------------------------------------------------------------------------------------- | +| Blockfrost | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/blockfrost.ts) | +| Kupmios | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/kupmios.ts) | +| Maestro | Built-in | [View File](https://github.com/butaneprotocol/blaze-cardano/blob/main/packages/blaze-query/src/maestro.ts) | +| UTxORPC (U5C) | External | [Repository](https://github.com/utxorpc/blaze-provider) | ### Docs Blaze channel is in the [TxPipe Discord](https://discord.gg/FAeAR6jX)! - ### Runs on Blaze A list of projects, apps, websites, repositories, which depend on this library in some manner. diff --git a/packages/blaze-tx/src/tx.ts b/packages/blaze-tx/src/tx.ts index 39d68d70..dec92614 100644 --- a/packages/blaze-tx/src/tx.ts +++ b/packages/blaze-tx/src/tx.ts @@ -1518,8 +1518,35 @@ export class TxBuilder { excessValue = value.merge(excessValue, selectionResult.selectedValue); spareInputs = selectionResult.inputs; // Add selected inputs to the transaction. - for (const input of selectionResult.selectedInputs) { - this.addInput(input); + if (selectionResult.selectedInputs.length > 0) { + for (const input of selectionResult.selectedInputs) { + this.addInput(input); + } + } else { + if (this.body.inputs().size() == 0) { + if (!spareInputs[0]) { + throw new Error( + "No spare inputs available to add to the transaction", + ); + } + // Select the input with the least number of different multiassets from spareInputs + const [inputWithLeastMultiAssets] = spareInputs.reduce( + ([minInput, minMultiAssetCount], currentInput) => { + const currentMultiAssetCount = value.assetTypes( + currentInput.output().amount(), + ); + return currentMultiAssetCount < minMultiAssetCount + ? [currentInput, minMultiAssetCount] + : [minInput, value.assetTypes(minInput.output().amount())]; + }, + [spareInputs[0], value.assetTypes(spareInputs[0].output().amount())], + ); + this.addInput(inputWithLeastMultiAssets); + // Remove the selected input from spareInputs + spareInputs = spareInputs.filter( + (input) => input !== inputWithLeastMultiAssets, + ); + } } if (this.body.inputs().values().length == 0) { throw new Error( diff --git a/packages/blaze-tx/test/tx.test.ts b/packages/blaze-tx/test/tx.test.ts index 12901dfe..7fd2cf66 100644 --- a/packages/blaze-tx/test/tx.test.ts +++ b/packages/blaze-tx/test/tx.test.ts @@ -2,6 +2,7 @@ import { Address, hardCodedProtocolParams, NetworkId, + RewardAccount, TransactionId, TransactionInput, TransactionOutput, @@ -93,6 +94,7 @@ describe("Transaction Building", () => { // console.dir(tx.toCore(), {depth: null}) expect(inputValue.toCbor()).toEqual(outputValue.toCbor()); }); + it("Should correctly balance change for a really big output change", async () => { // $hosky const testAddress = Address.fromBech32( @@ -169,4 +171,58 @@ describe("Transaction Building", () => { ); expect(inputValue.toCbor()).toEqual(outputValue.toCbor()); }); + + it("A transaction should always have some inputs", async () => { + // $hosky + const testAddress = Address.fromBech32( + "addr1q86ylp637q7hv7a9r387nz8d9zdhem2v06pjyg75fvcmen3rg8t4q3f80r56p93xqzhcup0w7e5heq7lnayjzqau3dfs7yrls5", + ); + const utxos = [ + new TransactionUnspentOutput( + new TransactionInput(TransactionId("0".repeat(64)), 0n), + new TransactionOutput(testAddress, value.makeValue(50_000_000n)), + ), + new TransactionUnspentOutput( + new TransactionInput(TransactionId("1".padStart(64, "0")), 0n), + new TransactionOutput(testAddress, value.makeValue(40_000_000n)), + ), + ]; + const tx = await new TxBuilder(hardCodedProtocolParams) + .addUnspentOutputs(utxos) + .setNetworkId(NetworkId.Testnet) + .setChangeAddress(testAddress) + .addWithdrawal( + RewardAccount.fromCredential( + testAddress.getProps().paymentPart!, + NetworkId.Testnet, + ), + 100_000_000n, + ) + .payAssets(testAddress, value.makeValue(48_708_900n)) + .complete(); + + const inputValue = value.merge( + tx + .body() + .inputs() + .values() + .map((x) => + utxos + .find((y) => y.input().toCbor() == x.toCbor())! + .output() + .amount(), + ) + .reduce(value.merge, value.zero()), + value.makeValue(100_000_000n), + ); + + const outputValue = value.merge( + flatten(tx.body().outputs().values()) + .map((x) => x.amount()) + .reduce(value.merge, value.zero()), + new Value(tx.body().fee()), + ); + expect(inputValue.toCbor()).toEqual(outputValue.toCbor()); + expect(tx.body().inputs().values().length).toBeGreaterThan(0); + }); });