Skip to main content
Version: Next

ICStaking Module

Overview

The ICStaking module facilitates liquid staking of native tokens for Cosmos-based chains. Users can mint LSD tokens ( cAsset) by staking their assets. This module batches staked assets from users and delegates them at defined intervals. It also compounds staking rewards automatically, staking these compounded rewards to maximize earnings. The ICStaking module depends on the Cosmos interchain accounts feature to execute staking-related operations on supported chains. It also allows staking on chain without ICA, using multi-sig trusted accounts.

Concepts

Host Chain

The Host Chain is a chain where native tokens earn rewards through staking. It holds the necessary data for staking assets and retrieving related information. The HostChain structure includes these elements:

  • Id: A unique identifier provided by the host chain creator (governance). Once established, this id cannot be changed. It's used in the cToken denom, where a host chain with an id "uatom" would be "c:uatom". Typically, the id value matches the BaseDenom field.
  • ConnectionType: Identifies the type of interchain connection with the host chain. The ConnectionType and ConnectionId fields are unique together. The supported connection types are:
    • ICA: The host chain is connected to PRYZM using the ICA app of the IBC protocol.
    • MULTI_SIG: The host chain is connected to PRYZM using multi-sig trusted accounts.
    • LOOP_BACK: The host chain is the PRYZM itself, and is used for liquid staking its own bond token.
  • ConnectionId: The connection identifier. For an ICA connection type, this is the ID for the IBC connection between PRYZM and the host chain, for a multi-sig connection, this is the ID of the associated MultiSigConnection, and for a loop-back connection, this is a static constant (connection-loopback). TheConnectionId and ConnectionType fields are unique together.
  • BaseDenom: The bond denomination of the host chain.
  • TransferChannels: A list of supported transfer channels for token transfers between the host chain and PRYZM. Each channel includes:
    • Type: The transfer channel protocol. The supported channel types are:
      • IBC: The channel is an IBC channel.
      • LOOP_BACK: The channel is used for transferring on the PRYZM chain itself, and replaces ibc transfer with bank send.
    • Id: The channel identifier. For IBC channel type, this is the ICS-20 channel ID.
    • WrappedDenom: An optional property representing the token name on the receiving chain. Useful when a bridge is used and the underlying asset is wrapped on the bridge, like axlWETH when the channel end is the Axelar network.
    • DestinationChain: An optional property specifying the target chain's name. Useful when a bridge is used and the host chain differs from the receiving chain.
  • Params: The staking parameters for the host chain. The ICStaking module defines default values for these parameters in the module params. These default values are overridden by the provided parameters for the host chain. The properties for this field are detailed in the Staking Parameters section.
  • AllowLsmShares: Whether this host chain allows users to stake their LSM shares minted on the host chain.
  • Validators: A list of whitelisted validators to whom PRYZM delegates the staked tokens. The tokens are distributed based on each validator's weight. Each validator has these properties:
    • Address: The validator's address on the host chain.
    • Weight: The weight of delegation to the validator. This is a decimal number between 0 and 1. The total weight of all validators per host chain must equal 1.
    • AllowLsmShares: Whether the LSM share tokens from this validator are allowed.

Host Chain State

The HostChainState is a data structure storing a portion of the host chain's state, necessary for PRYZM's operation. Each host chain has one HostChainState, created during the host chain's registration. The HostChainState structure contains the following information:

  • HostChainId: The ID of the host chain.
  • HostAccounts: Details about interchain accounts registered on the host chain by PRYZM. Each account includes the account's address, balance (reported by an oracle), and registration status on the host chain (NOT_REGISTERED, REGISTERING, REGISTERED). The following accounts are registered on the host chain:
  • Delegation: Initially receives transferred staked tokens. Interchain messages for delegation, undelegation, and redelegation are sent from this account.
  • Reward: Collects staking reward tokens. A portion of the rewards is transferred to the sweep account as protocol fee, with the remainder going to the delegation account for delegation. To collect rewards, the reward account must be set as the withdrawal account for the delegation account. The status of setting the reward account for accruing rewards is stored in the HostAccounts struct.
  • Sweep: Collects tokens to be transferred back to PRYZM, including protocol fee tokens and undelegated tokens. PRYZM sends appropriate IBC token transfer messages to the host chain on behalf of this account.
  • ValidatorStates: A list of validators and their state, including the delegation amount to each validator.
  • AmountToBeDelegated: Assets in the delegation interchain account ready for delegation. The sum of this value and UndelegatedAmountToCollect should equal the balance of the delegation interchain account unless there are accidental token transfers.
  • UndelegatedAmountToCollect: Undelegated assets in the delegation account awaiting collection. The sum of this value and AmountToBeDelegated should equal the balance of the delegation interchain account unless there are accidental token transfers.
  • LockedLsmValue: The value of LSM tokens (in terms of the host chain denom) that are in queue to either be transferred or redeemed on the host chain.
  • CollectedFee:The amount of fee taken from rewards that is collected and waiting to be transferred from host chain to the treasury.
  • ExchangeRate: Current cToken/Token exchange rate, calculated from oracle-reported data using the formula in the Oracle section.
  • State: Current state of the interchain operations state machine. Interchain operations require certain blocks to execute, and simultaneous operations may cause calculation conflicts. Operations begin only if no other operation is in progress. Possible state values include:
    • INITIALIZING: The host chain is initializing.
    • IDLE: No interchain operation is active; the host chain is ready for new operations.
    • TRANSFERRING: The host chain has sent a token transfer packet to the host chain and awaits acknowledgment.
    • DELEGATING: The host chain has sent delegation interchain messages and awaits acknowledgment.
    • UNDELEGATING: The host chain has sent undelegation interchain messages and awaits acknowledgment.
    • REDELEGATING: The host chain has sent redelegation interchain messages and awaits acknowledgment.
    • COMPOUNDING: The host chain has sent compounding interchain messages and awaits acknowledgment.
    • COLLECTING: The host chain has sent undelegation collection interchain messages and awaits acknowledgment.
    • SWEEPING: The host chain has sent sweeping interchain messages and awaits acknowledgment.
    • LSM_REDEEMING: The host chain has sent MsgRedeemTokensForShares interchain messages and awaits acknowledgment.
  • LastIdleStateHostHeight: The host chain's block height when the State changed to IDLE. The state becomes IDLE upon receiving an acknowledgment packet (or timeout) for an interchain message. This height is used when handling oracle reports to ensure that the oracle's data is more recent than the updates from the host chain's interchain message acknowledgments.

Undelegation

Users can unstake their assets by submitting their cToken. PRYZM then batches these requests and processes them periodically, each processing time referred to as an epoch. After the host chain's unbonding period, the undelegated assets are ready to be claimed. To claim these unbonded assets, they must be redeemed and transferred back to PRYZM.

Undelegating assets involves multiple steps and interchain messages. This process's state control requires information about the state stored on the chain, for which we use Undelegation records. Each host chain epoch has one Undelegation record. We also need ChannelUndelegation, which stores undelegation state information for each channel.

The Undelegation structure includes:

  • HostChain: Host chain ID.
  • Epoch: Undelegation epoch number.
  • ExchangeRate: The cToken/Token exchange rate at the undelegation epoch end.
  • Started: Indicates if the undelegation request has been launched on the host chain.
  • Completed: Indicates if the unbonding period has passed and the undelegated assets are available.
  • CompletionTime: The time when the undelegation completes and unbonded assets transfer to the delegation account.

The ChannelUndelegation structure includes:

  • HostChain: Host chain ID.
  • Epoch: Undelegation epoch number.
  • TransferChannel: The channel through which the undelegated assets should be received.
  • TotalCAmount: The total amount of cTokens to be undelegated.
  • UndelegatedCAmount: The total amount of cTokens that have been sent for undelegation.
  • ReceivedAmount: The amount of assets already undelegated and transferred to the redeem account.
  • PendingAmount: The amount of assets awaiting receipt.
  • PendingCAmount: The cToken equivalent of assets awaiting receipt.
  • Swept: Indicates if the IBC transfer messages for sweeping assets to PRYZM have been sent successfully.
  • Received: Indicates if all the undelegations have been fully received. If received is true, PendingAmount must be zero.
  • ClaimedUAmount: The amount of unstaked tokens redeemed by users. A channel undelegation record is deleted when ClaimedUAmount equals TotalCAmount.

Staking

Users can stake their base denom tokens from the host chain (transferred to PRYZM using IBC) and mint cTokens based on the cAsset/Asset exchange rate. The provided assets are stored in the delegation queue module account for future delegation. Users must also specify the channel through which the assets have been transferred to ensure proper unwinding when transferred back to the host chain on the same channel.

cAmount = amount / hostChainState.ExchangeRate
SendCoinsFromAccountToModule(userAddress, DelegationQueueAccount, amount)
MintCoinsForAccount(userAddress, hostChain.CDenom(), cAmount)

The delegation queue is processed periodically (refer to DelegationInterval), and the collected assets are transferred to the host chain via the delegation transfer bridge.

Staking Lsm Shares

Users can stake their current delegations on the host chain using LSM module; user can mint LSM tokens on the host chain, transfer it to PRYZM using IBC and mint cTokens based on the LSM shares to token ratio and cAsset/Asset exchange rate. The provided assets are instantly transferred to the host chain, and after the transfer acknowledgment, the tokens are queued to be redeemed to shares on the host chain. Users must also specify the channel through which the LSM tokens have been transferred to ensure proper unwinding when transferred back to the host chain on the same channel.

transferToHostChain(amount)
tokensForShares := validator.TokensFromShares(amount)
hostChainState.LockedLsmValue += tokensForShares

cAmount = tokensForShares / hostChainState.ExchangeRate
MintCoinsForAccount(userAddress, hostChain.CDenom(), cAmount)

Unstaking

Users can request to unstake their cAssets. In return, they receive an unstaking token of the underlying token for the epoch they submitted their requests, maintaining a 1:1 ratio with the cAssets. For example, a user unstaking 100 c:uatom on channel 0 in the 10th epoch will receive 100 u:uatom:channel-0:10.

SendCoinsFromAccountToModule(userAddress, UndelegationQueueAccount, cAmount)
undelegation = getCurrentChannelUndelegation(hostChainId, transferChannel)
MintCoinsForAccount(userAddress, hostChain.UDenom(undelegation), cAmount)
undelegation.TotalCAmount += cAmount

Following the conclusion of each epoch, a message encompassing all undelegated amounts is dispatched to the host chain, utilizing the exchange rate at the epoch's end time. Once the unbonding period on the host chain expires, the unstaked assets are transferred from the delegation account back to PRYZM, to a module account named the redeem account. Subsequently, users can redeem their unstaking tokens for the asset. The redemption rate, representing the exchange rate of the redeem, is derived from the amount received from the host chain.

undelegation := GetChannelUndelegation(hostChainId, epoch, transferChannel)
...[check that undelegation.Received is true]...
redemptionRate = undelegation.ReceivedAmount / undelegation.TotalCAmount
amount = uAmount * redemptionRate
SendCoinsFromModuleToAccount(RedeemAccount, userAddress, amount)
BurnCoins(userAddress, hostChain.UDenom(undelegation), uAmount)
undelegation.ClaimedUAmount += uAmount

Instant Unstaking

Users have the option to unstake their tokens instantly by exchanging their cAssets for assets in the delegation queue, incurring a fee. The delegation queue may be depleted or insufficient. Users can specify the range of assets they expect to receive: minCAmount and maxCAmount. If the delegation queue's available assets fall short of the minCAmount equivalent, the operation fails. The user can redeem any other amount up to the maxCAmount equivalent.

minAmount = minCAmount * hostChainState.ExchangeRate
maxAmount = maxCAmount * hostChainState.ExchangeRate
balance = getDelegationQueueBalance(hostChain, transferChannel)
amount = min(maxAmount, balance)
if amount > minAmount {
SendCoinsFromModuleToAccount(DelegationQueueAccount, userAddress, amount)
}

Netting-Off

Netting-off involves offsetting the delegation queue's asset amount with the asset amount requested for undelegation. This process streamlines asset flow between PRYZM and the host chain and facilitates users to redeem part of their unstaking tokens without the unbonding period wait. As the protocol expands and staking requests outnumber unstaking requests, undelegation never happens, allowing users to redeem their assets immediately after netting-off.

Each epoch concludes with a match between the delegation and undelegation queues. The queue holding fewer assets is emptied, and the opposite queue has the same amount canceled. The following code exemplifies this process:

// for each transfer channel
delegatingAmount = getDelegationQueueBalance(hostChain, transferChannel)
undelegation = getCurrentChannelUndelegation(hostChainId, transferChannel)
undelegatingCAmount = undelegation.TotalCAmount - undelegation.UndelegatedCAmount
undelegatingAmount := undelegatingCAmount * hostChainState.ExchangeRate

if undelegatingAmount >= delegatingAmount {
// if the undelegating amount is greater or equal than delegation,
// we want to truncate the delegation account and send it to redeem account
delegationSubtraction = delegatingAmount

// and then burn the corresponding cASSET amount from undelegation account
undelegationSubtraction = delegatingAmount / hostChainState.ExchangeRate
} else {
// if the undelegating amount is lower than delegation,
// we want to send undelegating amount from delegation account to redeem account
delegationSubtraction = undelegatingAmount

// and then burn all the assets in undelegation account
undelegationSubtraction = undelegatingCAmount
}

// send delegationSubtraction amount from delegation queue to redeem account to be redeemed by users
SendCoinsFromModuleToModule(DelegationQueueAccount, RedeemAccount, delegationSubtraction)

// burn undelegationSubtraction amount of cAssets from undelegation queue
BurnCoins(types.UndelegationQueueAccount, undelegationSubtraction)

// update channel undelegation record with the delegation subtraction as received amount of assets
undelegation.UndelegatedCAmount += undelegationSubtraction
undelegation.ReceivedAmount += delegationSubtraction

Bridge

Bridges implement a universal interface for interaction with host chains. They manage replies, errors, and communication timeouts from the host chain. Each bridge simplifies the communication process for the rest of the logic, enabling message sending to the host chain.

Every bridge has a unique ID for routing message execution results. Bridge implementers must set a ReplyData in the store to access message execution results. The ReplyData object includes the following attributes:

  • Key: The unique key for the reply data.
  • BridgeId: The ID of the bridge receiving the reply.
  • HostChainId: The ID of the host chain expected to reply.
  • Data: The marshalled data for the message reply, storing the context of asynchronous message execution.
  • PacketId: Deprecated. It used to be the ID of the packet linked with the reply data.

The module defines these bridge interfaces:

type MessageBridge interface {
GetID() string
MsgReply(ctx sdk.Context, replyData types.ReplyData, txMsgData *sdk.TxMsgData) error
MsgError(ctx sdk.Context, replyData types.ReplyData, error string) error
MsgTimeout(ctx sdk.Context, replyData types.ReplyData) error
}

type TransferBridge interface {
GetID() string
TransferReply(ctx sdk.Context, replyData types.ReplyData) error
TransferError(ctx sdk.Context, replyData types.ReplyData, error string) error
TransferTimeout(ctx sdk.Context, replyData types.ReplyData) error
}

Upon receiving transfer result, the icstaking handler checks for expected replies and directs them to the correct bridge method. If the host chain acknowledgment is successful, the MsgReply (or TransferReply) method is called with the reply data and the host chain response. If the acknowledgment contains an error, the MsgError (or TransferError) method is invoked with the error message. If no acknowledgment arrives before timeout, MsgTimeout (or TransferTimeout) handles it.

Bridge implementers can use the following abstract structs to leverage utility methods:

(b AbstractMsgBridge) SendMsgs(
ctx sdk.Context,
hostChain types.HostChain,
port string,
msgs []sdk.Msg,
contextData proto.Marshaler,
) error

(b AbstractTransferBridge) Transfer(
ctx sdk.Context,
hostChain types.HostChain,
channel types.TransferChannel,
from string,
to string,
coin sdk.Coin,
contextData proto.Marshaler,
) error

These methods abstract the interaction of the bridges with the host chain, and execute the proper action based the type of host chain's connection. If the connection type is ICA, the bridge sends ICA tx execution packet to the host chain and if the type is MULTI_SIG, the bridge uses MultiSigPacket to execute the messages.

The icstaking module permits other modules to register their bridges and interact with host chains. For example, the pgov module uses this feature to transmit proposal votes to the host chain.

The bridges in the icstaking module include:

  • Delegation Transfer Bridge
  • Delegation Bridge
  • Undelegation Bridge
  • Compound Bridge
  • Collect Undelegated Bridge
  • Sweep Bridge
  • Redelegation Bridge
  • Set Withdraw Address Bridge
  • Lsm Transfer Bridge
  • Lsm Bridge

Multi-Sig

Some chains do not have ICA integrated. This means that Pryzm cannot directly interact with those host chains. In such cases, the icstaking module uses multi-sig trusted accounts to execute the messages. The multi-sig trusted accounts are registered on the host chain and are used to execute the messages on behalf of Pryzm. Another trusted multi-sig account (operator) is also registered on the Pryzm to acknowledge the execution response of messages on the host chain. For sending messages, Pryzm stores a packet containing those messages on the chain, and an off-chain entity (the operator) loads the packet and sends them to the host chain. After the submission of the transaction on the host chain, the operator acknowledges the response of the transaction on the Pryzm chain.

Multi-Sig Connection

This struct store the information of a connection to a host chain and includes the following properties:

  • Id: The identifier of the connection. HostChain uses this field to associate a host chain to this connection.
  • Operator: The operator address of the multi-sig connection on Pryzm, allowed to acknowledge packets (this address must be a multi-sig to be secure).
  • LastSequence: The last packet sequence sent on this connection.
  • LatestHostHeight: The latest height of host chain known on Pryzm, the host chain block when the last packet is executed and Pryzm have got the acknowledgment.

Multi-Sig Packet

This struct store the information of a packet to be sent on the host chain. It is removed from the store when the operator acknowledges the execution of the packet. The packet includes the following properties:

  • ConnectionId: The identifier of the connection the packet is sent on.
  • Sequence: The sequence of the packet. This is used to preserve ordering in the execution of the packets.
  • Messages: The list of messages meant to be executed on the host chain.

Loop-Back

ICStaking module also supports liquid staking the bond token of PRYZM itself. This is done by creating a loop-back host chain. The loop-back host chain is a special host chain that represents PRYZM itself. In order to re-use the most of the logic that has been used for other chains, the loop-back host chain is created with a special connection type called LOOP_BACK. The messages that are meant to be executed on the loop-back host chain are stored in a packet and the packet is executed directly in the PRYZM's end blocker. The LoopBackPacket contains the following properties:

  • Id: A unique auto-generated id for the packet.
  • Data: The data of the packet. This data can be of one of the following types:
    • LoopBackMsgs: This type contains the messages that are going to be executed on the Pryzm.
    • LoopBackTransfer: This type contains a coin transfer information that is used to replace ibc transfers with a simple bank send.

Oracle

PRYZM employs the oracle module to access external information. The ICStaking module uses the oracle to refresh the host chain state. The oracle reports the following data to this module for each host chain:

  • BlockHeight: The oracle-reported block height for the host chain.
  • ValidatorStates: A list of validators and their states, including delegation amounts.
  • DelegationAccountBalance: The balance of the delegation interchain account.
  • RewardAccountBalance: The balance of the reward interchain account.
  • SweepAccountBalance: The balance of the sweep interchain account.
  • LastCompletedUndelegationEpoch: The highest undelegation epoch number for which the undelegation is complete and ready for sweeping to PRYZM.

Upon receiving this data from the oracle, PRYZM updates the host chain state when:

  1. The host chain state is IDLE to prevent conflict as the bridge functions are concurrently updating the state.
  2. The host chain block height, as reported by the oracle, is more recent than PRYZM's last known idle state block height (hostChainState.LastIdleStateHostHeight). This condition ensures the oracle's data is current, considering the oracle module's latency due to voting delay.

When these conditions are satisfied, the host chain state and cAsset/Asset exchange rate are updated using the formula:

ER=totalDelegation+delegationQueue+AmountToBeDelegated+LockedLsmValue+rewardAmounttotalCSupplyER = \frac{totalDelegation + delegationQueue + AmountToBeDelegated + LockedLsmValue + rewardAmount}{totalCSupply}

Here, totalDelegationtotalDelegation and rewardAmountrewardAmount are reported by oracle, delegationQueuedelegationQueue represents the amount of staked assets awaiting delegation, and totalCSupplytotalCSupply is the total supply of the cAsset.

Following the state update, if the reward account balance is positive, the compounding operation is executed. If the oracle indicates that some undelegation records are complete and redeemable, the state of the completed undelegations is updated, and the sweeping operation is performed.

Messages

MsgUpdateParams

This message updates the module parameters. Only the governance can execute this message.

message MsgUpdateParams {
string authority = 1;
Params params = 2 [(gogoproto.nullable) = false];
}

MsgRegisterHostChain

This message allows to register a new host chain on PRYZM. It can be executed by the governance module address (using a governance proposal) or one of the admin addresses defined in the module's parameters.

message MsgRegisterHostChain {
string creator = 1;
HostChain host_chain = 2 [(gogoproto.nullable) = false];
}

MsgUpdateHostChain

This message allows to update the whitelisted validators and staking parameters of a registered host chain. It can be executed by the governance module address(using a governance proposal) or one of the admin addresses defined in the module's parameters.

message MsgUpdateHostChain {
string creator = 1;
string host_chain_id = 2;
repeated Validator validators = 3;
StakingParams params = 4;
google.protobuf.BoolValue allow_lsm_shares = 5;
}

MsgStake

This message allows users to stake transferred assets, minting cTokens at the current host chain exchange rate. The response includes the amount of minted cTokens and the fee deducted from the assets.

message MsgStake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string amount = 4; // sdk.Int
}
message MsgStakeResponse {
cosmos.base.v1beta1.Coin c_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}

MsgStakeLsmShares

This message allows users to stake transferred LSM share tokens that has been minted on the host chain and mints cTokens based on the current host chain exchange rate and the ratio of LSM shares to tokens. The response includes the amount of minted cTokens and the fee deducted from them.

message MsgStakeLsmShares {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string lsm_denom = 4;
string amount = 5;
}
message MsgStakeLsmSharesResponse {
cosmos.base.v1beta1.Coin c_amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}

MsgUnstake

This message enables users to request unstaking of their cTokens and retrieval of unstaking tokens, specifying the channel for retrieving their unstaked assets after the unbonding period. The response includes the amount of minted unstaking tokens.

message MsgUnstake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string c_amount = 4; // sdk.Int
}
message MsgUnstakeResponse {
cosmos.base.v1beta1.Coin u_amount = 1 [(gogoproto.nullable) = false];
}

MsgRedeemUnstaked

This message allows users to submit their unstaking tokens upon completion of the token's epoch undelegation, and redeem their assets. The response includes the amount of redeemed tokens and the deducted fee.

message MsgRedeemUnstaked {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string u_amount = 4; // sdk.Int
uint64 epoch = 5;
}
message MsgRedeemUnstakedResponse {
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}

MsgInstantUnstake

This message allows users to instantly redeem their assets for given cTokens, within a specified acceptable range for the redeemed amount. The response includes the amount of redeemed assets and the deducted fee.

message MsgInstantUnstake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string min_c_amount = 4; // sdk.Int
string max_c_amount = 5; // sdk.Int
}
message MsgInstantUnstakeResponse {
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin fee = 2 [(gogoproto.nullable) = false];
}

MsgRebalanceDelegations

This message allows governance or module admins to request a rebalancing of delegations for host chain validators. The process of rebalancing is executed only if a specific duration has passed since the last rebalancing and the deviation from the expected weights of delegations is above a certain threshold.

message MsgRebalanceDelegations {
string creator = 1;
string host_chain = 2;
}

MsgRegisterInterchainAccount

This message permits users to register interchain accounts, particularly in instances of registration failures or when a timeout has occurred, resulting in closed ICA channels.

Request

message MsgRegisterInterchainAccount {
string creator = 1;
string host_chain = 2;
ICARegistrationType registration_type = 3;
}

enum ICARegistrationType {
DELEGATION = 0; // Register delegation interchain account
REWARD = 1; // Register reward interchain account
SWEEP = 2; // Register sweep interchain account
REWARD_CLAIMING = 3; // Register reward account as the withdraw address on the host chain
}

MsgCreateMultiSigConnection

This message allows to create a new multi-sig connection on PRYZM. It can be executed by the governance module address (using a governance proposal) or one of the admin addresses defined in the module's parameters.

message MsgCreateMultiSigConnection {
string creator = 1;
string id = 2;
string operator = 3;
}

MsgUpdateMultiSigConnection

This message allows to update the operator of a new multi-sig connection on PRYZM. It can be executed by the governance module address (using a governance proposal) or one of the admin addresses defined in the module's parameters.

message MsgUpdateMultiSigConnection {
string creator = 1;
string id = 2;
string operator = 3;
}

MsgAcknowledgeMultiSigPacket

This message allows the multi-sig connection operator to acknowledge the execution (with success or failure) of a packet on the host chain.

message MsgAcknowledgeMultiSigPacket {
string creator = 1;
string connection_id = 2;
uint64 sequence = 3;
Acknowledgement ack = 4;
ibc.core.client.v1.Height height = 5;
string tx_hash = 6;
}

MsgRegisterHostAccounts

This message allows the multi-sig connection operator to register the delegation, reward and sweep account addresses of a host chain. This is necessary to execute this message after the host chain is created to make it IDLE.

message MsgRegisterHostAccounts {
string creator = 1;
string host_chain = 2;
string delegation_address = 3;
string reward_address = 4;
string sweep_address = 5;
}

MsgRetryFailedLsmTransfer

This message allows permission-less users to retry a failed LSM token transfer.

message MsgRetryFailedLsmTransfer {
string creator = 1;
string host_chain = 2;
string lsm_denom = 3;
string transfer_channel = 4;
}

Queries

Params

This query fetches the parameters of the module.

message QueryParamsRequest {}

message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}

HostChain

This query retrieves a host chain using its id.

message QueryGetHostChainRequest {
string host_chain_id = 1;
}

message QueryGetHostChainResponse {
HostChain host_chain = 1;
}

HostChainAll

This query fetches all host chains.

message QueryAllHostChainRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

message QueryAllHostChainResponse {
repeated HostChain host_chain = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

HostChainState

This query retrieves the state of a host chain using its id.

message QueryGetHostChainStateRequest {
string host_chain_id = 1;
}

message QueryGetHostChainStateResponse {
HostChainState host_chain_state = 1 [(gogoproto.nullable) = false];
}

HostChainStateAll

This query fetches the state of all host chains.

message QueryAllHostChainStateRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

message QueryAllHostChainStateResponse {
repeated HostChainState host_chain_state = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

Undelegation

This query retrieves an Undelegation using the host chain id and epoch number.

message QueryGetUndelegationRequest {
string host_chain = 1;
uint64 epoch = 2;
}

message QueryGetUndelegationResponse {
Undelegation undelegation = 1 [(gogoproto.nullable) = false];
}

UndelegationAll

This query fetches all Undelegation records, filtered by the host chain.

message QueryAllUndelegationRequest {
string host_chain = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

message QueryAllUndelegationResponse {
repeated Undelegation undelegation = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

IncompleteUndelegationAll

Fetches a list of incomplete undelegations, filtered by the provided host chain and sorted by completion time.

message QueryIncompleteUndelegationRequest {
string host_chain = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

message QueryIncompleteUndelegationResponse {
repeated Undelegation undelegation = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

ChannelUndelegation

This query retrieves a ChannelUndelegation using the host chain id, epoch number and the transfer channel.

message QueryGetChannelUndelegationRequest {
string host_chain = 1;
uint64 epoch = 2;
string transfer_channel = 3;
}

message QueryGetChannelUndelegationResponse {
ChannelUndelegation channel_undelegation = 1 [(gogoproto.nullable) = false];
}

ChannelUndelegationAll

This query fetches all ChannelUndelegation records, filtered by host chain and epoch number.

message QueryAllChannelUndelegationRequest {
string host_chain = 1;
uint64 epoch = 2;
cosmos.base.query.v1beta1.PageRequest pagination = 3 [(gogoproto.nullable) = true];
}

message QueryAllChannelUndelegationResponse {
repeated ChannelUndelegation channel_undelegation = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

DelegationQueueBalance

Fetches the balance of a delegation queue for a specific host chain on a particular transfer channel.

message QueryDelegationQueueBalanceRequest {
string host_chain = 1;
string transfer_channel = 2;
}

message QueryDelegationQueueBalanceResponse {
cosmos.base.v1beta1.Coin balance = 1 [(gogoproto.nullable) = false];
}

EpochInfo

Fetches the last delegation, undelegation, redelegation and lsm redeem times, and the current undelegation epoch number

message QueryEpochInfoRequest {
string host_chain = 1;
}

message QueryEpochInfoResponse {
google.protobuf.Timestamp last_delegation_time = 1;
google.protobuf.Timestamp last_redelegation_time = 2;
google.protobuf.Timestamp last_lsm_redeem_time = 3;
google.protobuf.Timestamp last_undelegation_time = 4;
uint64 current_undelegation_epoch = 5;
}

ReplyDataAll

This query fetches all ReplyData records.

message QueryAllReplyDataRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
}

message QueryAllReplyDataResponse {
repeated ReplyData reply_data = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

RedeemableLsmAll

This query fetches all RedeemableLsm records.

message QueryAllRedeemableLsmRequest {
string host_chain = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2 [(gogoproto.nullable) = true];
}

message QueryAllRedeemableLsmResponse {
repeated RedeemableLsm redeemable_lsm = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

FailedLsmTransferAll

This query fetches all FailedLsmTransfer records.

message QueryAllFailedLsmTransferRequest {
string host_chain = 1;
cosmos.base.query.v1beta1.PageRequest pagination = 2 [(gogoproto.nullable) = true];
}

message QueryAllFailedLsmTransferResponse {
repeated FailedLsmTransfer failed_lsm_transfer = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

MultiSigConnection

This query retrieves a MultiSigConnection using the connection identifier.

message QueryGetMultiSigConnectionRequest {
string id = 1;
}

message QueryGetMultiSigConnectionResponse {
MultiSigConnection multi_sig_connection = 1 [(gogoproto.nullable) = false];
}

MultiSigConnectionAll

This query fetches all MultiSigConnection records.

message QueryAllMultiSigConnectionRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
}

message QueryAllMultiSigConnectionResponse {
repeated MultiSigConnection multi_sig_connection = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

MultiSigPacket

This query retrieves a MultiSigPacket using the connection identifier and the packet sequence.

message QueryGetMultiSigPacketRequest {
string connection_id = 1;
uint64 sequence = 2;
}

message QueryGetMultiSigPacketResponse {
MultiSigPacket multi_sig_packet = 1 [(gogoproto.nullable) = false];
}

MultiSigPacketAll

This query fetches all MultiSigConnection records.

message QueryAllMultiSigPacketRequest {
string connection_id = 1 [(gogoproto.nullable) = true];
cosmos.base.query.v1beta1.PageRequest pagination = 2 [(gogoproto.nullable) = true];
}

message QueryAllMultiSigPacketResponse {
repeated MultiSigPacket multi_sig_packet = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

SweepTransferAll

This query fetches all SweepTransfer records.

message QueryAllSweepTransferRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1 [(gogoproto.nullable) = true];
}

message QueryAllSweepTransferResponse {
repeated SweepTransfer sweep_transfer = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2 [(gogoproto.nullable) = true];
}

SimulateStake

This query simulates the staking operation; If amount_in is provided, the resulting minted cAsset amount and fee deduction is returned. If amount_out is provided, the amount of assets needed to mint the provided amount of cAssets is calculated and returned.

message QuerySimulateStakeRequest {
string host_chain = 1;
string transfer_channel = 2;
string amount_in = 3 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
string amount_out = 4 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
];
}

message QuerySimulateStakeResponse {
cosmos.base.v1beta1.Coin amount_in = 1;
cosmos.base.v1beta1.Coin amount_out = 2;
cosmos.base.v1beta1.Coin fee_amount = 3 [(gogoproto.nullable) = false];
}

Events

EventSetHostChain

Emitted when a host chain is set. It includes the host chain information.

message EventSetHostChain {
HostChain host_chain = 1 [(gogoproto.nullable) = false];
}

EventSetHostChainState

Emitted when the host chain's state is set. It includes the host chain state details.

message EventSetHostChainState {
HostChainState host_chain_state = 1 [(gogoproto.nullable) = false];
}

EventSetParams

Emitted when parameters are set. It contains the module parameters.

message EventSetParams {
Params params = 1 [(gogoproto.nullable) = false];
}

EventSetUndelegation

Emitted when an undelegation record is set. Includes the undelegation record details.

message EventSetUndelegation {
Undelegation undelegation = 1 [(gogoproto.nullable) = false];
}

EventSetChannelUndelegation

Emitted when a channel undelegation record is set. Includes the channel undelegation record details.

message EventSetChannelUndelegation {
ChannelUndelegation channel_undelegation = 1 [(gogoproto.nullable) = false];
}

EventStake

Emitted when a user stakes assets. Contains details related to the stake transaction.

message EventStake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string amount = 4; // sdk.Int
cosmos.base.v1beta1.Coin fee = 5;
cosmos.base.v1beta1.Coin c_amount = 6;
}

EventStakeLsmShares

Emitted when a user stakes their LSM share tokens. Contains details related to the stake lsm shares transaction.

message EventStakeLsmShares {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string lsm_denom = 4;
string amount = 5; // sdk.Int
cosmos.base.v1beta1.Coin fee = 6 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin c_amount = 7 [(gogoproto.nullable) = false];
}

EventUnstake

Emitted when a user requests to unstake assets. Contains details related to the unstake transaction.

message EventUnstake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string c_amount = 4; // sdk.Int
cosmos.base.v1beta1.Coin u_amount = 5;
}

EventRedeemUnstaked

This event triggers when a user redeems unstaked assets, encapsulating the details of the redemption transaction.

message EventRedeemUnstaked {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
uint64 epoch = 4;
string u_amount = 5; // sdk.Int
cosmos.base.v1beta1.Coin amount = 6;
cosmos.base.v1beta1.Coin fee = 7;
}

EventInstantUnstake

This event triggers when a user requests instant asset unstaking, encapsulating the details of the instant unstake transaction.

message EventInstantUnstake {
string creator = 1;
string host_chain = 2;
string transfer_channel = 3;
string min_c_amount = 4; // sdk.Int
string max_c_amount = 5; // sdk.Int
cosmos.base.v1beta1.Coin amount = 6;
cosmos.base.v1beta1.Coin fee = 7;
}

EventSetMultiSigConnection

This event triggers when a multi-sig connection is created or updated.

message EventSetMultiSigConnection {
MultiSigConnection connection = 1 [(gogoproto.nullable) = false];
}

EventSetMultiSigPacket

This event triggers when a multi-sig packet is created or updated (a message is being sent).

message EventSetMultiSigPacket {
MultiSigPacket packet = 1 [(gogoproto.nullable) = false];
}

EventAcknowledgeMultiSigPacket

This event triggers when an acknowledgment is received for a multi-sig packet.

message EventAcknowledgeMultiSigPacket {
string connection_id = 1;
uint64 sequence = 2;
Acknowledgement ack = 3 [(gogoproto.nullable) = false];
ibc.core.client.v1.Height height = 4 [(gogoproto.nullable) = false];
string tx_hash = 5;
}

State

Params

Module parameters are stored under the following store key:

([]byte("v1/params/")) -> ProtoBuf(Params)

HostChain

Host chains are stored using their IDs as store keys:

(
[]byte("v1/host-chain/") |
[]byte(id)
) -> ProtoBuf(HostChain)

Host chains are also indexed with the connection type and ID to guarantee unique connection IDs:

(
[]byte("v1/host-chain-by-connection/") |
[]byte(connectionType) | // as uint32 in 4 bytes
[]byte(connectionId)
) -> []byte(hostChainId)

The state of host chains is stored using the host chain ID as the store key:

(
[]byte("v1/host-chain-state/") |
[]byte(hostChainId)
) -> ProtoBuf(HostChainState)

Epoch Counters

PRYZM stores the last delegation time, last undelegation time, last redelegation time, last lsm redeem time and the current undelegation number to periodically process delegation, undelegation, redelegation, and lsm redeem batches.

(
[]byte("v1/last-delegation-time/") |
[]byte(hostChainId)
) -> []byte(lastDelegationTime) // marshalled time
(
[]byte("v1/last-undelegation-time/") |
[]byte(hostChainId)
) -> []byte(lastUndelegationTime) // marshalled time
(
[]byte("v1/last-redelegation-time/") |
[]byte(hostChainId)
) -> []byte{lastRedelegationTime} // marshalled time
(
[]byte("v1/last-lsm-redeem-time/") |
[]byte(hostChainId)
) -> []byte(lastLsmRedeemTime) // marshalled time
(
[]byte("v1/undelegation-epoch/") |
[]byte(hostChainId)
) -> []byte(epoch) // marshalled uint64

Undelegation

Undelegations are stored using the host chain ID and epoch number as the store key:

(
[]byte("v1/undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(epoch) // represented as an 8-byte uint 64
) -> ProtoBuf(Undelegation)

Incomplete undelegations, sorted by completion time for post-completion handling, are stored using the host chain ID, completion time, and epoch number as the key.

(
[]byte("v1/incomplete-undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(completionTime) | // formatted as a sortable string
[]byte(epoch) // represented as an 8-byte uint 64
) -> undelegationKey

Undelegations are indexed regardless of whether they are initiated or completed:

(
[]byte("v1/unstarted-undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(epoch) // represented as an 8-byte uint 64
) -> []byte{1}
(
[]byte("v1/completed-undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(epoch) // represented as an 8-byte uint 64
) -> []byte{1}

Channel Undelegation

Channel undelegations are stored with the host chain id, epoch number, and transfer channel id as their store key:

(
[]byte("v1/channel-undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(epoch) | // represented as an 8-byte uint 64
[]byte(transferChannel)
) -> ProtoBuf(ChannelUndelegation)

Channel undelegations are also indexed if they haven't been swept:

(
[]byte("v1/unswept-channel-undelegation/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(epoch) | // represented as an 8-byte uint 64
[]byte(transferChannel)
) -> []byte{1}

Redeemable Lsm

Records of the amount of LSM tokens that must be redeemed on the host chain are stored with the following structure:

(
[]byte("v1/redeemable-lsm/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(lengthPrefixedLsmDenom)
) -> ProtoBuf(RedeemableLsm)

Failed Lsm Transfer

Records of the LSM tokens that have been failed to be transferred to the host chain are stored with the following structure:

(
[]byte("v1/failed-lsm-transfer/") |
[]byte(lengthPrefixedHostChainId) |
[]byte(lengthPrefixedLsmDenom) |
[]byte(transferChannel)
) -> ProtoBuf(FailedLsmTransfer)

Multi-Sig Connection

Multi-Sig Connections are stored with their id as the store key:

(
[]byte("v1/multi-sig-connection/") |
[]byte(connectionId)
) -> ProtoBuf(MultiSigConnection)

Multi-Sig Packet

Multi-Sig Packets are stored with their associated connection id and their sequence as the store key:

(
[]byte("v1/multi-sig-packet/") |
[]byte(lengthPrefixedConnectionId) |
[]byte(sequence) // represented as an 8-byte uint 64
) -> ProtoBuf(MultiSigPacket)

Loop-Back Packet

Loop-Back Packets are stored with their associated id as the store key:

(
[]byte("v1/loop-back-packet/") |
[]byte(id) // represented as an 8-byte uint 64
) -> ProtoBuf(LoopBackPacket)

The latest generated id is also stored to be used in generating new id for new packets:

(
[]byte("v1/loop-back-packet-last-id/")
) -> ProtoBuf([]byte{lastId}) // represented as an 8-byte uint 64

Reply Data

Bridges' reply data records are stored with their key as the store key:

(
[]byte("v1/reply-data/") |
[]byte(key)
) -> ProtoBuf(ReplyData)

Sweep Transfer

Sweep transfer records are stored to handle the sweep transfer process and timeout:

(
[]byte("v1/sweep-transfer/") |
[]byte(timeout) | // represented as an 8-byte uint 64
[]byte(lengthPrefixedHostChainId) |
[]byte(channel)
) -> ProtoBuf(SweepTransfer)

IBC Ports

PRYZM is the ICA controller in the connection with the host chain and opens one port for each interchain account responsible for sending packets to control the account on the host chain. Therefore, for every defined host chain on PRYZM, it opens three ports with the following names:

  • Delegation account: icacontroller-delegation-{hostChainId}
  • Reward account: icacontroller-reward-{hostChainId}
  • Sweep account: icacontroller-sweep-{hostChainId}

Parameters

Module Params

StakingParams

This parameter serves as the default staking parameters for all host chains. Each host chain can override each staking parameter's default value by specifying a value in the HostChain object.

Admins

This parameter is a list containing admin addresses, authorized to register and update host chains without gov proposal.

Staking Params

The staking parameters include the following properties:

FeeRatios

The fee ratios for each operation supported by the module are:

  • Yield: The portion of the asset staking reward taken as the protocol fee.

    • Type: sdk.Dec
    • Range: [0, 1]
    • Default Value: 0.1
  • Staking: The portion of the asset amount a user is staking that is taken as the fee.

    • Type: sdk.Dec
    • Range: [0, 1]
    • Default Value: 0
  • Unstaking: Fee taken as a ratio of the asset amount a user redeems.

    • Type: sdk.Dec
    • Range: [0, 1]
    • Default Value: 0
  • InstantUnstaking: Fee taken as a ratio of the instant asset amount a user redeems.

    • Type: sdk.Dec
    • Range: [0, 1]
    • Default Value: 0.05

DelegationInterval

Interval for PRYZM to send delegation messages to the host chain.

  • Type: time.Duration
  • Range: [0, 24h]
  • Default Value: 6h

LsmRedeemInterval

Interval for PRYZM to send LSM redeem messages to the host chain.

  • Type: time.Duration
  • Range: [0, 24h]
  • Default Value: 6h

UndelegationInterval

Interval for PRYZM to send undelegation messages to the host chain. Max value depends on the host chain's UnbondingTime / MaxEntries.

  • Type: time.Duration
  • Range: [0, 120h]
  • Default Value: 72h

IBCTransferTimeout

Timeout duration when sending IBC transfer messages.

  • Type: time.Duration
  • Range: [0, 1h)
  • Default Value: 30m

ICATimeout

Timeout duration when sending ICA messages.

  • Type: time.Duration
  • Range: [0, 1h)
  • Default Value: 30m

RebalanceParams

Parameters for re-balancing delegation between whitelisted validators.

  • MaxMsgs: Maximum number of redelegation messages in each re-balance operation.

    • Type: int32
    • Range: [1, 10]
    • Default Value: 3
  • RebalanceThreshold: Minimum divergence a validator delegation weight must reach to trigger a re-balance operation.

    • Type: sdk.Dec
    • Range: [0, 1]
    • Default Value: 0.1
  • MinRebalanceAmount: Minimum asset amount for each redelegation message.

    • Type: sdk.Int
    • Range: [0, inf)
    • Default Value: 1_000_000_000
  • MinRebalanceInterval: Minimum interval between two re-balance operations.

    • Type: time.Duration
    • Range: [1h, inf)
    • Default Value: 21d

Genesis

The genesis state of this module includes:

  • Params: Parameters for the icstaking module.
  • port_id: Bound port used by the blockchain network.
  • host_chain_list: List of all host chains.
  • host_chain_state_list: List of all host chain states.
  • undelegation_list: List of all undelegation records.
  • channel_undelegation_list: List of all channel undelegation records.
  • reply_data_list: List of all reply data records.
  • redeemable_lsm_list: List of all redeemable LSM records.
  • failed_lsm_transfer_list: List of all failed LSM transfer records.
  • multi_sig_connection_list: List of all multi-sig connections.
  • multi_sig_packet_list: List of all multi-sig packets.
  • loop_back_packet_list: List of all loop-back packets.
  • loop_back_packet_last_id: Last generated loop-back packet id.
  • last_delegation_time_list: List of last delegation times for host chains.
  • last_redelegation_time_list: List of last redelegation times for host chains.
  • last_lsm_redeem_time_list: List of last lsm redeem times for host chains.
  • last_undelegation_time_list: List of last undelegation times for host chains.
  • undelegation_epoch_list: List of the current delegation epochs for host chains.
  • delegate_transfer_session_list: List of all delegate transfer sessions.
  • sweep_transfer_list: List of all transfer sweeps.