[@kadena/client] Redefine the interface of @kadena/client in a document (separate code generation and usage of PactCommand and ICommandBuilder)
Closed this issue · 2 comments
The way @kadena/client
was designed is to use Pact.modules.<module>.<defun>(args)
. However, we can also use it as just a transaction builder, and customize the PactCommand.code
property. Therefore, it would be good to separate the PactCommand
-builder from the code-generated version.
Topics to consider:
- should
PactCommand
be in a separate library - should we include the "default"
coin
caps in the PactCommand so thatPactCommand.addCap
will be strongly typed for thecoin
module
Proposal to change this issue to refactor/rewrite CommandBuilder to Support Functional Implementation
Currently, we use a builder pattern (CommandBuilder
) to create transactions in kadena/client. However, we want to separate the code generation (Pact.modules.<module>.<defun>
) from the CommandBuilder
functions (setData
, addCap
, and setMeta
) to introduce a more functional approach. This will improve the usuability of the library and allows us to create an implementation that can create transactions with more than one statement.
We propose a two-step approach:
Step 1: Version 1.0.0
We will implement the builder pattern in version 1.0.0
and rewrite the current implementation to allow the creation of multiple pact-expressions
in one transaction. We will keep the interface of Pact.modules.coin.transfer().addCap.addMeta.addSigners
intact but make it compatible with the functional approach.
Step 2: Version 1.1.0
In version 1.1.0
, we will extract all the functions from the builder and allow users to interact with them individually. We will also refactor the builder into an abstraction on top of the separated functions. Both the builder pattern and functional pattern will be available for users to consume.
With these changes, users will have the flexibility to choose between the builder pattern or functional pattern to create transactions for Kadena blockchain. We believe that this refactoring will make the code more modular, maintainable, and extendable in the long run.
A new idea is proposed to separate the functional part into:
- a separate package (e.g. @kadena/client-fp) or
- as a separate export from @kadena/client (e.g.
import { fp } from '@kadena/client'
).
Creating a transaction is a 3 step process before it can be send to the blockchain:
- pact code -> getTransactionBuilder
- transaction-builder -> finalizeTransaction
- signed transaction
-> sign() (side-effect free promise)
-> send/local/poll/pollUntil (side-effect free promises)
The interface for 1.0.0 as mentioned above should be able to support the following.
const expr = Pact.modules.coin.transfer("from", "to", amount) // pact expression generator
const builder = createCustomTransactionBuilder(`(coin.transfer)`) // (creates untyped `builder`)
const transaction = createTransaction(pactExpressions, pe2, pe3) // creates typed transaction with
// { "payload": { "exec": { "code": "(coin.transfer "from" "to" 10.0)" }, "data": "" }, "signers": [], "meta": {}, "nonce": "" }
const transaction = transaction // returns only the transaction with the functions on prototype
// function returns a clone
.addCap() // based on expressions' required caps
.setData() // replaces the data
.setMeta()
/*
transaction.payload
transaction.addCap('coin.GAS', sender)
{
"payload": { "exec": { "data": null, "code": "(+ 1 2)" } },
"signers": [
{
"pubKey": "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca",
"caps": [{ name: "coin.GAS", args: [] }]
}
],
"meta": {
"gasLimit": 1000,
"chainId": "0",
"gasPrice": 1.0e-2,
"sender": "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca"
},
"nonce": "nonce-value"
}
*/
const tx = builder.createTransaction() //
/* {
"hash": "H6XjdPHzMai2HLa3_yVkXfkFYMgA0bGfsB0kOsHAMuI",
"sigs": [{}],
"cmd": "{\"payload\":{\"exec\":{\"data\":null,\"code\":\"(+ 1 2)\"}},\"signers\":[{\"pubKey\":\"368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca\"}],\"meta\":{\"gasLimit\":1000,\"chainId\":\"0\",\"gasPrice\":1.0e-2,\"sender\":\"368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca\"},\"nonce\":\"nonce-value\"}" }
*/
// further operations are done using isolated functions
const partiallyOrFullySignedTx = signWithX(tx)
// signWithX will be able to use a util function
// to do `addSignatures` on the vanilla transaction
const sentTransaction = await send(partiallyOrFullySignedTx)
const sentTransactionFinished = await sendAndPollUntil(partiallyOrFullySignedTx)
const confirmedTransaction = await pollUntil(partiallyOrFullySignedTx /*| sentTransaction*/)
// pollUntil should work with two types.
// - One uses the hash, the other the requestKey