vechain-sdk-js
vechain-sdk-js copied to clipboard
💡 [REQUEST] - single function helper for transaction generation
Summary
Building a transaction is a multi-step process that can be confusing when getting started with the SDK.
It requires to:
- build clauses
- estimateGas
- buildTransactionBody
- signTransaction
- sendTransaction
For example:
const senderAccount = {
privateKey:
'f9fc826b63a35413541d92d2bfb6661128cd5075fcdca583446d20c59994ba26',
address: '0x7a28e7361fd10f4f058f9fefc77544349ecff5d6',
};
const clauses = [
clauseBuilder.functionInteraction(CONTRACT_ADDRESS, 'increment()'),
];
const gasResult = await thor.gas.estimateGas(clauses, senderAccount.address);
const tx = await thor.transactions.buildTransactionBody(
clauses,
gasResult.totalGas,
{
isDelegated: true,
}
);
const signedTx = await thor.transactions.signTransaction(
tx,
senderAccount.privateKey,
{
delegatorUrl: 'https://sponsor-testnet.vechain.energy/by/90',
}
);
const sendTransactionResult = await thor.transactions.sendTransaction(signedTx);
Basic Example
This can be simplified with a single function that combines all previous steps into one:
const signedTx = await thor.transactions.combinedSignedAndSendTransaction(
[
clauseBuilder.functionInteraction(CONTRACT_ADDRESS, 'increment()')
],
senderAccount.privateKey,
{
delegatorUrl: 'https://sponsor-testnet.vechain.energy/by/90',
}
);
this could internally:
- estimate glas within the function, remove the burden from user
- build the body from the provided clauses, if delegation is used, detect it by the use of delegation setting in the options object
- sign it
- send it
- return the transaction id
additionally the clauses could have fragment support and automatically call functionInteraction
or deployContract
in certain situations, removing the need to manually use the clauseBuilder
as well.
Example clauses:
[
{
to: '0x..',
fragment: 'increment()',
args: []
},
{
data: '0x..bytecode-to-deploy'
},
clauseBuilder.transferToken(..),
"etc."
]
Hi @ifavo , the SDK can send that same transaction using the following code:
const contract = thor.contracts.load(
CONTRACT_ADDRESS,
CONTRACT_ABI,
senderPrivateKey
);
await contract.transact.increment();
Maybe the missing pieces here are the multi clauses support and the delegator url, which we will add.
What do you think of this solution?
@Valazan I am happy to learn about contracts
module, looks very nice and I will give it a try!
It looks like it will work like an ethers contract instance, is this what's been used under the hood?
As you mentioned, multi-clause and all the other regular transaction options like transaction dependency, expiration or delegation are not available with that call.
@ifavo the design is inspired by ethers but under the hood, it's basically doing what you mentioned above.
Yes, I'll add an issue for the delegator (we had already one for the multi clauses).
Yes, give it a try! every feedback is appreciated
@Valazan I just had my first test with it, it works a bit different than ethers
. The main difference is that I expected it to have the functions as root attribute (contract.name()
instead of (contract.read.name()
).
I just found in my tests that I also miss to pass in a revision, which is possible with executeContractCall
. Should I create a new issue for this suggestion or is this not going to be supported? I understand that I can use setContractReadOptions
, which needs to be reset manually when used only once.
Is there a possibility to base this functionality on the ethers implementation? It could lower the code amount to maintain and increase compatibility with widely used standards by also using already imported libraries.
Hi @ifavo, we have decided to call the functionalities with read and transact before to keep the types returned predictable. A transact call returns the transaction id, while a read call will return an ethers.result. Also the contract object itself has its own methods with returned types. Defining dynamic functions in the root, means also to make the return type uncertain. It should be just a small change of behaviour for ethers users, they have just to remember to put read/transact before the call.
As per the proposal to add some custom parameters (like the revision) to the call, for sure!
We can pass an object into the args. ( e.g. { revision: value } ), what do you think? I'll open a task.
I understand, it also adds a bit more friction to developers coming from somewhere else or trying to re-use existing code from other projects that rely on ethers. Its one of my regular struggles, that I normally can not simple re-use existing open source projects because it requires customization to work with vechain tooling. It can still be solved by adding a wrapper/proxy that creates a ether compatible version.
I think passing the object in the args is a good and simple solution, if there is an additional parameter that satisfies the options, it will be interpreted as options :-)
I understand, it also adds a bit more friction to developers coming from somewhere else or trying to re-use existing code from other projects that rely on ethers. Its one of my regular struggles, that I normally can not simple re-use existing open source projects because it requires customization to work with vechain tooling. It can still be solved by adding a wrapper/proxy that creates a ether compatible version.
@Valazan 💡 would it be possible to get an ethers BaseContract instance from the contract loader, maybe something like this:
const vtho = thor.contracts.load('0x0000000000000000000000000000456e65726779', abi);
vtho.getEthersInstance()
// – or –
const vtho = thor.contracts.loadWithEthers('0x0000000000000000000000000000456e65726779', abi);
@ifavo yes, I'll speak with the team to see if we can add to the roadmap the ethers-like loader you proposed for the contracts.
Due an update of the SDK I needed to update the transaction sending, its becoming more complex now and a single function is more needed now I believe.
The current simplest example is now:
import {
ThorClient,
VeChainProvider,
ProviderInternalBaseWallet,
signerUtils,
} from '@vechain/sdk-network';
import {
clauseBuilder,
TransactionHandler,
} from '@vechain/sdk-core';
// Sender
const senderAccount = {
privateKey:
'f9fc826b63a35413541d92d2bfb6661128cd5075fcdca583446d20c59994ba26',
address: '0x7a28e7361fd10f4f058f9fefc77544349ecff5d6',
};
// Clause to execute
const clauses = [
clauseBuilder.functionInteraction(
'0x8384738c995d49c5b692560ae688fc8b51af1059',
'increment()'
),
];
// 1. create thor client
const thor = ThorClient.fromUrl('https://testnet.vechain.org');
// 2. Estimate Gas Fee
const gasResult = await thor.gas.estimateGas(clauses, senderAccount.address);
// 3. build transaction
const txBody = await thor.transactions.buildTransactionBody(
clauses,
gasResult.totalGas,
{
isDelegated: true,
}
);
/*
// SDK <beta.3
const signedTx = await thor.transactions.signTransaction(
txBody,
senderAccount.privateKey,
{
delegatorUrl: 'https://sponsor-testnet.vechain.energy/by/90',
}
);
*/
// SDK >=beta.15
// 4. create wallet
const wallet = new ProviderInternalBaseWallet([senderAccount], {
delegator: {
delegatorUrl: 'https://sponsor-testnet.vechain.energy/by/90',
},
});
// 5. create provider with thor client + wallet + delegation enabled
const providerWithDelegationEnabled = new VeChainProvider(thor, wallet, true);
// 6. get wallet through provider (wallet.getSigner() does not work)
const signer = await providerWithDelegationEnabled.getSigner(
senderAccount.address
);
// 7. convert tx to request input(?)
const txInput = signerUtils.transactionBodyToTransactionRequestInput(
txBody,
senderAccount.address
);
// 8. sign tx
const rawDelegateSigned = await signer.signTransaction(txInput);
// 9. restore transaction from hex signed transaction
const signedTx = TransactionHandler.decode(
Buffer.from(rawDelegateSigned.slice(2), 'hex'),
true
);
// 10. send transaction
const sendTransactionResult = await thor.transactions.sendTransaction(signedTx);
@ifavo I'm closing this task since the contract module has received a lot of improvements, including the type inference with abitype and the building of clauses for multiclauses transactions.
I think that implementing a contract class that extends the ethers BaseContract will add little benefits ( and also will not support VechainThor unique features )
What do you think?
@Valazan Can you share an example on the improvement?
I believe the optimal experience is a variation of this flow:
/* setup env */
const thorClient = ThorClient.fromUrl('https://testnet.vechain.org');
const privateKey = '0x..'
/* prepare */
const clauses = []
const options = { /* toggle fee delegation, tx comment, dependency, configure privateKey, etc. */ }
/* execute in one call */
const tx = await thorClient.sendTransaction(clauses, options)
/* wait & track */
await tx.wait()
for comparison, the way the thor-devkit works:
/* setup env */
const NODE_URL = 'https://..'
const privateKey = Buffer.from(..)
/ * prepare */
const clauses = []
const options = { /* toggle fee delegation, tx comment, dependency, configure privateKey, etc. */ }
/* execute */
const transaction = new Transaction({
clauses,
...options
})
transaction.signature = secp256k1.sign(transaction.signingHash(), privateKey)
const { id } = await post(NODE_URL, { raw: `0x${transaction.encode().toString('hex')}` })
/* wait & track */
// ..
I browsed through the code and do you suggest the use ofexecuteMultipleClausesTransaction
?
It is great and very close, but I believe it requires two adjustments to be most useful:
- transaction options
- support raw clauses
/* setup env */
const thor = ThorClient.fromUrl('https://testnet.vechain.org');
const randomSigner = new VeChainPrivateKeySigner(secp256k1.generatePrivateKey(), thor);
/* prepare */
const clauses = [
clauseBuilder.functionInteraction(
'0x8384738c995d49c5b692560ae688fc8b51af1059',
'increment()'
),
];
const options = { /* toggle fee delegation, tx comment, dependency, etc. */ }
/* execute */
const tx = await thor.contracts.executeMultipleClausesTransaction(clauses, randomSigner, /* pass options here? */ );
/* wait & track */
await tx.wait()
@ifavo nice suggestions, I've opened a specific task (https://github.com/vechain/vechain-sdk-js/issues/1003). In the meantime I'm closing this one, since it has been opened for a long time with different topics.