All pages
Powered by GitBook
1 of 17

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Basics

Learn the basics of working with the IBC Developer Toolkit

IBC Developer Toolkit

Learn how to use Secret Network's IBC Developer Toolkit to design dApps with confidential computing.

Basics

  • Overview

  • Cross-chain Messaging with IBC hooks

Overview

Secret Network's CCL (Confidential Computing Layer) IBC Toolkit is a cross-chain messaging and confidential computation SDK for Cosmos developers.

The toolkit allows Cosmos developers to build novel cross-chain applications such as:

  • private DAO voting mechanisms

  • secure random number generation (Secret VRF)

  • confidential data access control via Secret NFTs

  • encrypted DeFi order books

  • and more!

Continue to to learn how to use Secret Network's Confidential Computing Layer SDK , or dive into the Key value store tutorial πŸ”₯

Usecases
Key Value store
VRF
Confidential Voting
Sealed Bid Auctions
Supported Networks
Mainnet
Testnet
Cross-Chain messaging
here

Usecases

Storing Encrypted Data on Secret Network

Learn how to use Secret Network as the confidential computation layer of the Cosmos

For any Cosmos chain, you can use encrypted payloads to execute and query confidential messages on Secret Network smart contracts.

Using our Confidential Computation Layer (CCL) SDK, you can seamlessly handle encrypted payloads, as the master gateway contract on Secret Network automatically decrypts the payload and hands the decrypted payload over to the target contract.

The encryption of the payload is done using the ChaCha20-Poly1305, an authenticated encryption with additional data (AEAD) algorithm.

The key for this symmetric encryption is created by using the Elliptic-curve Diffie-Hellman (ECDH) scheme, comprising of two components:

  1. An extra encryption public key provided from the Secret Gateway Contract

  2. A randomly created (ephemeral) encryption private key on the user side (independent of the user wallet's private key)

Combining both of these keys together via the ECDH Scheme yields our encryption key, which we use to encrypt the payload with ChaCha20-Poly1305.

As a first example for this, we have used the CCL SDK to encrypt a string and subsequently store it in a Secret Network contract.

IBC-Hooks

Initiate a contract call with an incoming IBC token transfer using IBC hooks

Overview

IBC-Hooks is an IBC middleware that uses incoming ICS-20 token transfers to initiate smart contract calls. This allows for arbitrary data to be passed in along with token transfers. This is useful for a variety of use cases such as cross-chain token swaps, auto-wrapping of SNIP-20 tokens, General Message Passing (GMP) between Secret Network and EVM chains, and much more! The mechanism enabling this is a memo field on every ICS20 transfer packet as of IBC v3.4.0. Wasm hooks is an IBC middleware that parses an ICS20 transfer, and if the memo field is of a particular form, it executes a Wasm contract call.

Note that the metadata in the memo field is not used within ICS-20 itself, but instead, a middleware or custom CosmWasm contract can wrap around the transfer protocol to parse the metadata and execute custom logic based off of it.

ICS20 Packet Structure

ICS20 is JSON native, so JSON is used for the memo format:

An ICS20 packet is formatted correctly for Wasm hooks if the following all hold:

  • memo is not blank

  • memo is valid JSON

  • memo has at least one key, with value "wasm"

If an ICS20 packet is not directed towards wasmhooks, wasmhooks doesn't do anything. If an ICS20 packet is directed towards wasmhooks, and is formatted incorrectly, then wasmhooks returns an error.

ICS20 Packet Execution Flow

Before Wasm hooks:

  • Ensure the incoming IBC packet is cryptographically valid

  • Ensure the incoming IBC packet has not timed out

In Wasm hooks, before packet execution:

  • Ensure the packet is correctly formatted (as defined above)

  • Edit the receiver to be the hardcoded IBC module account

In Wasm hooks, after packet execution:

  • Construct wasm message as defined above

  • Execute wasm message

  • If wasm message has error, return ErrAck

  • Otherwise continue through middleware

Auto-wrapping of SNIP-20 Example

receives funds from IBC, wraps them as SNIP-20 tokens, and then transfers them to the recipient that is specified in the ibc-hooks message:

Ack callbacks

A contract that sends an IBC transfer may need to listen for the acknowledgment (ack) of that packet. To allow contracts to listen to ack of specific packets, we provide Ack callbacks. The sender of an IBC transfer packet may specify a callback in the memo field of the transfer packet when the ack of that packet is received.

Only the IBC packet sender can set the callback

Ack callback implementation

For the callback to be processed, the transfer packet's memo should contain the following in its JSON:

{"ibc_callback": "secret1contractAddr"}

The WASM hooks will keep the mapping from the packet's channel and sequence to the contract in storage. When an ack is received, it will notify the specified contract via an execute message.

Interface for receiving the Acks and Timeouts

should implement the following interface for a sudo message:

Mainnet

Secret Network's IBC CCL SDK is available on IBC hooks-enabled chains with an IBC transfer channel enabled for Secret Network.

See additional Mainnet transfer channels on Mintscan .

Network
Relayer
Counterparty

Noble

SECRET / channel-88

NOBLE / channel-17

Axelar

SECRET / channel-20

AXELAR / channel-12

Juno

SECRET / channel-8

JUNO / channel-48

Evmos

SECRET / channel-18

EVMOS / channel-15

Osmosis

SECRET / channel-1

OSMOSIS / channel-88

here

Testnet

Secret Network's IBC CCL SDK is available on IBC hooks-enabled chains with an IBC transfer channel enabled for Secret Network.

Secret Network's IBC Developer Toolkit is available on any Cosmos chain that has established an IBC transfer channel with Secret Network!

Learn how to create your own testnet relayer channel here to start using the IBC Developer Toolkit!

memo["wasm"] has exactly two entries, "contract" and "msg"

  • memo["wasm"]["msg"] is a valid JSON object

  • receiver == memo["wasm"]["contract"]

  • See more here.
    The following contract
    The contract that awaits the callback
    {
      //... other ibc fields omitted for example
      "data": {
        "denom": "denom on counterparty chain (e.g. uatom)", // will be transformed to the local denom (ibc/...)
        "amount": "1000",
        "sender": "addr on counterparty chain", // will be ignored and shown to the contract as a null sender (cannot be verifed over IBC)
        "receiver": "secret1contractAddr",
        "memo": {
          "wasm": {
            "contract": "secret1contractAddr",
            "msg": {
              "raw_message_fields": "raw_message_data"
            }
          }
        }
      }
    }
    #[entry_point]
    pub fn execute(_deps: DepsMut, env: Env, info: MessageInfo, msg: Msg) -> StdResult<Response> {
        match msg {
            Msg::WrapDeposit {
                snip20_address,
                snip20_code_hash,
                recipient_address,
            } => Ok(Response::default().add_messages(vec![
                CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
                    contract_addr: snip20_address.clone(),
                    code_hash: snip20_code_hash.clone(),
                    msg: to_binary(&secret_toolkit::snip20::HandleMsg::Deposit { padding: None })
                        .unwrap(),
                    funds: info.funds.clone(),
                }),
                CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
                    contract_addr: snip20_address,
                    code_hash: snip20_code_hash,
                    msg: to_binary(&secret_toolkit::snip20::HandleMsg::Transfer {
                        recipient: recipient_address,
                        amount: info.funds[0].amount,
                        memo: None,
                        padding: None,
                    })
                    .unwrap(),
                    funds: vec![],
                }),
            ])),
            }
        }
    #[cw_serde]
    pub enum IBCLifecycleComplete {
        #[serde(rename = "ibc_ack")]
        IBCAck {
            /// The source channel (secret side) of the IBC packet
            channel: String,
            /// The sequence number that the packet was sent with
            sequence: u64,
            /// String encoded version of the ack as seen by OnAcknowledgementPacket(..)
            ack: String,
            /// Weather an ack is a success of failure according to the transfer spec
            success: bool,
        },
        #[serde(rename = "ibc_timeout")]
        IBCTimeout {
            /// The source channel (secret side) of the IBC packet
            channel: String,
            /// The sequence number that the packet was sent with
            sequence: u64,
        },
    }
    
    /// Message type for `sudo` entry_point
    #[cw_serde]
    pub enum SudoMsg {
        #[serde(rename = "ibc_lifecycle_complete")]
        IBCLifecycleComplete(IBCLifecycleComplete),
    }
    
    #[entry_point]
    pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult<Response> {
        match msg {
            SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck {
                channel,
                sequence,
                ack,
                success,
            }) => todo!(),
            SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCTimeout {
                channel,
                sequence,
            }) => todo!(),
        }
    }

    Functions, Methods, and Data Structures

    CCL SDK

    The Secret Network CCL SDK can be forked .

    Data Structures

    The essential parameters required for chacha20poly1305flow are defined in the following data structure:

    EncryptedParams

    A data structure that is visible to all network participants and can be transmitted over non-secure channels

    EncryptedPayload

    Data meant to be encrypted and stored in the payload field of EncryptedParams

    Custom Contract Message

    Your contract must define an endpoint where a user can pass all the required fields of the EncryptedParams. E.g:

    If you want to define a custom message, rename the fields, or add additional fields, there is a helpful trait WithEncryption that you can implement. It simply tells the compiler how to extract the essential parameters from your custom message and turn it into EncryptedParams

    Implementing the trait for your message will allow you to use other useful methods of the SDK (like handle_encrypted_wrapper) that significantly simplify the development experience.

    Example of the implementation for the ExecuteMsg is as follows:

    Extending existing data structures

    The SDK has multiple data structures that already implement WithEncryption trait and also use the template engine of Rust to make them easily extendable. Take for example the following message:

    You can define a new message that extends the GatewayExecuteMsg by simply providing a new type for the Extension instead of the default Option<Empty> like this:

    Your extended type in this case is available under MyGatewayExecuteMsg::Extension variant and you can use it in your contract like this:

    Functions and methods

    handle_encrypted_wrapper

    The encryption logic, handle_encrypted_wrapper, is where the encryption magic happens ⭐

    You can review the function in the SDK here. It has the following functionality:

    1. Check if Message is Encrypted:

      • If the message is encrypted (msg.is_encrypted()), it proceeds with decryption.

    2. Extract Encryption Parameters:

      • Retrieves the encryption parameters from the message (msg.encrypted()).

    3. Check Nonce:

      • Ensures the nonce has not been used before to prevent replay attacks.

    4. Load Encryption Wallet:

      • Loads the encryption wallet from storage.

    5. Decrypt Payload:

      • Decrypts the payload using the wallet and the provided parameters (payload, user_key, and nonce).

    decrypt_to_payload uses chacha20poly1305 algorithm

    1. Verify Credentials:

    • Constructs a CosmosCredential from the decrypted data.

    • Inserts the nonce into storage to mark it as used.

    • Verifies the sender using the verify_arbitrary function with the credential.

    1. Deserialize Inner Message:

    • Converts the decrypted payload into the original message type E.

    • Ensures the decrypted message is not encrypted (nested encryption is not allowed).

    1. Return Decrypted Message and Updated Info:

    • Returns the decrypted message and updated MessageInfo with the verified sender.

    chacha20poly1305_decrypt

    The following function uses the following types for as the input parameters:

    • cosmwasm_std::Binary,

    • std::vec::Vec.

    • [u8]

    • and others that implement Deref<Target = [u8]> trait

    Various authentication utilities

    To verify a message that was was signed through a method cosmos arbitrary (036) message format, you can use the following function:

    The method takes in a CosmosCredential struct as an argument which is a a helpful wrapper over essential required fields required for the verification:

    Both CosmosCredential and EncryptedParams can be used with String or base64 encoded Binary types

    To generate a preamble message for the cosmos arbitrary (036) message format, you can use the following utility function:

    The function uses a hardcoded JSON string with all the required keys present and sorted.

    here
    /// A data structure that is safe to be visible by all network participants and can be transmited over non-secure channels
    struct EncryptedParams {
        /// Encrypted payload containging hidden message
        pub payload            :   Binary,
        /// Sha256 hash of the payload
        pub payload_hash       :   Binary,
        /// Signed base64 digest of the payload_hash being wrapped
        /// in an cosmos arbitrary (036) object and rehashed again with sha256
        pub payload_signature  :   Binary,
        /// Public key of wallet used for deriving a shared key for chacha20_poly1305
        /// Not necessary the same as user's public key 
        pub user_key           :   Binary,
        /// One-time nonce used for chacha20_poly1305 encryption
        pub nonce              :   Binary,
    }
    
    /// Data meant to be encrypted and stored in the payload field of [EncryptedParams]
    #[cw_serde]
    pub struct EncryptedPayload {
        /// bech32 prefix address of a wallet used for signing hash of the payload 
        pub user_address   :  String,
        /// Public key of a wallet used for signing hash of the payload 
        pub user_pubkey   :   Binary,
        /// Human readable prefix for the bech32 address on the remote cosmos chain
        pub hrp           :   String,
        /// Plaintext message e.g. normal `ExecuteMsg` of your contract
        pub msg           :   Binary,
    }
    pub enum ExecuteMsg {
        ...
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        }
        ...
    
    }
    trait WithEncryption : Serialize + Clone  {
        fn encrypted(&self)     -> EncryptedParams;
        fn is_encrypted(&self)  -> bool;
    }
    impl WithEncryption for ExecuteMsg {
       fn encrypted(&self)     -> EncryptedParams {
           match self.clone() {
               ExecuteMsg::Encrypted {
                   payload,
                   payload_signature,
                   payload_hash,
                   user_key,
                   nonce,
               } => EncryptedParams {
                   payload,
                   payload_signature,
                   payload_hash,
                   user_key,
                   nonce
               },
               _ => panic!("Not encrypted")
    
           }
       }
    
       fn is_encrypted(&self)  -> bool {
           if ExecuteMsg::Encrypted{..} = self {
               true
           } else {
               false
           }
       }
    }
    pub enum GatewayExecuteMsg<E = Option<Empty>> 
        where E: JsonSchema
    {
        ResetEncryptionKey  {} ,
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        },
    
        Extension {
            msg : E
        }
    }
    // Degine your custom message
    #[cw_serde]
    pub enum MyCustomMessage {
        HandleFoo {}
        HandleBar {}
    }
    // Extend the GatewayExecuteMsg
    pub type MyGatewayExecuteMsg = GatewayExecuteMsg<MyCustomMessage>;
    /// MyGatewayExecuteMsg
    match msg {
        ... 
        ResetEncryptionKey => { ... },
    
        MyGatewayExecuteMsg::Extension{msg} => {
    
            /// MyCustomMessage
            match msg {
                MyCustomMessage::HandleFoo{} => {
                    // Do something
                }
                MyCustomMessage::HandleBar{} => {
                    // Do something
                }
            }
        }
        
        ...
    }
          let decrypted  = wallet.decrypt_to_payload(
                &params.payload,
                &params.user_key,
                &params.nonce,
            )?;
    pub fn chacha20poly1305_decrypt(
        ciphertext    :     &impl Deref<Target = [u8]>,
        key           :     &impl Deref<Target = [u8]>,
        nonce         :     &impl Deref<Target = [u8]>,
    ) -> StdResult<Vec<u8>> {
        ...
    }
    fn verify_arbitrary<M : Display>(api:  &dyn Api, cred: &CosmosCredential<M>) -> StdResult<String>
    pub struct CosmosCredential<M = String> 
        where M: Display
    {
        /// public key matching the respective secret that was used to sign message
        pub pubkey    :   Binary,
        /// signed sha256 digest of a message wrapped in arbitary data (036) object
        pub signature :   Binary,
        /// signed inner message before being wrapped with 036
        pub message   :   M,
        /// prefix for the bech32 address on remote cosmos chain
        pub hrp       :   String
    }
    fn preamble_msg_arb_036(signer: &str, data: &str) -> String

    Typescript SDK

    CCL IBC SDK for typescript developers

    Typescript Demo

    See a fullstack Next.js typescript demo here (using Osmosis Mainnet). Code available here.

    Dependencies

    For encryption, we recommend using @solar-republic/neutrino which has useful chacha20poly1305related functionalities and additional primitives for generating ephemereal lightweight wallets. Installation:

    For signing, encoding and other cryptographic needs in the Cosmos ecosystem, it is common to use the suite of @cosmjs packages. You can install the following:

    If you are developing in the browser environment or connecting to a public network you might also need

    Note: You can also use any Typescript / Javascript package managers and runtimes e,g, bun, yarn, pnpm etc.

    Generating Wallets

    For chacha20poly1305, we need to use a crypthographic keypair and it's advised to use one that isn't the same as the user's wallet. The SDK provides a method for generating a new wallet that can be used for encryption purposes. For our purposes, we just need a private / public keys of Secp256k1 type and there are various ways to generate them.

    @cosmjs/crypto

    @solar-republic/neutrino

    @secretjs

    Query Client

    Before proceeding to encryption, you might want to create a quering client that will be used for querying the state and contract of the Secret Network. At the very least, it is requited for fetching the public key of a gateway contract for deriving a shared key used later for encryption.

    To perform a simple query on a secret contract we can use methods from @solar-republic/neutrino:

    For more persistent use-cases you can use secretjs:

    Signatures

    To make sure that malicious applications aren't tricking the user into signing an actual blockchain transaction, it is discouraged to sign arbitrary blobs of data. To address the situation, there are various standards that inject additional data to the message before signing it. The most used in the Cosmos ecosystem is defined in which is also used in the SDK.

    Browser Wallets

    Most of the Cosmos wallets provide a method for signing arbitrary messages following the mentioned specification.

    Here is a definition taken from documentation of :

    Although the API method requires a chainId, it is set to empty string before signing the message

    Cosmology

    Cosmology signArbitrary method as part of the interface for their wallet client and provides implementation / integration for every popular Cosmos wallet out there

    CosmJS

    The logic of the method has already been implemented and proposed as an addition to the library however it has been hanging in a unmerged PR for a while. You can find the full implementation with examples and tests

    Manually

    Getting Signer and Signer Address

    Firstly we need to get an amino signer that will be used for generating the signature. @cosmjs has a defined interface OfflineAminoSigner with signAmino and getAccounts methods and any other signer that implements it can be used for the purpose.

    Generating the message and StdSignDoc

    To use signAmino, we need to generate a StdSignDoc object that will be used for signing the message to pass as an argument.

    CosmJS provides a function for this:

    The 036 standard requires the message fields to AminoMsg to be:

    As for the rest of the fields, they can be set to an empty string or 0. The final example will look like this:

    After getting the document we can sign it with the signer:

    Encryption

    After getting a public key of a gateway contract you can use it to derive a shared key like this:

    Encrypting + Signing

    Produced digest of hashing the ciphertext can be used as our message that we want to sign according to the 036 standard. The final message will look like this:

    After this we are getting all the required fields for creating an EncryptedPayload message or an ExecuteMsg::Encrypted { ... }

    Broadcasting the message

    The encrypted message is safe to broadcast over public blockchain and other infrastructure. A common use-case in context of Cosmos account might be broadcasting it over IBC originating from a chain other than the Secret Network.

    The potential use-case might involve broadcasting the message by initiating an IBC message directly and attaching the message as a payload (IBC-Hook) or passing the message to a smart contract on a remote chain to process and bridge it to the Secret Network.

    Since Cosmwasm is quite flexible with defining messages due to supporting JSON serialization, it is possible the process is very similar in both cases so we only going to cover IBC-hooks for simplicity:

    Key-Value store Developer Tutorial

    Learn how to use Secret Network as the confidential computation layer of the Cosmos with IBC hooks

    Overview

    Secret Network's Confidential Computation SDK uses IBC hooks to seamlessly handle cross-chain encrypted payloads, which means Cosmos developers can now encrypt and decrypt messages with a simple token transfer.

    See fullstack demo for Osmosis Mainnet here.

    This tutorial explains how to upload your own Key-value store contract on Secret Network, which you can use to encrypt values on Secret Network and transmit them cross-chain from a Cosmos chain of your choice! After this tutorial, you will have the tools you need to encrypt messages on any IBC hooks-enabled Cosmos chain.

    In this example, you will send a token from Osmosis mainnet to Secret Network mainnet to encrypt a stringπŸ”₯

    Getting Started

    To get started, clone the :

    Configuring Environment Variables

    Install the dependencies:

    Create an env file. Simply update to omit ".testnet" from the file name and then add your wallet's mnemonics:

    Note that for our consumer chain, we are using the endpoint, chainID, token, and prefix for Osmosis Mainnet. But you could update this for any Cosmos chain that has IBC hooks enabled and a with Secret Network πŸ˜„

    Upload the encryption contract on Secret Network

    Now that you have your environment configured, it's time to upload the encryption contract to Secret Network.

    First, compile the contract:

    This compiles the contract and creates a wasm file in the following directory:

    The files that make up the IBC SDK, including the script to upload the contract to Secret Network, are in .

    Compile the typescript files so you can execute them with node:

    Once you run the above command, the typescript files in ./src will be compiled as javascript files in ./dist.

    Upload and instantiate the encryption contract on Secret Network Mainnet:

    In your terminal, a codeID, codeHash, and contractAddress will be returned:

    Update with your contractAddress and codeHash accordingly:

    Encrypt a payload with Typescript SDK

    Now that you have your encryption smart contract uploaded on Secret Network, let's use it to store encrypted messages from Osmosis Mainnet Most of the ECDH cryptography has been abstracted away so there are only a few values you need to change.

    The functions in are helper functions that help us configure cross-chain network clients, IBC token transfers, etc. However, there is also an additional function which executes the gateway contract called ! Execute-gateway.js demonstrates sending a token transfer to store an unencrypted as well as an encrypted message.

    Feel free to update the to be encrypted.

    To encrypt the payload, run execute-gateway.js:

    This will initiate a Token transfer:

    As well as an IBC Acknowledgement response:

    Congrats! You have now used the Secret Network CCL SDK to encrypt a string on Osmosis Mainnet!

    Encryption SDK - how it works

    Now that you have used Secret Network's CCL SDK to successfully encrypt a string cross-chain, let's examine the you deployed on Secret Network to understand how everything works underneath the hood.

    At a high level, you can think of the SDK like so:

    1. There is a gateway contract deployed to Secret Network, which has the ability to encrypt a string, as well as query the encrypted string.

    2. The gateway contract imports helper functions from the , which is where the gateway contract imports encryption and query types, etc.

    You can add additional functionality to the gateway contract as you see fit. For example, you could write an execute message that stores encrypted votes or encrypted NFTs, etc!

    To use the SDK's encryption helper functions, simply write your gateway contract messages inside of the , which imports from the SDK :)

    Now let's examine each of the gateway contract's files to understand how it encrypts a user-inputted string.

    state.rs

    is where we define the keymap that holds our encrypted strings. The keymap SECRETS is designed to store a mapping from account user addresses (as strings) to their secrets (also as strings), using the Bincode2 serialization format.

    msg.rs

    is where we define the functionality of our gateway contract. It has 2 primary functionalities: storing encrypted strings and querying encrypted strings. Note that the types ExecuteMsg and QueryMsg are defined in the SDK .

    • GatewayExecuteMsg: Defines execution messages that can be sent to the contract, including resetting an encryption key, sending encrypted data, and extending with additional message types.

    • GatewayQueryMsg: Defines query messages that can be sent to the contract, including querying for an encryption key, querying with authentication data, querying with a permit, and extending with additional query types.

    contract.rs

    contains the smart contract logic which allows the following:

    • Execution: Processes messages to reset the encryption key or store a secret, ensuring only authorized access.

    • Querying: Handles queries to retrieve the encryption key and perform permissioned queries.

    The encryption logic, handle_encrypted_wrapper, is imported from the SDK at . This is where the encryption magic happens ⭐.

    You can review the function in the SDK . It has the following functionality:

    1. Check if Message is Encrypted:

      • If the message is encrypted (msg.is_encrypted()), it proceeds with decryption.

    2. Extract Encryption Parameters:

    uses chacha20poly1305 algorithm

    1. Verify Credentials:

    • Constructs a CosmosCredential from the decrypted data.

    • Inserts the nonce into storage to mark it as used.

    • Verifies the sender using the verify_arbitrary function with the credential.

    1. Deserialize Inner Message:

    • Converts the decrypted payload into the original message type E.

    • Ensures the decrypted message is not encrypted (nested encryption is not allowed).

    1. Return Decrypted Message and Updated Info:

    • Returns the decrypted message and updated MessageInfo with the verified sender.

    Summary

    In this tutorial, you learned how to utilize Secret Network's Confidential Computation SDK to encrypt and decrypt messages across Cosmos chains using IBC hooks. By following the steps outlined, you successfully:

    1. Configured the development environment with the necessary dependencies and environment variables.

    2. Uploaded and instantiated an encryption contract on the Secret Network mainnet.

    3. Encrypted and transmitted a payload from the Osmosis Mainnet to the Secret mainnet using IBC token transfers.

    With these tools and knowledge, you are now equipped to handle cross-chain encrypted payloads on any IBC hooks-enabled Cosmos chain, enhancing the security and confidentiality of your blockchain applications.

    Supported Networks

    ADR 036
    Keplr wallet
    defines
    [PR] Here
    Retrieves the encryption parameters from the message (msg.encrypted()).
  • Check Nonce:

    • Ensures the nonce has not been used before to prevent replay attacks.

  • Load Encryption Wallet:

    • Loads the encryption wallet from storage.

  • Decrypt Payload:

    • Decrypts the payload using the wallet and the provided parameters (payload, user_key, and nonce).

  • repository
    env.testnet
    transfer channel
    ./src
    contracts.json
    ./src
    execute-gateway.js
    strings
    gateway smart contract
    SDK
    InnerMethods enum
    GatewayExecuteMsg
    state.rs
    msg.rs
    here
    contract.rs
    line 55
    here
    decrypt_to_payload
    npm install --save @solar-republic/neutrino
    npm install --save @cosmjs/crypto @cosmjs/amino @cosmjs/encoding
    npm install --save @cosmjs/stargate
    # or
    npm install --save @cosmjs/cosmwasm-stargate
    import { Slip10Curve, Random, Bip39, Slip10, stringToPath, Secp256k1 } from "@cosmjs/crypto"
    
    const seed = await Bip39.mnemonicToSeed(Bip39.encode(Random.getBytes(16)));
    const { privateKey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, stringToPath("m/44'/1'/0'/0"));
    const pair = await Secp256k1.makeKeypair(privateKey);
    // must be compressed to 33 bytes from 65
    const publicKey = Secp256k1.compressPubkey(pair.pubkey);
    import { gen_sk, sk_to_pk } from "@solar-republic/neutrino"
    
    const privateKey = gen_sk(); 
    const publicKey = sk_to_pk(privateKey);
    import { Wallet } from "secretjs";
    const { privateKey, publicKey } = new Wallet()
    import { SecretContract } from "@solar-republic/neutrino"
    
    // create a contract instantse
    const contract = await SecretContract(
        secretNodeEndpoint,
        secretContractAddress
    )
    
    // query example:
    // get encryption key from a gateway contract
    const queryRes = await contract.query({ encryption_key: {} })
    
    
    // extract res value
    const gatewayPublicKey = queryRes[2]
    import { SecretNetworkClient } from "secretjs"
    
    // create a client:
    const client = new SecretNetworkClient({
        chainId, 
        url // endpoint URL of the node
    });
    
    // query the contact and get the value directly
    const gatewayPublicKey =  await client.query.compute.queryContract({
        contract_address
        code_hash, // optionally
        { encryption_key: {} } // query msg
    });
    // window.keplr.signArbi....
    signArbitrary(chainId: string, signer: string, data: string | Uint8Array) : Promise<StdSignature>
    // In browser environment we can get it from a wallet extension. E.g with Keplr:
    const signer = window.keplr.getOfflineSigner(chainId);
    
    
    //  ...
    
    
    // In Node environment we can use `Secp256k1Wallet` class that also implements `OfflineAminoSigner` interface
    import { Secp256k1Wallet } from "@cosmjs/amino"
    
    
    // see examples of generating a random above but in this case you will probably be using a persistent one from a .env file 
    // you can also pass extra options like prefix as the second argument
    const signer = await Secp256k1HdWallet.fromMnemonic(userMnemonic) 
    
    
    //  ...
    
    // Here we are getting the first accounts from the signer
    // accessing the address and renaming it to `signerAddress` 
    const [{ address : signerAddress }] =  await signer.getAccounts();
    
    //  ...
    function makeSignDoc(msgs: AminoMsg[], fee: StdFee, chainId: string, memo: string | undefined, accountNumber: number | string, sequence: number | string, timeout_height?: bigint): StdSignDoc;
    type AminoMsg = {
        // static type url
        type: ;
        value: {
            // signer address
            signer: string;
            // plaintext or base64 encoded message
            data: string;
        }
    }
    const data = "my message";
    
    
    const signDoc  = makeSignDoc(
        [{                              // list of amino messages
            type: "sign/MsgSignData", 
            value:  { 
                signer: signerAddress, 
                data:   data 
            }
        }], 
        { gas: "0", amount: [] },      // StdFee
        "",                            // chainId 
        "",                            // memo
        0,                             // accountNumber
        0                              // sequence
        // timeout_height
    )
    const signRes = await signer.signAmino(signerAddress, signDoc);
    import { sha256, Random } from "@cosmjs/crypto"
    import { fromBase64, toBase64, toAscii } from "@cosmjs/encoding";
    import { chacha20_poly1305_seal, ecdh } from "@solar-republic/neutrino"
    // this is a dependency of `@solar-republic/neutrino` so consider importing these methos 
    import { concat, json_to_bytes } from "@blake.regalia/belt";
    
    
    // ...
    //   define
    //  
    //      `clientPrivateKey`
    //      `gatewayPublicKey`
    //      `signerAddress`
    //
    //   like described above
    // ...
    
    
    const sharedKey = sha256(ecdh(clientPrivateKey, gatewayPublicKey))
    
    // We also need to generate a one-time nonce, which can be done like this:
    const nonce =  Random.getBytes(12)
    
    // Prepare a message for the action you want to take on the contract
    const msg = ExecuteMsg { ...}
    
    
    /// Defining payload structure idential to the Rust data structure
    const payload : EncryptedPayload = {
        // e.g, cosmos1...
        user_address: signerAddress,
        // uint8array -> base64  (-> Binary)
        user_pubkey: toBase64(signerPubkey),
        // e.g. "cosmos" from "cosmos1..."
        hrp: signerAddress.split("1")[0], 
        // or to toBinary(msg) from `@cosmjs/cosmwasm-stargate`
        msg: toBase64(json_to_bytes(msg))
    }
    
    
    /// getting the payload ciphertext 
    const ciphertext = concat(chacha20_poly1305_seal(
        sharedKey,
        nonce,
        // or toUtf8( JSON.stringify(payload) )  
        json_to_bytes( payload )
    ));
    
    // finally the payload_hash is sha256 of the ciphertext
    const ciphertextHash = sha256(ciphertext);
    
    
    //    ...
    // calling `makeSignDoc` with nullifed fields like described earlier
    const signDoc = getArb36SignDoc(signerAddress, ciphertextHash);
    // signing the message
    const signRes = await signer.signAmino(signerAddress, signDoc);
    const encrypted = {
        // uint8array -> base64  (-> Binary)
        nonce              :     toBase64(nonce),
        // public key of a pair that was used in deriving a shread key 
        user_key           :     toBase64(clientPublicKey),
        // ciphertext of with the user data and actual message
        payload            :     toBase64(ciphertext),
        // sha256 hash of the ciphertext
        payload_hash       :     toBase64(ciphertextHash),
        // signatire over sha256( getArb36SignDoc( payload_hash ) ) already in base64
        payload_signature  :     signRes.signature.signature,
    }
    import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx";
    import { SigningStargateClient, MsgTransferEncodeObject } from "@cosmjs/stargate";
    
    /// creating a client
    const client = await SigningStargateClient(
        "https://rpc.cosmoshub.io"    //  endpoint of the remote network 
        signer,                       // offlineSigner  
    )
    
    
    // defining the IBC transfer message
    const msg : MsgTransferEncodeObject = {
        typeUrl: "/ibc.applications.transfer.v1.MsgTransfer",
        value: MsgTransfer.fromPartial({
          sender:   signerAddress,
          receiver: secretGatewayContractAddress,
          sourceChannel: "channel-0",
          sourcePort: "transfer",
          timeoutTimestamp:  BigInt(
            // 5 minutes from now           |  ms -> ns
            Math.floor(Date.now() + 300_000) * 1_000_000
          ),
          // IBC Hook memo msg
          memo: JSON.stringify({
            wasm: {
                // must be same as receiver
                contract: secretGatewayContractAddress,
                // encrypted message defined above
                msg: encrypted
            }
          })
        })
    }
    
    // signing and broadcasting the message
    const res = await client.signAndBroadcast(signerAddress, [msg])
    git clone https://github.com/writersblockchain/cosmos-ccl-sdk.git
    npm install
    SECRET_MNEMONIC=""
    SECRET_CHAIN_ENDPOINT="https://lcd.mainnet.secretsaturn.net"
    SECRET_CHAIN_ID="secret-4"
    SECRET_TOKEN="uscrt"
    
    
    CONSUMER_MNEMONIC=""
    CONSUMER_CHAIN_ENDPOINT="https://rpc.osmosis.zone:443"
    CONSUMER_CHAIN_ID="osmosis-1"
    CONSUMER_TOKEN="uosmo"
    CONSUMER_PREFIX="osmo"
    CONSUMER_GAS_PRICE="0.25"
    CONSUMER_DECIMALS=6
    make build-mainnet-reproducible
    "./optimized-wasm/gateway_simple.wasm.gz"
    npm run build
    node dist/deploy.js
    "code_id": 8882,
    "code_hash": "f3c2e28cd1574d128ded60ce967cdb46f7515d807be49127bcc9249c5fd97802",
    "address": "secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm"
    {
        "gateway": {
            "address": "secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm",
            "hash": "d4a018804bf63b6cfd5be52b650368e8ad89f57c66841f6b2da7ee143dfc75fb"
        }
    }
    node dist/execute-gateway.js
    Sending IBC token...
    receiver: secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm
    token: uosmo
    amount: 1
    source_channel: channel-88
    memo: {"wasm":{"contract":"secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm","msg":{"extension":{"msg":{"store_secret":{"text":"new_text_68ss4"}}}}}}
    Broadcasted IbcTX. Waiting for Ack: Promise { <pending> }
    ibc Ack Received!
    info ack: {
      packet: {
        sequence: '252',
        source_port: 'transfer',
        source_channel: 'channel-88',
        destination_port: 'transfer',
        destination_channel: 'channel-1',
        data: Uint8Array(887) [
          123,  34,  97, 109, 111, 117, 110, 116,  34,  58,  34,  49,
           34,  44,  34, 100, 101, 110, 111, 109,  34,  58,  34, 117,
           97, 120, 108,  34,  44,  34, 109, 101, 109, 111,  34,  58,
           34, 123,  92,  34, 119,  97, 115, 109,  92,  34,  58, 123,
           92,  34,  99, 111, 110, 116, 114,  97,  99, 116,  92,  34,
           58,  92,  34, 115, 101,  99, 114, 101, 116,  49, 113,  48,
          109, 121,  99,  99, 108, 117,  57,  50,  55, 117,  53, 109,
           48, 116, 110,  53,  48, 122, 103, 108,  53,  97, 102,  52,
          117, 116, 114, 108,
          ... 787 more items
        ],
        timeout_height: { revision_number: '0', revision_height: '0' },
        timeout_timestamp: '1722277043000000000'
      },
          let decrypted  = wallet.decrypt_to_payload(
                &params.payload,
                &params.user_key,
                &params.nonce,
            )?;

    Secret VRF for IBC with IBC-Hooks

    Verifiable on-chain random number generator for the entire Cosmos.

    Secret VRF (Verifiable Random Function)

    This VRF tutorial uses IBC Hooks, as introduced in IBC v3.4.0. If your chain is on a previous IBC version, see the SecretVRF tutorial using proxy contracts here.

    Secret VRF is a provably fair and verifiable on-chain random number generator (RNG) that enables smart contracts to access random values without compromising security or usability. Coupled with cross-chain interoperable smart contracts using , Secret Network enables developers and projects across the Cosmos access to state-of-the-art on-chain RNG.

    Use Secret VRF to build reliable smart contracts for any application that relies on unpredictable outcomes:

    • NFT Minting: Utilize randomness for features like unordered minting, trait randomization, and identity numbering, enhancing the authenticity and security of NFT collections.

    • Web3 Gaming: Apply randomness in gambling, damage calculation, loot boxes, and boss drops to build trust among players by ensuring fairness and preventing any player from having an unfair advantage.

    • DAO Operations: Employ randomness for wallet initialization, task assigning, unordered voting/liquidations, and order book ordering, facilitating equitable and secure decentralized governance and operations.

    Learn more about how SecretVRF works in-depth .

    Getting Started

    To use SecretVRF on any IBC-enabled chain with IBC hooks, all that is required is:

    1. An IBC transfer channel between Secret Network and the consumer chain that receives randomness.

    2. Uploading the RNG Consumer Contract to your chain and directing it to your chain's transfer channel

    You can look up existing transfer channels between Secret Network on a block explorer such as Ping or .

    Requesting Randomness

    Git clone the IBC hooks randomness repository:

    Update the and the to the channel-id for your IBC-enabled chain:

    For this example, we request randomness on Juno Mainnet, but you can request randomness on any IBC-compatible chain that has a transfer channel established with Secret Network.

    Once you have updated the transfer channels, compile the contract:

    Upload the compiled contract:

    Upon successful , a code_id is returned:

    Instantiate the contract with the returned code_id:

    Upon succesful , a contract_address is returned:

    Now that you've instantiated your randomness contract, all that's left is to request randomness! Simply execute request_random:

    A transaction hash will be returned:

    You can update the job_id string to any string of your choosing

    Navigate to the recently for your contract:

    And then view the magic of cross-chain randomness with IBC hooks 😍:

    Congrats! You've just sent a verifiable on-chain random byte with SecretVRF πŸ™Œ.

    Conclusion

    Secret VRF revolutionizes blockchain applications by providing a secure and verifiable source of randomness, critical for fairness in NFT minting, gaming, and DAO operations. Its seamless integration with IBC hooks enables cross-chain interoperability, allowing developers across the Cosmos ecosystem to build more reliable and elegant smart contracts.

    If you have any questions, join our and a Secret developer will assist you ASAP.

    Cross-chain Messaging with IBC Hooks

    IBC Hooks for Cross-Chain Messaging

    IBC-Hooks is an IBC middleware that uses incoming ICS-20 token transfers to initiate smart contract calls. Secret Network created a Cosmos Developer SDK that uses IBC hooks to execute Secret gateway contracts, which allows Cosmos developers on other chains to use Secret as a privacy layer for your chain.

    The SDK abstracts the complexities involved in interacting with the Secret Network for applications that deal with Cosmos wallets. It introduces a secure method for generating confidential messages and reliably authenticating users at the same time using the chacha20poly1305 algorithm.

    The SDK can be used be for developing major gateways that forward incoming messages to Secret Network, as well as built-in support for confidential messages directly in other contracts. To learn by doing, start with the key value store developer tutorial .

    SDK Requirements

    • IBC Transfer channel between the consumer chain and Secret Network

    See for a list of existing transfer channels between Cosmos chains and Secret Network

    here
    Mintscan
    IBC hooks
    here
    here
    here
    SECRET_TRANSFER_CHANNEL_ID
    CHAIN_TRANSFER_CHANNEL_ID
    upload
    instantiation
    received transactions
    discord
    Existing IBC connections with Secret Network
    git clone https://github.com/SecretFoundation/ibchooks-secretVRF.git
    //Juno
    const SECRET_TRANSFER_CHANNEL_ID: &str = "channel-8";
    const CHAIN_TRANSFER_CHANNEL_ID: &str = "channel-48";
    make build-mainnet-reproducible
    junod tx wasm store consumer-side/artifacts/secret_ibc_rng_consumer_side_proxy.wasm --from <your-wallet> --chain-id juno-1 --node https://rpc.juno.chaintools.tech:443 --gas 1200000 --gas-prices 0.075ujuno
    {"key":"code_id","value":"4210"}]}]}]"
    junod tx wasm instantiate 4210 '{}' --label RNG-IBC-JUNO --no-admin --from <your-wallet> --chain-id juno-1 --node https://rpc.juno.chaintools.tech:443 --gas 200000 --gas-prices 0.075ujuno
    [{"key":"_contract_address","value":"juno1srwcjsaslt9ewujg6wcpcwv08lsrsn6rx6ja5mqx4qngqjh8cugqt73c8m"},
    junod tx wasm execute "juno1srwcjsaslt9ewujg6wcpcwv08lsrsn6rx6ja5mqx4qngqjh8cugqt73c8m" '{"request_random":{"job_id":"1"}}' --from <your-wallet> --chain-id juno-1 --node https://rpc.juno.chaintools.tech:443 --gas 200000 --gas-prices 0.075ujuno --amount 1ujuno
    txhash: 92D5BDA1344529B58EAD5A0068A807632F46BCCCF05CF10E67211F9CBBD2A74B
    {"amount":"1","denom":"transfer/channel-8/ujuno","memo":"{\"wasm\": {\"contract\": \"juno1srwcjsaslt9ewujg6wcpcwv08lsrsn6rx6ja5mqx4qngqjh8cugqt73c8m\", \"msg\": {\"receive_random\": {\"job_id\": \"1\", \"randomness\": \"bjFHP7rrLwP4f6fpGpeTt5+N1zPiTO+y7da7RI9kHzk=\", \"signature\": \"y+Kwu0T2gwRwDZGCdDPzGrm6hE2S2UZF1e1jm47pv85pdRdP7HdOfl6T+VKfAE4hPxSWJ5LBcTSNZ+b0KTe0xQ==\"}}}}","receiver":"juno1srwcjsaslt9ewujg6wcpcwv08lsrsn6rx6ja5mqx4qngqjh8cugqt73c8m","sender":"secret1up0mymn4993hgn7zpzu4je34w0n5s7l0mem7rk"}

    Confidential Voting

    A fullstack tutorial for cross-chain confidential voting on IBC-connected chains.

    Overview

    This tutorial explains how to upload a confidential voting contract on Secret Network, which you can execute and query for private voting on any IBC-connected chain. πŸš€

    In this example, you will learn how to deploy a confidential voting contract on Secret Network which you will execute from Osmosis mainnet.

    The SDK abstracts IBC interactions with the Secret Network for applications that use Cosmos wallets. It introduces a secure method for generating confidential messages and reliably authenticating users at the same time using the chacha20poly1305 algorithm.

    View the typescript SDK , which we will learn how to implement shortly πŸŽ‰

    In this tutorial you will learn:

    1. How to run the fullstack cross-chain Next.js application in your browser.

    2. How to use the core smart contract logic for confidential cross-chain votes and proposals using IBC hooks.

    3. How to deploy your very own voting contract on Secret Network.

    See a fullstack demo!

    The only requirement for using Secret Network's IBC toolkit is an existing IBC transfer channel. πŸ€“ You can view existing channels or a channel of your own.

    Getting Started

    Clone the repo:

    cd into next-frontend and install the dependencies:

    Add in cosmos-ccl-encrypted-payloads-demo/next-frontend:

    Start the application:

    The fullstack Next.js application should now be running in your browser! You can use it to create confidential votes and proposals. Now that you have the application running in your browser, let's dive into the smart contract logic!

    Understanding the contract

    Execution Messages

    To encrypt proposals and votes using the SDK, we use the enum called , which wraps the possible actions in the voting smart contract, namely, and , with the SDK encryption logic:

    InnerMethods leverage the SDK by using the to structure encrypted execution messages for cross-chain proposal management and voting:

    Understanding the Encryption SDK

    The magic of Secret Network's encryption SDK happens , with handle_encrypted_wrapper:

    The Extension variant inExecuteMsg leverages the functionality of handle_encrypted_wrapper because the wrapper decrypts and verifies the entire message payload before the Extension message is processed. Here’s how it works:

    1. handle_encrypted_wrapper Applies to the Entire Input:

      • When the execute function is called, the msg and info parameters are initially encrypted.

      • handle_encrypted_wrapper

    Frontend Encryption Logic

    Now that you understand how the encryption SDK functions, let's look how it's connected to the frontend. The Next.js encryption logic can be found in :

    :

    :

    Both of these functions access a confidential voting contract already deployed on Secret Network (at the end of this tutorial, you will learn how to deploy your own).

    Then, we call the execute_gateway_contract function, which is where all of the cross-chain SDK logic is implemented using IBC hooks:

    You can further examine sendIBCToken and gatewayChachaHookMemo in the CCL-SDK ibc.ts file .

    Query Messages

    There are two types of queries using the CCL SDK:

    1. Unauthenticated queries ie Extension, which are queries that don’t require sensitive or protected data. Example: Retrieving a list of proposals or votes, which is public.

    2. Authenticated queries ie Inner Queries (WithPermit, WithAuthData), which are queries that require auth_data from the caller for permission validation. Example: MyVote { proposal_id } retrieves a user-specific vote.

    To query encrypted votes using the CCL SDK, use the enum , which wraps the possible queries in the voting smart contract, namely, MyVote, with the CCL SDK encryption logic:

    InnerMethods leverages the SDK by using the types to query encrypted cross-chain votes. You have the choice of using two different types of encrypted queries: query_with_auth_data and query_with_permit. In this tutorial, you we will learn how to implement query_with_permit.

    1. :

    1. :

    Now let's take a look at the frontend code to see how query_with_permit is implemented. πŸ˜„

    Frontend Query Logic

    The Next.js query decryption logic can be found in :

    :

    :

    Because votes are encrypted, we must decrypt them in order to query a wallet's votes. The frontend function securely queries the Secret smart contract using query permits, ensuring that only authorized users can access sensitive data. Query permits are cryptographic credentials that:

    • Prove the user’s ownership of a wallet.

    • Allow contracts to verify the user’s identity.

    • Avoid exposing the private key.

    You now should have all the tools you need to use the IBC CCL toolkit! Lastly, let's learn how to deploy your own voting contract on Secret Network πŸ‘

    How to upload a voting contract to Secret Network

    cd into deploy-scripts and install the dependencies:

    Add your wallet mnemonic to . Then compile the contract:

    The compile script requires to be open for successful compilation

    Compile the typescript upload script so you can upload the compiled voting contract:

    Once you run the above command, the typescript upload file in ./src will be compiled as javascript file in ./dist.

    Upload and instantiate the voting contract on Secret Network Mainnet:

    In your terminal, a codeID, codeHash, and contractAddress will be returned:

    Finally, update with your contract's code_hash and address:

    Conclusion

    Congratulations on completing this on using Secret Network's IBC SDK to encrypt votes cross-chain using Secret smart contracts! πŸŽ‰ You've explored the intricacies of encrypted messaging, cross-chain IBC interactions, and secure smart contract execution using the Secret Network CCL SDK. By building and running the fullstack application, you’ve gained hands-on experience in contract deployment, frontend integration, and secure querying with query permits. πŸš€

    is invoked with these parameters:
  • This function decrypts and verifies the wrapper (encrypted payload) to produce:

    • A decrypted msg (of type ExecuteMsg).

    • A decrypted info (with verified sender details).

  • Decrypted msg Can Contain ExecuteMsg::Extension:

    • After decryption, the msg can match any variant of ExecuteMsg, including ExecuteMsg::Extension.

    • For example:

    • Here, Extension contains an additional layer of messages (InnerMethods) which define specific functionality.

  • How handle_encrypted_wrapper Affects Extension:

    • The msg inside ExecuteMsg::Extension (i.e., InnerMethods) is also encrypted in the original input.

    • handle_encrypted_wrapper ensures:

      • The outer msg is decrypted (revealing Extension).

      • The inner data (e.g., InnerMethods) is now in cleartext and ready for logical execution.

    • Without this decryption, the contract could not access or process InnerMethods within the Extension.

  • Validation and Security:

    • By verifying and decrypting the entire payload at the wrapper level, the contract ensures:

      • The Extension message is authentic and unaltered.

      • The sender (info) is authenticated and valid.

      • Any operations within InnerMethods (like creating proposals or voting) are authorized based on the decrypted info and secure data.

  • Extended Queries

    Inner Queries

    Data Access Level

    Public or general data

    Private or user-specific data

    Authorization

    No extra authentication required

    Requires auth_data or permit

    Processing Function

    query::query_extended

    query::query_with_auth_data

    Use Cases

    Public info like proposals, votes

    Personal data like a user’s vote

    here
    here
    here
    create
    environment variables
    InnerMethods
    CreateProposal
    Vote
    GatewayExecuteMsg
    here
    Gateway.ts
    Create Proposal
    Vote on Proposal
    here
    InnerQueries
    GatewayQueryMsg
    WithAuthData
    WithPermit
    Gateway.ts
    Query proposals
    Query votes
    query_contract_auth
    .env
    Docker
    config.ts
    Fullstack Confidential Voting on Secret Network

    Sealed Bid Auctions

    A fullstack tutorial for cross-chain confidential sealed bid auctions on IBC-connected chains.

    Overview

    This tutorial explains how to upload a confidential sealed bid auction contract on Secret Network, which you can execute and query for private auctions on any IBC-connected chain. πŸš€

    In this example, you will learn how to deploy a confidential auction contract on Secret Network which you will execute from Osmosis mainnet.

    The SDK abstracts IBC interactions with the Secret Network for applications that use Cosmos wallets. It introduces a secure method for generating confidential messages and reliably authenticating users at the same time using the chacha20poly1305 algorithm.

    git clone https://github.com/writersblockchain/cosmos-ccl-encrypted-payloads-demo
    cd next-frontend && npm install
    NEXT_PUBLIC_SECRET_CHAIN_ENDPOINT="https://lcd.mainnet.secretsaturn.net"
    NEXT_PUBLIC_SECRET_CHAIN_ID="secret-4"
    NEXT_PUBLIC_CONSUMER_CHAIN_ID="osmosis-1"
    npm run dev
    #[cw_serde]
    pub enum InnerMethods {
        CreateProposal {
            name: String,
            description: String,
            end_time: Uint64
        },
    
        Vote {
            proposal_id: Uint64,
            vote: VoteOption
        },
    }
    
    pub type ExecuteMsg   =   GatewayExecuteMsg<InnerMethods>;
    #[cw_serde]
    pub enum ExecuteMsg {
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        }
    
        ResetEncryptionKey  { },
    
        Extension {
            msg: {
                CreateProposal { ... }   /   Vote { ... },   
            }
        }
    }
    let (
            msg, 
            info
        ) = sdk::handle_encrypted_wrapper(
            deps.api, deps.storage, info, msg
        )?;
      const create_proposal = async (name: string, description: string, end_time: string = "60") => {
        const contract = contractConfig.votes;
        const msg = { create_proposal: { name, description, end_time } }
        return await execute_gateway_contract(contract, msg);
      }
     const vote_proposal = async (proposal_id: string, vote: string) => {
        const contract = contractConfig.votes;
        const msg = { vote: { proposal_id, vote } }
        return await execute_gateway_contract(contract, msg);
      }
    const execute_gateway_contract = async (contract: Contract, msg: object) => {
        const ibcConfig = loadIbcConfig(chainId);
        const keplrOfflineSigner = (window as any).getOfflineSigner(chainId);
    
        const response = await sendIBCToken(
          cosmosjs!,
          keplrAddress!,
          contract.address,
          token!,
          "1",
          ibcConfig.consumer_channel_id,
          await gatewayChachaHookMemo(
            keplrOfflineSigner,
            { extension: { msg } },
            chainId!,
            contract,
          )
        );
        return response;
      };
    #[cw_serde]
    pub enum ExtendedQueries {
        Proposals {},
        Proposal { proposal_id: u64 },
        AllVotes { proposal_id: u64 },
    }
    
    #[cw_serde]
    pub enum InnerQueries {
        MyVote { proposal_id: u64 },
    }
    
    pub type QueryMsg   =   GatewayQueryMsg<InnerQueries, sdk::CosmosAuthData, ExtendedQueries>;
    pub fn query_with_auth_data(
        deps        :   Deps, 
        env         :   Env, 
        auth_data   :   CosmosAuthData,
        query       :   InnerQueries
    ) -> StdResult<Binary> {
        auth_data.verify(deps.api)?;
        auth_data.check_data(deps.storage, &env)?;
        let address = auth_data.primary_address()?;
        query_inner(deps, env,address, query)
    }
    pub fn query_with_permit(
        deps        :   Deps, 
        env         :   Env, 
        permit      :   Permit,
        hrp         :   Option<String>,
        query       :   InnerQueries
    ) -> StdResult<Binary> {
        let address = secret_toolkit::permit::validate(
            deps, 
            PERMIT_PREFIX, 
            &permit, 
            env.contract.address.to_string(), 
            hrp.as_deref()
        )?;
        query_inner(deps, env, address, query)
    }
    const query_proposals = (): Promise<[number, Proposal][]> => {
        return query_contract_public(contractConfig.votes, { extension: { query: { proposals: { } } } });
      }
    const query_my_vote = (proposal_id: number, message?: string) => {
        return query_contract_auth(contractConfig.votes, { my_vote: { proposal_id } }, message);
      }
    const query_contract_auth = async (
        contract: Contract,
        query: object,
        data: string = "Query Permit"
      ): Promise<any> => {
    
        const storageKey = `${keplrAddress}:${contract.address}:queryPermit}`;
        const queryPermitStored = localStorage.getItem(storageKey);
    
        let credential: CosmosCredential;
    
        if (queryPermitStored) {
          credential = JSON.parse(queryPermitStored) as CosmosCredential;
        } else {
          const toSign: DataToSign = {
            chain_id: "secret-4",
            contract_address: contract.address,
            nonce: toBase64(Random.getBytes(32)),
            data: btoa(data)
          }
          const message = toUtf8(JSON.stringify(toSign));
          const signRes = await (window as any).keplr.signArbitrary(chainId, keplrAddress!, JSON.stringify(toSign))
          credential = {
            message: toBase64(message),
            signature: signRes.signature,
            pubkey: signRes.pub_key.value,
            hrp: keplrAddress!.split("1")[0]
          }
          localStorage.setItem(storageKey, JSON.stringify(credential));
        }
        const res = await queryGatewayAuth(contract, query, [credential]);
        console.log("query:", query, " res:", res);
        return res;
      }
    cd deploy-scripts && npm install
    make build-mainnet-reproducible
    npm run build
    node dist/deploy_voting.js
    "code_id": 8882,
    "code_hash": "f3c2e28cd1574d128ded60ce967cdb46f7515d807be49127bcc9249c5fd97802",
    "address": "secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm"
    const contractMultiConfig: ContractMultiConfig = {
        votes: {
            address: "secret1jtc7f8cj5hhc2mg9v5uknd84knvythvsjhd66a",
            hash: "ff8443878e8a339637c45c13abc4385c4f0c5668b992afc912e5f59e5d098654"
        },
    };
     let (msg, info) = sdk::handle_encrypted_wrapper(deps.api, deps.storage, info, msg)?;
    ExecuteMsg::Extension { msg } => {
        match msg {
            InnerMethods::CreateProposal { name, description, end_time } => { /* logic */ },
            InnerMethods::Vote { proposal_id, vote } => { /* logic */ },
        }
    },

    View the typescript SDK here, which we will learn how to implement shortly πŸŽ‰

    In this tutorial you will learn:

    1. How to run the fullstack cross-chain Next.js application in your browser.

    2. How to use the core smart contract logic for confidential cross-chain auctions using IBC hooks.

    3. How to deploy your very own auction contract on Secret Network.

    See a fullstack demo here!

    The only requirement for using Secret Network's IBC toolkit is an existing IBC transfer channel. πŸ€“ You can view existing channels here or create a channel of your own.

    Getting Started

    Clone the repo:

    cd into next-frontend and install the dependencies:

    Add environment variables in cosmos-ccl-encrypted-payloads-demo/next-frontend:

    Start the application:

    The fullstack Next.js application should now be running in your browser! You can use it to create confidential auctions and bids. Now that you have the application running in your browser, let's dive into the smart contract logic!

    Understanding the contract

    Execution Messages

    To encrypt auction bids using the SDK, we use the enum called InnerMethods, which wraps the possible actions in the auction smart contract, namely, CreateAuctionItem and Bid, with the SDK encryption logic:

    InnerMethods leverage the SDK by using the GatewayExecuteMsg to structure encrypted execution messages for cross-chain auction management and bidding:

    Understanding the Encryption SDK

    The magic of Secret Network's encryption SDK happens here, with handle_encrypted_wrapper:

    The Extension variant inExecuteMsg leverages the functionality of handle_encrypted_wrapper because the wrapper decrypts and verifies the entire message payload before the Extension message is processed. Here’s how it works:

    1. handle_encrypted_wrapper Applies to the Entire Input:

      • When the execute function is called, the msg and info parameters are initially encrypted.

      • handle_encrypted_wrapper is invoked with these parameters:

      • This function decrypts and verifies the wrapper (encrypted payload) to produce:

        • A decrypted msg (of type ExecuteMsg).

        • A decrypted info (with verified sender details).

    2. Decrypted msg Can Contain ExecuteMsg::Extension:

      • After decryption, the msg can match any variant of ExecuteMsg, including ExecuteMsg::Extension.

    3. How handle_encrypted_wrapper Affects Extension:

      • The msg inside ExecuteMsg::Extension (i.e., InnerMethods) is also encrypted in the original input.

    4. Validation and Security:

      • By verifying and decrypting the entire payload at the wrapper level, the contract ensures:

        • The Extension message is authentic and unaltered.

    Frontend Encryption Logic

    Now that you understand how the encryption SDK functions, let's look how it's connected to the frontend. The Next.js encryption logic can be found in Gateway.ts:

    Create Auction:

    Bid on Auction:

    Both of these functions access a confidential auction contract already deployed on Secret Network (at the end of this tutorial, you will learn how to deploy your own).

    Then, we call the execute_gateway_contract function, which is where all of the cross-chain SDK logic is implemented using IBC hooks:

    You can further examine sendIBCToken and gatewayChachaHookMemo in the CCL-SDK ibc.ts file here.

    Query Messages

    There are two types of queries using the CCL SDK:

    1. Unauthenticated queries ie Extension, which are queries that don’t require sensitive or protected data. Example: Retrieving a list of auctions, which is public.

    2. Authenticated queries ie Inner Queries (WithPermit, WithAuthData), which are queries that require auth_data from the caller for permission validation. Example: MyBid { auction_id: u64 } retrieves a user-specific bid.

    Extended Queries

    Inner Queries

    Data Access Level

    Public or general data

    Private or user-specific data

    Authorization

    No extra authentication required

    Requires auth_data or permit

    Processing Function

    query::query_extended

    query::query_with_auth_data

    Use Cases

    Public info like auctions

    Personal data like a user’s bids

    To query encrypted bids using the CCL SDK, use the enum InnerQueries, which wraps the possible queries in the auction smart contract, namely, MyBid, with the CCL SDK encryption logic:

    InnerMethods leverages the SDK by using the GatewayQueryMsg types to query encrypted cross-chain bids. You have the choice of using two different types of encrypted queries: query_with_auth_data and query_with_permit. In this tutorial, you we will learn how to implement query_with_permit.

    1. WithAuthData:

    1. WithPermit:

    Now let's take a look at the frontend code to see how query_with_permit is implemented. πŸ˜„

    Frontend Query Logic

    The Next.js query decryption logic can be found in Gateway.ts:

    Query auctions:

    Query bids:

    Because bids are encrypted, we must decrypt them in order to query a wallet's bids. The frontend function query_contract_auth securely queries the Secret smart contract using query permits, ensuring that only authorized users can access sensitive data. Query permits are cryptographic credentials that:

    • Prove the user’s ownership of a wallet.

    • Allow contracts to verify the user’s identity.

    • Avoid exposing the private key.

    You now should have all the tools you need to use the IBC CCL toolkit! Lastly, let's learn how to deploy your own auction contract on Secret Network πŸ‘

    How to upload a voting contract to Secret Network

    cd into deploy-scripts and install the dependencies:

    Add your wallet mnemonic to .env. Then compile the contract:

    The compile script requires Docker to be open for successful compilation

    Compile the typescript upload script so you can upload the compiled vauction contract:

    Once you run the above command, the typescript upload file in ./src will be compiled as javascript file in ./dist.

    Upload and instantiate the voting contract on Secret Network Mainnet:

    In your terminal, a codeID, codeHash, and contractAddress will be returned:

    Finally, update config.ts with your contract's code_hash and address:

    Conclusion

    Congratulations on completing this on using Secret Network's IBC SDK to encrypt auction bids cross-chain using Secret Network smart contracts! πŸŽ‰ You've explored the intricacies of encrypted messaging, cross-chain IBC interactions, and secure smart contract execution using the Secret Network CCL SDK. By building and running the fullstack IBC application, you’ve gained hands-on experience in contract deployment, frontend integration, and secure querying with Secret Network query permits. πŸš€

    IBC Relaying with Go Relayer

    Learn how to run the Go relayer to create a transfer channel between any Cosmos chain and Secret Network.

    Overview

    The Go relayer is a relayer implementation written in Golang. It can create clients, connections, and channels, as well as relay packets and update and upgrade clients.

    In order to use Secret Network's IBC Developer Toolkit, you need an IBC transfer channel established between Secret Network and your Cosmos chain.

    In this section, you will learn:

    • How to get started with the Go relayer.

    • Basic Go relayer commands.

    • How to create a transfer channel between Secret Network testnet and Neutron testnet.

    Let's get started!

    Installing Go Relayer

    Clone the :

    Install Go:

    Set Go path:

    Build the Go relayer:

    If you run into any errors during installation, you can install without make like so:

    To check that the installation was successful, run:

    Which returns:

    Configuring Go Relayer

    The configuration data is added to the config file, stored at $HOME/.relayer/config/config.yaml by default.

    If this is the first time you run the relayer, first initialize the config with the following command:

    And check the config with:

    Now you are all set to add the chains and paths you want to relay on, add your keys and start relaying. You will set up two testnet chains: Neutron's pion-1 and Secret Network's pulsar-3.

    Add chain configs

    The rly chains add command fetches chain metadata from the and adds it to your config file:

    rly chains add will check the liveliness of the available RPC endpoints for that chain in the chain registry. The command may fail if none of these RPC endpoints are available. In this case, you will want to manually add the chain config.

    Create wallet keys

    Create new keys for the relayer to use when signing and relaying transactions:

    Query your key balances:

    You can fund your Secret Network testnet wallet and your Neutron testnet wallet πŸŽ‰

    Then, edit the relayer's key values in the config file to match the key-names chosen above.

    The configuration data is added to the config file, stored at $HOME/.relayer/config/config.yaml:

    You can unveil hidden folders on mac (ie ./relayer) with keyboard shortcut : Command + Shift + .

    Configure path metadata in the config file

    You configured the chain metadata, now you need path metadata.

    Update your config file like so to use a configuration path that has been tested in production:

    Create a relayer path:

    Check Configuration Status

    Before starting to relay and after making some changes to the config, you can check the status of the chains in the config:

    Which returns this output when healthy:

    And you can check the status of the paths in the config:

    In case one of the checks receives a ✘ instead of βœ”, you will need to check if you completed all the previous steps correctly.

    Starting the Relayer

    Finally, start the relayer on the desired path. The relayer will periodically update the clients and listen for IBC messages to relay:

    Congrats! You are now relaying between Secret Network testnet and Neutron testnet! πŸŽ‰

    Further reading:

    git clone https://github.com/writersblockchain/cosmos-ccl-encrypted-payloads-demo
    cd next-frontend && npm install
    NEXT_PUBLIC_SECRET_CHAIN_ENDPOINT="https://lcd.mainnet.secretsaturn.net"
    NEXT_PUBLIC_SECRET_CHAIN_ID="secret-4"
    NEXT_PUBLIC_CONSUMER_CHAIN_ID="osmosis-1"
    npm run dev
    #[cw_serde]
    pub enum InnerMethods {
        CreateAuctionItem {
            name: String,
            description: String,
            end_time: Uint64
        },
    
        Bid {
            auction_id: Uint64,
            amount: Uint128
        },
    }
    
    pub type ExecuteMsg                 =   GatewayExecuteMsg<InnerMethods>;
    #[cw_serde]
    pub enum ExecuteMsg {
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        }
    
        ResetEncryptionKey  { },
    
        Extension {
            msg: {
                CreateAuctionItem { ... }   /   Bid { ... },   
            }
        }
    }
    let (
            msg, 
            info
        ) = sdk::handle_encrypted_wrapper(
            deps.api, deps.storage, info, msg
        )?;
     const create_auction = async (name: string, description: string, end_time: string = "60") => {
        const contract = contractConfig.auctions;
        const msg = { create_auction_item: { name, description, end_time } }
        return await execute_gateway_contract(contract, msg);
      }
      const bid_auction = async (auction_id: string, amount: string) => {
        const contract = contractConfig.auctions;
        const msg = { bid: { auction_id, amount } }
        return await execute_gateway_contract(contract, msg);
      }
    const execute_gateway_contract = async (contract: Contract, msg: object) => {
        const ibcConfig = loadIbcConfig(chainId);
        const keplrOfflineSigner = (window as any).getOfflineSigner(chainId);
    
        const response = await sendIBCToken(
          cosmosjs!,
          keplrAddress!,
          contract.address,
          token!,
          "1",
          ibcConfig.consumer_channel_id,
          await gatewayChachaHookMemo(
            keplrOfflineSigner,
            { extension: { msg } },
            chainId!,
            contract,
          )
        );
        return response;
      };
    #[cw_serde]
    pub enum ExtendedQueries {
        Auctions {},
        Auction { auction_id: u64 },
        AllBids { auction_id: u64 },
        Result { auction_id: u64 },
    }
    
    #[cw_serde]
    pub enum InnerQueries {
        MyBid { auction_id: u64 },
    }
    
    pub type ExecuteMsg                 =   GatewayExecuteMsg<InnerMethods>;
    pub type QueryMsg                   =   GatewayQueryMsg<InnerQueries, CosmosAuthData, ExtendedQueries>
    pub fn query_with_auth_data(
        deps        :   Deps, 
        env         :   Env, 
        auth_data   :   CosmosAuthData,
        query       :   InnerQueries
    ) -> StdResult<Binary> {
        auth_data.verify(deps.api)?;
        auth_data.check_data(deps.storage, &env)?;
        let address = auth_data.primary_address()?;
        query_inner(deps, env,address, query)
    }
    pub fn query_with_permit(
        deps        :   Deps, 
        env         :   Env, 
        permit      :   Permit,
        hrp         :   Option<String>,
        query       :   InnerQueries
    ) -> StdResult<Binary> {
        let address = secret_toolkit::permit::validate(
            deps, 
            PERMIT_PREFIX, 
            &permit, 
            env.contract.address.to_string(), 
            hrp.as_deref()
        )?;
        query_inner(deps, env, address, query)
    }
      const query_auctions = () => {
        return query_contract_public(contractConfig.auctions, { extension: { query: { auctions: { } } } });
      }
     const query_my_bid = (auction_id: number, message?: string) => {
        return query_contract_auth(contractConfig.auctions, { my_bid: { auction_id } }, message);
      }
    const query_contract_auth = async (
        contract: Contract,
        query: object,
        data: string = "Query Permit"
      ): Promise<any> => {
    
        const storageKey = `${keplrAddress}:${contract.address}:queryPermit}`;
        const queryPermitStored = localStorage.getItem(storageKey);
    
        let credential: CosmosCredential;
    
        if (queryPermitStored) {
          credential = JSON.parse(queryPermitStored) as CosmosCredential;
        } else {
          const toSign: DataToSign = {
            chain_id: "secret-4",
            contract_address: contract.address,
            nonce: toBase64(Random.getBytes(32)),
            data: btoa(data)
          }
          const message = toUtf8(JSON.stringify(toSign));
          const signRes = await (window as any).keplr.signArbitrary(chainId, keplrAddress!, JSON.stringify(toSign))
          credential = {
            message: toBase64(message),
            signature: signRes.signature,
            pubkey: signRes.pub_key.value,
            hrp: keplrAddress!.split("1")[0]
          }
          localStorage.setItem(storageKey, JSON.stringify(credential));
        }
        const res = await queryGatewayAuth(contract, query, [credential]);
        console.log("query:", query, " res:", res);
        return res;
      }
    cd deploy-scripts && npm install
    make build-mainnet-reproducible
    npm run build
    node dist/deploy_auction.js
    "code_id": 8882,
    "code_hash": "f3c2e28cd1574d128ded60ce967cdb46f7515d807be49127bcc9249c5fd97802",
    "address": "secret1q0mycclu927u5m0tn50zgl5af4utrlkzz706lm"
    const contractMultiConfig: ContractMultiConfig = {
        auctions: {
            address: "secret1jtc7f8cj5hhc2mg9v5uknd84knvythvsjhd66a",
            hash: "ff8443878e8a339637c45c13abc4385c4f0c5668b992afc912e5f59e5d098654"
        },
    };
    πŸš€
    Go relayer repository
    chain registry
    here
    here
    IBC Go Relayer docs
    Cosmos Go Relayer docs
    Creating paths across chains
    For example:
  • Here, Extension contains an additional layer of messages (InnerMethods) which define specific functionality.

  • handle_encrypted_wrapper ensures:
    • The outer msg is decrypted (revealing Extension).

    • The inner data (e.g., InnerMethods) is now in cleartext and ready for logical execution.

  • Without this decryption, the contract could not access or process InnerMethods within the Extension.

  • The sender (
    info
    ) is authenticated and valid.
  • Any operations within InnerMethods (like bidding) are authorized based on the decrypted info and secure data.

  • git clone https://github.com/cosmos/relayer.git
    brew install go
    export GOPATH=$HOME/go
    export PATH=$PATH:$GOPATH/bin
    cd relayer
    make install
    export GOBIN=$HOME/go/bin
    mkdir -p $GOBIN
    
    go clean -cache
    
    go build -ldflags "-X github.com/cosmos/relayer/v2/cmd.Version=$(git describe --tags | sed 's/^v//') \
    -X github.com/cosmos/relayer/v2/cmd.Commit=$(git log -1 --format='%H') \
    -X github.com/cosmos/relayer/v2/cmd.Dirty=$(git status --porcelain | wc -l | xargs)" \
    -o $GOBIN/rly main.go
    rly version
    version: 2.6.0-rc.1
    commit: 3b9ec008999973469aeab4bbdbcb44ff4886b8b8
    cosmos-sdk: v0.50.5
    go: go1.23.4 darwin/arm64
    rly config init
    rly config show
    rly chains add testnets/secretnetworktestnet
    rly chains add testnets/neutrontestnet
    rly keys add secretnetworktestnet secret-test #this is the name of your key
    rly keys add neutrontestnet neutron-test #this is the name of your key
    rly query balance secretnetworktestnet secret-test
    rly query balance neutrontestnet neutron-test
    chains:
        neutrontestnet:
            type: cosmos
            value:
                key-directory: /Users/yourname/.relayer/keys/pion-1
                key: neutron-test
                chain-id: pion-1
                rpc-addr: https://rpc-lb-pion.ntrn.tech:443
                
         secretnetworktestnet:
            type: cosmos
            value:
                key-directory: /Users/yourname/.relayer/keys/pulsar-3
                key: secret-test
                chain-id: pulsar-3
                rpc-addr: https://pulsar.rpc.secretnodes.com:443
    global:
        debug-listen-addr: 127.0.0.1:5183
        metrics-listen-addr: 127.0.0.1:5184
        timeout: 10s
        memo: ""
        light-cache-size: 20
        log-level: info
        ics20-memo-limit: 0
        max-receiver-size: 150
    chains:
        neutrontestnet:
            type: cosmos
            value:
                key-directory: /Users/<your-user-name>/.relayer/keys/pion-1
                key: neutron-test
                chain-id: pion-1
                rpc-addr: https://rpc-lb-pion.ntrn.tech:443
                backup-rpc-addrs: []
                account-prefix: neutron
                keyring-backend: test
                dynamic-gas-price: true
                gas-adjustment: 2
                gas-prices: 0.043untrn
                min-gas-amount: 400000
                max-gas-amount: 500000
                debug: false
                timeout: 20s
                block-timeout: ""
                output-format: json
                sign-mode: direct
                extra-codecs: []
                coin-type: 118
                signing-algorithm: ""
                broadcast-mode: batch
                min-loop-duration: 0s
                extension-options: []
                feegrants: null
        secretnetworktestnet:
            type: cosmos
            value:
                key-directory: /Users/<your-user-name>/.relayer/keys/pulsar-3
                key: secret-test
                chain-id: pulsar-3
                rpc-addr: https://pulsar.rpc.secretnodes.com:443
                backup-rpc-addrs:
                    - https://pulsar.rpc.secretnodes.com:443
                account-prefix: secret
                keyring-backend: test
                dynamic-gas-price: false
                gas-adjustment: 1.2
                gas-prices: 0.1uscrt
                min-gas-amount: 400000
                max-gas-amount: 500000
                debug: false
                timeout: 20s
                block-timeout: ""
                output-format: json
                sign-mode: direct
                extra-codecs: []
                coin-type: 529
                signing-algorithm: ""
                broadcast-mode: batch
                min-loop-duration: 0s
                extension-options: []
                feegrants: null
    rly paths new pulsar-3 pion-1 my-path #this is the name of your path 
    rly chains list
    0: pulsar-3          -> type(cosmos) key(βœ”) bal(βœ”) path(βœ”)
    1: pion-1            -> type(cosmos) key(βœ”) bal(βœ”) path(βœ”)
    rly paths list
    0: secretnetworktestnet-nuetrontestnet -> chns(βœ”) clnts(βœ”) conn(βœ”) (pulsar-3<>pion-1)
    rly start my-path 
    2024-12-13T17:40:56.021378Z     info    Chain is in sync        {"chain_name": "neutrontestnet", "chain_id": "pion-1"}
    2024-12-13T17:41:01.999823Z     info    Client update threshold condition met   {"path_name": "my_demo_path", "chain_id": "pion-1", "client_id": "07-tendermint-543", "trusting_period": 72000000, "time_since_client_update": 85721551, "client_threshold_time": 0}
     let (msg, info) = sdk::handle_encrypted_wrapper(deps.api, deps.storage, info, msg)?;
    ExecuteMsg::Extension { msg } => {
        match msg {
            InnerMethods::CreateAuctionItem { name, description, end_time } => { /* logic */ },
            InnerMethods::Bid { auction_id, 
                        amount  } => { /* logic */ },
        }
    },