solana-web3.js icon indicating copy to clipboard operation
solana-web3.js copied to clipboard

Suggestion: add a helper function just for sender -> recipient SOL transfers

Open mikemaccana opened this issue 2 years ago • 8 comments

Motivation

A lot of the time users just want to send some SOL from a single account to a single recipient. Creating transactions for these should be simpler.

I can understand concern about wanting to present the reusable low level functionality of web3.js, but we can always document the helper function, then show the equivalent in the low-level tool to help people's understanding.

Example use case

A user wishes to send some SOL to another person, possibly adding a note.

const signature = await sendSOL({ 
  from: CryptoKeyPair, 
  to: address, 
  lamports: bigInt or number, 
  note?: string
})

This would:

  • Create a transaction with a single instruction to transfer some SOL to the recipient
  • Optionally add a note instruction (visible in end-user wallets) using the Note program. Notes are a common part of non-blockchain transactions and make for a more readable transaction history in end user apps. Note (and Memo) have been documented by Foundation for some time and are supported right now by Glow and Phantom's transaction histories.
  • Sign the transaction with the privateKey of the sender
  • Get a recent blockhash and add it to the transaction
  • Execute the transaction

Details

Ideally we'd be able to handle any SPL token:

const signature = await sendTokens({ 
  from: CryptoKeyPair, 
  to: address, 
  amountInMinorUnits: bigInt or number, 
  tokenMint: address,
  note?: string
})

Which would:

  • Work out the ATAs for the token mint specified on the from and to address
  • Create an instruction to transfer the token specified between those ATAs instead

But my understanding is that the Token and AssociatedTokenAddress will be supported in a separate library built by kinobi.

Still a person can dream ☺️

mikemaccana avatar Oct 06 '23 14:10 mikemaccana

Linking @mcintyre94's comment at https://github.com/solana-labs/solana-web3.js/issues/1694#issuecomment-1750789156 re: shipping a general purpose high level library.

mikemaccana avatar Oct 09 '23 12:10 mikemaccana

IMO we should autogenerate these and create a good API around the generated instructions so they can be easily composed together. Here's a rough example of what this could look like.

await pipe(
  createTransaction(),
  transferSol(context, { from, to, lamports }),
  addMemo(context, { memo }),
  tx => sendAndConfirm(tx)
)

And by passing a context offered by a framework like Umi, this could even become.

await transferSol(umi, { from, to, lamports })
  .add(addMemo(umi, { memo }))
  .sendAndConfirm(umi)

We can always tweak the end-user API to make it more friendly. If people feel comfortable using them directly, then we've done a good job. If we start to feel the need of adding lots of helper functions to abstract the end-user API, it's an indication that we should revisit its design.

lorisleiva avatar Oct 09 '23 13:10 lorisleiva

The main thing there is that piping and method chaining are not popular among JS developers, compared to using plain JS objects for configuration. https://github.com/solana-labs/solana-web3.js/issues/1693 has a lot of detail on this, @Woody4618 may have some thoughts too.

mikemaccana avatar Oct 10 '23 15:10 mikemaccana

That proposal doesn't really help with signing, sending and confirming transactions though. Which mean devs will end up using 3-4 const variables or piping anyway. However, notice the second code snippet which uses a TransactionBuilder object provided by Umi. That's just an example showing that, in the end, we can design whichever API we want even from the same autogenerated code. Right now, the new Web3.js doesn't offer an TransactionBuilder object to optimise tree-shakability. But we could offer an optional TransactionBuilder for users that want to have a different (more fluent) experience.

I also agree that piping and functional programming in general is not super popular amongst JS devs but it is the optimal way to offer slim libraries. If we provide a good function programming experience and educate devs about it then there's a way this can work even without a TransactionBuilder or similar.

lorisleiva avatar Oct 10 '23 15:10 lorisleiva

I dont really have a strong oppinion on this. I didnt know the pipe operator until yesterday. Looks like another thing ppl need to learn. How would this example look like with a instruction to an anchor program? And can i also just get the instruction from the sendSol to build my own transaction? How do i sign? How can i add another feepayer or partial sign? I havnt really tried the new package yet.

Woody4618 avatar Oct 10 '23 15:10 Woody4618

Pardon the wait, it's been a long month with Breakpoint

That proposal doesn't really help with signing, sending and confirming transactions though.

That's correct. The intention is to create the transaction with a plain JS object per other common JS SDKs.

Which mean devs will end up using 3-4 const variables or piping anyway.

That's not correct - the intention of the proposal is require a single const for the entire transaction and its instructions and other internal components.

I've added more detail to https://github.com/solana-labs/solana-web3.js/issues/1693#issuecomment-1823460118 to explain the thinking here.

mikemaccana avatar Nov 22 '23 19:11 mikemaccana

Oh, you want mutable transactions. The entire new web3.js is based on immutable data though. It is even defined as the first principle of the rewrite.

In order to improve the developer experience around this, @steveluscher created an "API for transaction builder" issue allowing us to offer and discuss potential solutions. From this, the pipe operator was chosen because it is the one that offers the most tree-shakability whilst having a decent developer experience.

I think going back on the data immutability principle would not be wise but I do agree that the pipe operator is not an ideal first introduction to the library for developers. As you can see on the issue above though, other alternatives also have their own drawbacks.


CleanShot 2023-11-23 at 11 18 30@2x

lorisleiva avatar Nov 23 '23 11:11 lorisleiva

I think this is somewhere where the immutability has clear benefits that IMO outweigh the cost of an additional concept to learn.

The most important limitation here comes from TypeScript. Given an object that has eg feePayer?: Address, we have no way of writing a function that only accepts an instance of that object when feePayer is set. We're able to do that with the current approach because we can funnel you through a function that returns ITransactionWithFeePayer and track that type through the entire lifetime.

Because of this, if we allowed transactions to be mutable and for you to eg. just set the feePayer field, then all checks would become runtime checks, and all of our ability to detect mistakes at compile time would go away. I think the tradeoff here for maintaining apps is too great.

mcintyre94 avatar Nov 23 '23 11:11 mcintyre94

This team, does not endeavour to maintain helpers at a higher level than the ones offered by the core libraries, but anyone can build one now using the primitives offered by @solana/web3.js and @solana-program/system!

This is as concise as we wish to get with the core libraries, so that others can be opinionated about exactly when and how to bring together the pieces necessary (eg. a recent blockhash) for a landable transaction.

https://github.com/solana-labs/solana-web3.js/blob/master/examples/transfer-lamports/src/example.ts#L102-L155

steveluscher avatar Oct 21 '24 22:10 steveluscher

Because there has been no activity on this issue for 7 days since it was closed, it has been automatically locked. Please open a new issue if it requires a follow up.

github-actions[bot] avatar Oct 29 '24 08:10 github-actions[bot]