Contract
Loading contract code
This is different then deploying contracts on the network. Think of loading a contract as preparing its content to act as a blueprint for when we chose to make it live (hence its associated online type name, LiveContract
).
A contract can be loaded from 2 sources: either referencing a local file or by giving the contract's code directly. To load a contract one of the following methods can be used:
Contract.newFrom
- given either thepath
of the.sol
file or the actualcode
of it, retrieves a singleContract
instance. If there are multiple contracts defined, by default, the first one is retrieved. This can be overwritten to retrieve either the n-thindex
contract (by defaultindex=0
) or a contract byname
.Contract.allFrom
- same asContract.name
in all regards except for the fact that it does not take in anindex
nor a contractname
and retrieves an array of all the available contracts. When building aContract
instance, there's also aignoreWarnings
property which is, by default, set tofalse
that allows to bypass solidity's warnings when building the contract's code.
A thing to keep in mind here is the fact that, once a Contract
instance has been constructed, this is also a guarantee that the provided code was accepted by the solidity compiler. This is the reason why Contract
s have byteCode
property defined on it.
Deploying contracts
Once an ApiSession
and a Contract
is available, deploying the code on the network is as simple as doing an session.upload(contract)
method call. This returns a Promise<LiveContract>
so you might want await-ing for the result.
Transaction meta-arguments
Going into more depth with this method, if one wants to tweak the Hedera File Service - Create File Transaction step with extra-arguments, just pass in a second param to the upload
call which is a object of the form {_file: {...}}
containing any required options. For example, to attach a "Hello Venin" memo to the uploaded contract code, the resulting call would end up being: session.upload(contract, {_file: {fileMemo: "Hello Venin"}})
.
To pass Create Smart Contract Transaction parameters, have the second object parameter contain a property called _contract
with the same rationale in mind: set values that you wish to send to the ContractCreateTransaction
constructor. For instance, if you would like to set a gas
contract-creation limit to 100,000tℏ, your end upload
call would be: session.upload(contract, {_contract: {gas: 100_000}})
. By the way, the default gas
set for contract-creation transaction can be tweaked by the HEDERAS_DEFAULT_CONTRACT_TRANSACTION_GAS
environment variable and is currently set to 169_000
.
You can, of course, pass in both _file
and _contract
options. Merging the above 2 examples, session.upload(contract, {_contract: {gas: 100_000}, _file: {fileMemo: "Hello Venin"}})
would end up uploading a Contract.byteCode
to Hedera and have a memo attached to the resulting file called "Hello Venin". It would then set a gas
limit of 100,000tℏ to create the contract. A working example of this, could look as follows:
Constructor parameters
Passing in constructor parameters is easy, just add them when ApiSession.upload
-ing like so: session.upload(contract, arg1, arg2, ... argn)
. If you're going to have meta-arguments (see above) as well as constructor-args passed in, add the meta-args object first and then add whatever constructor arguments are desired.
Example:
This uploads a contract
with a gas
create-contract transaction set to 100,000tℏ and calling the contract
's constructor passing in the string Venin is amazing!
.
Interacting with deployed contracts
Calling methods
As you've probably seen so many times now, following a successful deployment, await-ing a ApiSession.upload
call returns a LiveContract
instance which has the solidity's contract functions dynamically attached to it and available for calling. This means that if a contract A
has a method foo
on it, the resulting LiveContract
will also have a function foo
defined on it.
So if, for example, we were to upload solidity-by-example's First App Contract via a session.upload
call, that will eventually resolve to a LiveContract
instance which would have a get
, an inc
and a dec
defined on it as one might expect.
Of course, function arguments are also supported so if we have such a live-contract function (solidity-by-example's State Variable code, for instance), you can call into these methods, passing in the expected values as expected.
When dealing with big numbers, the library uses the same one used by the Hedera SDK: bignumber.js. This is intentional since one of the core design principles of Venin's API is to try to mimic as close as possible Hedera's own SDK return types.
I say as close as possible to allow for specially thought-of exceptions backed-up by common sense reasoning where the benefit in doing things in another way would be greater then following 1-to-1 with Hedera's SDK.
One such exception has to do with returning bytes
from a method. While Hedera would just return a hex-encoded string of those bytes, as of v0.7.5
, Venin returns a managed bytes collection (eg Uint8Array
) of bytes.
Dealing with events
Contract events are propagated upwards from LiveContract
through the EventEmitter
-inspired methods. As such one can listen to an event by simply calling a .onEvent("event_name", () => { ... })
on the live-contract instance. Our test cases include solidity-by-example's Events code to make sure this works. Have a look for yourself, if interested, or check it out right now:
Logs can also be emitted from within contract constructors provided that either the HEDERAS_DEFAULT_EMIT_CONSTRUCTOR_LOGS
parameter is set to true or that emitConstructorLogs
meta-arg is set to true in the _contract
object when upload
-ing the contract.
To get access to the constructor logs, you would need to destructure the live-contract upload
result like so:
As you can see from running the above snippet, the resulting logs
are an array of objects which adhere to the following schema:
{
name: string,
payload: any
}
name
is the name of the event while payload
is a JS object with keys named after the arguments of the event and values being the actual data passed when emit-ing that particular event.
Transaction meta-arguments
Similar to when uploading a Smart Contract, calling any of its methods follows the same meta-arguments passing logic: if the first argument is a JS object which has certain properties of interest, those properties are unpacked and used inside the transaction. One such property is the maxQueryPayment
which makes for a good example: lets say that we would like to set a maximum query payment of 0.001ℏ for calling the solidity-by-example's State Variable > get method. In this case, you would simply do a liveContract.get({maxQueryPayment: 100000})
and it would suffice.
Of course, similar to the "upload contract operation" detailed above, any argument following the the meta-arguments object would be passed to the method itself. In this regards, using the same State Variable contract, doing a liveContract.set({maxTransactionFee: 100000}, 42)
would call the set
method passing in integer 42
as parameter and setting the maxTransactionFee
for the transaction to 100,000tℏ which is 0.001ℏ.
Retrieving deployed contracts
Uploading a Contract
is not the only way to get a hold on a deployed, LiveContract
instance. ApiSession
also exposes a getLiveContract
method which takes in a ContractId
as it's id
object param and the contract's ABI as its abi
parameter to lock onto a deployed version of that code on the network.
Want to find out more? Have a look at our test-case for an example on how one might go about doing just that or check it out yourself with a pre-uploaded testnet SimpleStorage
contract just for the sake of example:
The abi
type required for the getLiveContract
property can be a ethers
Interface
object or anything that can be parsed into one. For our above example we used a more human readable approach.
Deleting a live contract
To delete a deployed contract owned by the account associated with the current ApiSession
, just do a LiveContract.deleteEntity({ transferAccountId?: AccountId, transferContractId?: ContractId })
where you can optionally pass in a transferAccountId
or a transferContractId
to transfer the hbar present on the deleted account to either an AccountId
or a ContractId
. If nothing is specified, the owner of the current ApiSession
will get the remainder of the tokens.
Updating a live contract
Updating a deployed contract is not currently possible but will be supported in a future release.