Custom Archway Contracts
If you want to use a custom contract (ie. embedding storage into your own contracts) you will need to follow a contract API that lets Jackal.js call the contracts interchangeably.
Here is a trimmed down version of our outpost factory
contract. This contract has two main functions, CreateOutpost
will create a new copy of the ICA controller contract.
And querying the state of this contract lets us figure out which controller contract to call to upload files.
Example Custom Contract
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{ContractState, STATE};
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
STATE.save(
deps.storage,
&ContractState::new(msg.storage_outpost_code_id, info.sender.to_string()),
)?;
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::CreateOutpost {
channel_open_init_options,
} => execute::create_outpost(deps, env, info, channel_open_init_options),
ExecuteMsg::MapUserOutpost { outpost_owner} => execute::map_user_outpost(deps, env, info, outpost_owner),
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetContractState {} => to_json_binary(&query::state(deps)?),
QueryMsg::GetUserOutpostAddress { user_address } => to_json_binary(&query::user_outpost_address(deps, user_address)?),
QueryMsg::GetAllUserOutpostAddresses { } => to_json_binary(&query::get_all_user_outpost_addresses(deps)?),
}
}
mod execute {
use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Uint128, Event, to_json_binary};
use storage_outpost::outpost_helpers::StorageOutpostContract;
use storage_outpost::types::msg::ExecuteMsg as IcaControllerExecuteMsg;
use storage_outpost::types::state::{CallbackCounter, ChannelState};
use storage_outpost::{
outpost_helpers::StorageOutpostCode,
types::msg::options::ChannelOpenInitOptions,
};
use storage_outpost::types::callback::Callback;
use serde_json_wasm::from_str;
use crate::state::{self, USER_ADDR_TO_OUTPOST_ADDR, LOCK};
use super::*;
pub fn create_outpost(
deps: DepsMut,
env: Env,
info: MessageInfo,
channel_open_init_options: ChannelOpenInitOptions,
) -> Result<Response, ContractError> {
let state = STATE.load(deps.storage)?;
let storage_outpost_code_id = StorageOutpostCode::new(state.storage_outpost_code_id);
if let Some(value) = USER_ADDR_TO_OUTPOST_ADDR.may_load(deps.storage, &info.sender.to_string())? {
return Err(ContractError::AlreadyCreated(value))
}
let _lock = LOCK.save(deps.storage, &info.sender.to_string(), &true);
let callback = Callback {
contract: env.contract.address.to_string(),
msg: None,
outpost_owner: info.sender.to_string(),
};
let instantiate_msg = storage_outpost::types::msg::InstantiateMsg {
owner: Some(info.sender.to_string()),
admin: Some(env.contract.address.to_string()),
channel_open_init_options: Some(channel_open_init_options),
callback: Some(callback),
};
let label
= format!("storage_outpost-owned by: {}", &info.sender.to_string());
let cosmos_msg = storage_outpost_code_id.instantiate(
instantiate_msg,
label,
Some(env.contract.address.to_string()),
)?;
let mut event = Event::new("FACTORY: create_ica_contract");
event = event.add_attribute("info.sender", &info.sender.to_string());
Ok(Response::new().add_message(cosmos_msg).add_event(event))
}
pub fn map_user_outpost(
deps: DepsMut,
env: Env,
info: MessageInfo,
outpost_owner: String,
) -> Result<Response, ContractError> {
let lock = LOCK.may_load(deps.storage, &outpost_owner)?;
if let Some(true) = lock {
LOCK.save(deps.storage, &outpost_owner, &false)?;
} else {
return Err(ContractError::MissingLock {})
}
USER_ADDR_TO_OUTPOST_ADDR.save(deps.storage, &outpost_owner, &info.sender.to_string())?;
let mut event = Event::new("FACTORY:map_user_outpost");
event = event.add_attribute("info.sender", &info.sender.to_string());
Ok(Response::new().add_event(event))
}
mod query {
use crate::state::{USER_ADDR_TO_OUTPOST_ADDR};
use super::*;
pub fn state(deps: Deps) -> StdResult<ContractState> {
STATE.load(deps.storage)
}
pub fn user_outpost_address(deps: Deps, user_address: String) -> StdResult<String> {
USER_ADDR_TO_OUTPOST_ADDR.load(deps.storage, &user_address)
}
pub fn get_all_user_outpost_addresses(deps: Deps) -> StdResult<Vec<(String, String)>> {
let mut all_entries = Vec::new();
let pairs = USER_ADDR_TO_OUTPOST_ADDR
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending);
for pair in pairs {
let (key, value) = pair?;
all_entries.push((key.to_string(), value));
}
Ok(all_entries)
}
}
}
Please refer to this GitHub repo for more information: https://github.com/JackalLabs/storage-outpost/blob/master/cross-contract/contracts/outpost-factory/src/contract.rs
Integrating with Jackal.js
When using a custom contract, you can make Jackal.js use it instead of the default Outpost Factory. It is as easy as adding a contract
field to the createWasmStorageHandler
function call.
myStorageHandler.createWasmStorageHandler({contract: 'YOUR_CONTRACT_ADDRESS_HERE'})