Skip to content

How to use Polytone

Art3miX edited this page Apr 19, 2023 · 1 revision

General

In Polytone we have 2 chains and 3 contracts

  • ChainA - the controller chain, the chain which controls accounts on ChainB
  • ChainB - the host chain, the chain that host accounts that are controlled by ChainA

Contracts:

  • Note - Deployed on ChainA to handle calls to ChainB
  • Voice - Deployed on chainB to handle calls from ChainA
  • Proxy - Deployed on chainB, is an account instantiated by the voice and only controlled by a single wallet from chainA

Upload

  • ChainA - upload Note
  • ChainB - Upload Voice and Proxy

Instantiate

  • ChainA - Instantiate Note contract
  • ChainB - Instantiate Voice contract (and pass Proxy code id)

Instantiate Controlled Note

A controlled Note allows instantiating a Note which can only be controlled by a single entity, this allows outposts to instantiate their own private Note and execute calls on behalf of its users.

To instantiate a controlled Note, pass an address in the controller parameter when instantiating a Note.

Open channel

Open a channel between your Note and Voice contracts, make sure the version is correct.

From ChainA execute on ChainB

You can execute a msg on Note on ChainA from any wallet you want (remember that any address on ChainA creates a 1 of 1 unique account on ChainB)

'{ "execute" : { "msgs" : [], callback: null, "timeout_seconds" : "600", "on_behalf_of" : null }}'
Execute {
    msgs: Vec<CosmosMsg<Empty>>,
    callback: Option<CallbackRequest>,
    timeout_seconds: Uint64,
    on_behalf_of: Option<String>, // only used on controlled Note
}

msgs: Vec<CosmosMsg<Empty>> - Array of cosmos messages to be executed on ChainB.

callback: Option<CallbackRequest>, - A callback to be executed with the responses.
receiver - contract address to execute the callback on
msg - your custom message to send with the callback

pub struct CallbackRequest {
    pub receiver: String,
    pub msg: Binary,
}

timeout_seconds: Uint64 - timeout in seconds for IBC packet.

on_behalf_of: Option<String> - Only used on controlled Note, to allow the controller to specify the original caller, and execute messages on his behalf on ChainB.

From ChainA query ChainB

Query {
    msgs: Vec<QueryRequest<Empty>>,
    callback: CallbackRequest,
    timeout_seconds: Uint64,
},

msgs: Vec<QueryRequest<Empty>> - queries to execute on ChainB

callback: CallbackRequest - callback to execute with the queries responses.

timeout_seconds: Uint64 - timeout in seconds for IBC packet.

Callbacks

When we execute or query something on ChainB, we usually want to do something with the response we get, this is what callbacks are for.

CallbackRequest has 2 fields needed for a callback:

  • receiver - contract address to execute the callback message
  • msg - a custom msg you want to pass to the callback.

Here is the callback format we will send:

pub struct CallbackMessage {
    pub initiator: Addr,
    pub initiator_msg: Binary,
    pub result: Callback,
}

initiator - the original caller (in case of controlled note, this field will be the on_behalf_of field pass from the controller) initiator_msg - the custom msg sent from the caller result - the result

The sent execute message will look like this:

// '{ "callback": { "initiator": ..., "initiator_msg": ..., "result": ... }}'
ExecuteMsg::Callback( CallbackMessage { initiator, initiator_msg, result } )  => { ... }

Callback result

The result in the callback will look like this:

pub enum Callback {
    Query(Result<Vec<Binary>, ErrorResponse>),
    Execute(Result<ExecutionResponse, String>),
    FatalError(String),
}

FatalError - will be returned when something bad happened, like out of gas, or timeout.

Query - will be returned if original message was Query, if successful a vector of query responses will be returned in Binary format, if any query errored, a ErrorResponse will be return with the index of the errored query, and the error msg

pub struct ErrorResponse {
    pub message_index: Uint64,
    pub error: String,
}

Execute - will be returned if original message was Execute, if any execute message errored, you will get the error string, if successful, you will get ExecutionResponse which contains the address on ChainB that executed the message (the account address for the original sender), and the result which is a vector of SubMsgResponse (same struct returned to you in reply if you executed a subMsg)

pub struct ExecutionResponse {
    pub executed_by: String,
    pub result: Vec<SubMsgResponse>,
}
  • All returned vector indexes in result correspond to the original messages vector sent.

Accounts

Sometimes you will need the account address on ChainB before executing something on Note ChainA, the accounts are created on first execute message sent on the Note, there are 2 ways to receive the account address on ChainB from Note ChainA:

  1. Query Note contract for the address: RemoteAddress { local_address: String }, if the response is empty or null, there is no address associated to the requested address, if the response is not empty, the returned string is the account address on ChainB.
  2. In callback as part of the result, you get ExecutionResponse which contains the executed_by field which contains the account address on ChainB.