Smart Contract
The architecture of Ownables follows that of a general CosmWasm contract. The only exception is that instead of deploying the contract on-chain and interacting with it that way, Ownable contracts are executed locally.
The wallet expects some methods from ownable-std
to be used.
Rust
msg.rs
Message file defines the expected interface for messages that are passed to our smart contract. You are free to introduce your own custom variants and fields for each message, but there are some macros provided by the ownable-std
crate.
Lets look at an example with all macros enabled:
Procedural macros like #[
ownables_instantiate_msg]
will introduce properties expected by the Ownable architecture. In the instantiation case, it would merge the InstantiateMsg
defined by you with all its properties with the following:
For the ExecuteMsg
case, having the #[ownables_transfer] and #[ownables_lock] will produce the following enum:
And for QueryMsg
, the following:
If you are curious about the properties/variants being introduced, feel free to take a look at ownable-std.
A few things are important to note that apply to all cases. First, ownable-std macro annotations should be declared above any derive and serde macros (as in the examples above).
Second, you are free to introduce any variants/fields that are necessary for your ownable.
Consider the following ExecuteMsg
:
After compiling, the ExecuteMsg
will look like the following:
contract.rs
In the contract file, we define the logic that will update and query the state of our contract.
The instantiate()
method should extract the necessary properties from its arguments and save them into the appropriate cw_storage_plus::Item
defined in state.rs
. If everything goes well, we return the Ok()
response with all the relevant properties that have been saved to the host wallet.
execute()
will contain any functions that the Ownable may contain. At the very least, having a transfer method is expected, but even that is technically optional if you want your Ownable to be non-transferable. Any logic surrounding that should be implemented by you. With the appropriate Item.update()
, the state will be updated to reflect the results of execution (if any).
Usually in your execute()
you will need to exhaustively match all variants available in ExecuteMsg
defined in our msg.rs
file. Given a transferable and lockable ownable, it may make sense to route the execution to respective methods:
try_transfer
and try_lock
are the methods where you will define your application logic related to transfering and locking the ownable.
With query()
method we query the contract state. Item.load()
will load the existing state variables, which you can then use to return the desired type (in the expected binary format). Considering our QueryMsg
definition above, our query entry point may look like this:
Once again, logic in methods like query_ownable_info
is entirely up to you. In general they will involve loading the appropriate state variables, building your return object, and converting it to StdResult<Binary>
.
state.rs
We use the state to define what properties our Ownables should have.
In most cases you will only be required to define your own Config
struct containing any properties specific to your design.
Other than that, you should store the following properties to allow your Ownable to be bridged, have metadata (defined in the CW721 spec), and to have the unlockable content:
Properties like OwnableInfo
, Metadata
, and NFT
should be imported from the ownable-std
package.
lib.rs
lib.rs
defines the entry point into our contract logic. It accepts and produces JSON (JsValue
or JsError
), which the wallet/dApp expects and interprets.
In turn, lib
methods will call the respective contract.rs
methods that contain the actual execution logic.
The code here should not change, as the wallet expects the functions to have the parameters and return values as they are defined.
Last updated