Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider deprecating the 'Simple Transaction Builder' in the long run #253

Open
mr-zwets opened this issue Dec 31, 2024 · 2 comments
Open
Labels
javascript-sdk Relates to the CashScript JavaScript SDK

Comments

@mr-zwets
Copy link
Member

mr-zwets commented Dec 31, 2024

I suggest we consider deprecating 'simple transaction builder' in the long run.

More practically, we should first have to get proper debug tooling for the advanced tx builder but afterwards we can mark the 'simple tx builder' as deprecated (but still available). Then in the V1 release it could be fully removed.

The Simple Transaction Builder

The simple transaction builder is the original way to spend from Contracts using the CashScript SDK. It has the following features:

  • Automatic UTXO selection
  • Automatic change output
  • Call transaction building directly on the contract object

Together they try to abstract away 'transaction building' in favor of a wallet-like API where you can "just spend from a balance":

const transferDetails = await contract.functions
  .transfer(aliceTemplate)
  .to(aliceAddress, 10000n)
  .send();

This method breaks down fast: what if you want to send the contract.getBalance() amount, how to spend this full contractBalance when taking into account fees without knowing the number of inputs? What if you want to spend from covenants, where having multiple inputs requires the shape of the transaction to change?

Bad Abstraction

The simple transaction builder is a bad abstraction, it hides the fact that transactions are built with in- and outputs and assumes that a similar to a wallet smart contracts usage involves 'spending some amount from a total balance, returning the change".

The 'simple transaction builder' has a lot of extra settings to override this default fee/change behavior making the API less simple and more complex than the 'advanced tx builder'.

In practice most usage of the simple transaction builder involves .from() and .withoutChange(), at which point you'd be better just using the advanced builder. It defeats the point of the simple transaction builder and is a good indication it is a bad abstraction.

const outputAmount = contractUtxo.value - 1000n
const transferDetails = await new TransactionBuilder({ provider })
  .addInput(contractUtxo, contract.unlock.transfer(aliceTemplate))
  .addOutput({ to: aliceAddress, amount: outputAmount })
  .send()

Anti-features

I argue the 3 features of the simple tx builder are actually anti-features:

  1. This assumes the selected inputs don't affect the transaction shape besides the changeAmount, but in many cases the inputs DO define the full tx shape (including outputs) like when sending the max balance or working with (self replicating) covenants where the inputs decide the structure of the outputs.
  1. Sending change back to the contract is a bad, weird default that only makes sense in this weird abstraction level where contracts are seen as a 'wallet' where to spend a balance from. However this is rarely how contracts really work.

  2. calling a function on an object seems like a nice abstraction but it hides the core of the UTXO model: transactions have in and outputs. This causes new developers to have a wrong mental model from the start and causes them to have to learn about the 'simple tx builder' workarounds like .from() and .withoutChange(), when they'd be better just using the advanced builder

2 Ways of doing things

I consider it very bad that there are two ways of doing things, both with different abstraction levels and their own API documentation.
It introduces the questions what the difference is between the two and causes people to understand both at some basic level.

The advanced ts builder is also just accessible to beginner devs, but it starts them of with the correct mental model instead of confusing abstractions. Instead of calling 'functions on contracts' the mental model becomes 'creating a transaction with in- and outputs'

There would just be one 'transaction builder' and one documentation page. This definitely feels like the correct direction long term

@mr-zwets mr-zwets added the javascript-sdk Relates to the CashScript JavaScript SDK label Dec 31, 2024
@SayoshiNakamario
Copy link

I would agree, having two different ways to do the same thing can cause some confusion.

Also agree that abstracting away the change output management in the simple builder can lead to some potentially bad situations if someone learns to ignore change outputs then applies that logic to the advanced builder.

I think it also does help with learning the UTXO mental model, and I would keep .addInput/.addOutput of the advanced builder to reinforce this over .to/.from of the simple builder.

I myself immediately defaulted to always using .withoutChange() and .withoutTokenChange() on the simple builder.

All assuming the advanced builder gets debugging 😁

@emergent-reasons
Copy link
Contributor

Hard to argue with this. I would argue that a "Simple" Builder might be a good idea as a higher level abstraction, but the current one isn't it because usage of it isn't actually simple and it makes bad choices as you outlined well. A simple builder would be much more complex under the hood.

In the meantime, as you also pointed out, the standard builder is not complicated to use and provides a correct mental model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
javascript-sdk Relates to the CashScript JavaScript SDK
Projects
None yet
Development

No branches or pull requests

3 participants