Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

it should be possible to create a transaction using plain objects #1693

Closed
mikemaccana opened this issue Oct 6, 2023 · 5 comments
Closed
Labels
enhancement New feature or request

Comments

@mikemaccana
Copy link
Contributor

mikemaccana commented Oct 6, 2023

Motivation

Piping and method chaining are not common approach to configuration in JS SDKs.

  • JS doesn't include an inbuilt pipeline operator, and the proposal to add one has been in draft since 2015, and is still in stage 2, which means it is still a draft and subject to change. Since pipelining isn't part of JS/TS, less people are familiar with the concept than with other languages. Many JS/TS front end developers do not have experience with languages (eg Elixir) where pipelinign is more popular.

  • Well regarded APIs like node.js, Svelte, React, Stripe, and Twilio use plain JS Object / TS Records to do configuration, rather than having end users use pipelining, method chaining, or similar.

It should be possible to create a transaction using Plain Old JavaScript Objects:

Before

// Create a transaction.
const transaction = pipe(
  createTransaction({ version: 0 }),
  (transaction) =>
    appendTransactionInstruction(
      getTransferInstruction(
        publicKey,
        destinationAddress,
        lamports(10_000_000n)
      ),
      transaction
    ),
  (transaction) => setTransactionFeePayer(publicKey, transaction),
  (transaction) =>
    setTransactionLifetimeUsingBlockhash(latestBlockhash, transaction)
);

Proposing

// Create a transaction.
const transaction = createTransaction({ 
  version: 0 
  instructions: [
    createInstruction(
      senderAddress,
      destinationAddress,
      lamports(10_000_000n)
    )
  ]
  feePayer: senderAddress,
  latestBlockhash
})

Example use case

A JS user, familiar with common JS practices of specifying config as a plain JS Object or TS Record<string, unknown> makes a transaction with createTransaction(). They know transactions have a series of instructions, so look for the instructions key find it. They add an array instructions as the value.

@mikemaccana mikemaccana added the enhancement New feature or request label Oct 6, 2023
@buffalojoec
Copy link
Contributor

I guess it would be totally possible to just use pipe under the hood here, but the resulting object would still be frozen like we're doing with each of those modular transaction modifiers.

We could roll something along these lines:

export function createTransaction(config: {
   feePayer?: Base58EncodedAddress,
   lifetime?: BlockhashLifetime | DurableNonceLifetime,
   instructions?: TransactionInstruction[],
   version: 'legacy' | 0,
}): Transaction | Transaction & TransactionWithFeePayer | Transaction & TransactionWithFeePayer & TransactionWithBlockhashLifetime {
   if 'feePayer' in config {
      pipe(
         createTransactionInner({ version: config.version }),
         tx => setTransactionFeePayer(config.feePayer, tx),
      )
   } else if {
      /* all the rest */
   }
}

@mikemaccana mikemaccana changed the title it should be possible to create a transaction using plain objects, without pipelining. it should be possible to create a transaction using plain objects Oct 6, 2023
@mikemaccana
Copy link
Contributor Author

mikemaccana commented Nov 22, 2023

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

The intention is that the result not be frozen - rather that the transaction is undateable using standard JS mechanisms (object and array accessors) until the moment the transaction is sent.

Signing, building a JSON-RPC body and sending would be handled as an atomic operation after the developer considers the transaction 'complete', ie, so the signature always matches the transaction body. For example a developer could add a instruction by using array methods they already know, or set values inside the transaction using object.key accessors.

When the developer wants to sign and send the transaction, they run const txID = await signAndSendTransaction(transaction) and:

  • a recent blockhash is added, if needed
  • a transaction version is added, if not specified
  • the transaction is signed with the private keys listed
  • the resulting JSON RPC body is built
  • the JSON RPC body is sent to the RPC server and confirmed

@buffalojoec
Copy link
Contributor

buffalojoec commented Nov 22, 2023

Thanks for circling back @mikemaccana, I think @lorisleiva has just the thing for you on this.
You can see glimpse of how this is going to look with the new signers API in #1792.

TL/DR: If you want short-hand, you'll do something like createDefaultTransaction(instructions) where instructions have actual signers in them as IAccountMeta, and then you'll signSendAndConfirmTransaction(..).

So it's coming along nicely!

@steveluscher
Copy link
Contributor

steveluscher commented Nov 12, 2024

Y'all are invited to publish helper packages that reconfigure the API like this. One of the good things about the functional API we've created is that it's flexible to the degree where it allows for this.

Be aware though, that:

  1. Your type system is going to be a nightmare. Our functional approach allows you to encapsulate a single mutation, and define the input/output types one ‘layer’ at a time. Your all-in API will either be type-unsafe, or have a combinatorial explosion of method overloads (ie. think of how you would define the method signature of createTransaction() to handle both nonce and blockhash-based transactions, multiplied by every transaction version).
  2. Your createTransaction() method will result in reduced package efficiency. It will have to contain code to handle multiple scenarios, some of which will go unused. Imagine supplying a blockhash when creating a transaction; all the nonce code will go unused, despite having been packaged in the function. Our functional approach means you get exactly the code that you need, and no more.

@steveluscher steveluscher closed this as not planned Won't fix, can't repro, duplicate, stale Nov 12, 2024
Copy link
Contributor

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 github-actions bot locked as resolved and limited conversation to collaborators Nov 20, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants