I am currently exploring ways to streamline the process of interacting with contracts on the Pallet Contracts. One challenge I've encountered is the need to manually write wrapper code for contracts based on their metadata if the source code is not available.
I'm curious if there are any existing tools or libraries available that can automate this task. Specifically, I'm looking for a tool that can take the contract metadata file as input and generate the corresponding wrapper ink source code.
To illustrate, here's an example of what I envision for a Flipper Contract:
Based on this metadata:
{
"source": {
"hash": "0xc60ae144a3574f51326d9613fd41aeeee3b903c0f76490a8cfb172026f15ec2a",
"language": "ink! 5.0.0-rc.1",
"compiler": "rustc 1.76.0",
"build_info": {
"build_mode": "Debug",
"cargo_contract_version": "3.2.0",
"rust_toolchain": "stable-aarch64-apple-darwin",
"wasm_opt_settings": {
"keep_debug_symbols": false,
"optimization_passes": "Z"
}
}
},
"contract": {
"name": "flipper",
"version": "0.1.0",
"authors": ["[your_name] <[your_email]>"]
},
"spec": {
"constructors": [
{
"args": [
{
"label": "init_value",
"type": {
"displayName": ["bool"],
"type": 0
}
}
],
"default": false,
"docs": [
"Constructor that initializes the `bool` value to the given `init_value`."
],
"label": "new",
"payable": false,
"returnType": {
"displayName": ["ink_primitives", "ConstructorResult"],
"type": 2
},
"selector": "0x9bae9d5e"
},
{
"args": [],
"default": false,
"docs": [
"Constructor that initializes the `bool` value to `false`.",
"",
"Constructors can delegate to other constructors."
],
"label": "default",
"payable": false,
"returnType": {
"displayName": ["ink_primitives", "ConstructorResult"],
"type": 2
},
"selector": "0xed4b9d1b"
}
],
"docs": [],
"environment": {
"accountId": {
"displayName": ["AccountId"],
"type": 6
},
"balance": {
"displayName": ["Balance"],
"type": 9
},
"blockNumber": {
"displayName": ["BlockNumber"],
"type": 12
},
"chainExtension": {
"displayName": ["ChainExtension"],
"type": 13
},
"hash": {
"displayName": ["Hash"],
"type": 10
},
"maxEventTopics": 4,
"staticBufferSize": 16384,
"timestamp": {
"displayName": ["Timestamp"],
"type": 11
}
},
"events": [],
"lang_error": {
"displayName": ["ink", "LangError"],
"type": 4
},
"messages": [
{
"args": [],
"default": false,
"docs": [
" A message that can be called on instantiated contracts.",
" This one flips the value of the stored `bool` from `true`",
" to `false` and vice versa."
],
"label": "flip",
"mutates": true,
"payable": false,
"returnType": {
"displayName": ["ink", "MessageResult"],
"type": 2
},
"selector": "0x633aa551"
},
{
"args": [],
"default": false,
"docs": [" Simply returns the current value of our `bool`."],
"label": "get",
"mutates": false,
"payable": false,
"returnType": {
"displayName": ["ink", "MessageResult"],
"type": 5
},
"selector": "0x2f865bd9"
}
]
},
"storage": {
"root": {
"layout": {
"struct": {
"fields": [
{
"layout": {
"leaf": {
"key": "0x00000000",
"ty": 0
}
},
"name": "value"
}
],
"name": "Flipper"
}
},
"root_key": "0x00000000",
"ty": 1
}
},
"types": [
{
"id": 0,
"type": {
"def": {
"primitive": "bool"
}
}
},
{
"id": 1,
"type": {
"def": {
"composite": {
"fields": [
{
"name": "value",
"type": 0,
"typeName": "<bool as::ink::storage::traits::AutoStorableHint<::ink::storage\n::traits::ManualKey<2054318728u32, ()>,>>::Type"
}
]
}
},
"path": ["flipper", "flipper", "Flipper"]
}
},
{
"id": 2,
"type": {
"def": {
"variant": {
"variants": [
{
"fields": [
{
"type": 3
}
],
"index": 0,
"name": "Ok"
},
{
"fields": [
{
"type": 4
}
],
"index": 1,
"name": "Err"
}
]
}
},
"params": [
{
"name": "T",
"type": 3
},
{
"name": "E",
"type": 4
}
],
"path": ["Result"]
}
},
{
"id": 3,
"type": {
"def": {
"tuple": []
}
}
},
{
"id": 4,
"type": {
"def": {
"variant": {
"variants": [
{
"index": 1,
"name": "CouldNotReadInput"
}
]
}
},
"path": ["ink_primitives", "LangError"]
}
},
{
"id": 5,
"type": {
"def": {
"variant": {
"variants": [
{
"fields": [
{
"type": 0
}
],
"index": 0,
"name": "Ok"
},
{
"fields": [
{
"type": 4
}
],
"index": 1,
"name": "Err"
}
]
}
},
"params": [
{
"name": "T",
"type": 0
},
{
"name": "E",
"type": 4
}
],
"path": ["Result"]
}
},
{
"id": 6,
"type": {
"def": {
"composite": {
"fields": [
{
"type": 7,
"typeName": "[u8; 32]"
}
]
}
},
"path": ["ink_primitives", "types", "AccountId"]
}
},
{
"id": 7,
"type": {
"def": {
"array": {
"len": 32,
"type": 8
}
}
}
},
{
"id": 8,
"type": {
"def": {
"primitive": "u8"
}
}
},
{
"id": 9,
"type": {
"def": {
"primitive": "u128"
}
}
},
{
"id": 10,
"type": {
"def": {
"composite": {
"fields": [
{
"type": 7,
"typeName": "[u8; 32]"
}
]
}
},
"path": ["ink_primitives", "types", "Hash"]
}
},
{
"id": 11,
"type": {
"def": {
"primitive": "u64"
}
}
},
{
"id": 12,
"type": {
"def": {
"primitive": "u32"
}
}
},
{
"id": 13,
"type": {
"def": {
"variant": {}
},
"path": ["ink_env", "types", "NoChainExtension"]
}
}
],
"version": "4"
}
The tool should generate the following ink contract in order to be used by another contract:
#![cfg_attr(not(feature = "std"), no_std, no_main)]
pub use self::wrapper::WrapperRef;
#[ink::contract]
mod wrapper {
#[ink(storage)]
pub struct Wrapper {}
impl Flipper {
#[ink(constructor)]
pub fn new(init_value: Transaction) -> Self {
panic!()
}
#[ink(constructor)]
pub fn default() -> Self {
panic!()
}
#[ink(message)]
pub fn flip(&mut self) {
panic!()
}
#[ink(message)]
pub fn get(&self) -> bool {
panic!()
}
}
}
The message implementations within the generated wrapper contract can be placeholder code, simply ensuring compilation. This wrapper serves as an interface for other contracts to interact with the on-chain contract, abstracting its logic.
If anyone knows of such a tool or has developed something similar, I'd greatly appreciate any insights or recommendations you can provide.