Integration Guide
Contract-level

Contract-level resolving

In addition to using the JS/TS libraries we provide, you can also call our Router functions directly via ink! in Rust. The examples below primarily focus on resolving a domain to a wallet address but can be extended to other integrations as well.

💡

Important: For most use cases, we recommend using our JS/TS libraries for resolving domains.

Recommended

Crate integration

We are providing an integration crate (opens in a new tab) to abstract away the complexities that come with performing cross-contract calls. Continue below to learn how to call the contract manually instead.

Step undefined

Dependency configuration

Add our integration crate to your contract's Cargo.toml file.

Cargo.toml
[dependencies]
azns-integration = { git = "https://github.com/azero-id/contract-integration", default-features = false }
 
[features]
std = [
    "azns-integration/std",
    ..
]

Step undefined

Resolving

Store the router address and import the type AccountIdOrDomain

lib.rs
#[ink::contract]
mod example {
    // Import `AccountIdOrDomain` type
    use azns_integration::AccountIdOrDomain;
 
    #[ink(storage)]
    pub struct Example {
        // Store AZERO-ID's router-contract address
        domain_router: AccountId,
    }
 
    impl Example {
        #[ink(constructor)]
        pub fn new(domain_router: AccountId) -> Self {
            Self { domain_router }
        }
 
        /// Returns the AccountId associated with the `user`
        #[ink(message)]
        pub fn simple_integration(&self, user: AccountIdOrDomain) -> Option<AccountId> {
            user.get_address(self.domain_router) // Resolves user to AccountId
        }
    }
}

Check out this example (opens in a new tab) in the crate repository.

Custom enum type AccountIdOrDomain

Allows users to pass either their AccountId or an AZERO.ID Domain as a message parameter.

pub enum AccountIdOrDomain {
    AccountId(AccountId),
    Domain(String),
}

Not Recommended

Manual integration

Using a low-level call

You can make low-level calls directly, for example, if you want to get the wallet address corresponding to a domain, you can use AznsRouter::get_address to get it:

lib.rs
#[ink(message)]
pub fn get_address(&self, router_addr: AccountId, domain: String) -> Option<AccountId> {
    match cfg!(test) {
        true => unimplemented!(
            "`invoke_contract()` not being supported (tests end up panicking)"
        ),
        false => {
            use ink::env::call::{build_call, ExecutionInput, Selector};
 
            // const GET_ADDRESS_SELECTOR: [u8; 4] = [0xD2, 0x59, 0xF7, 0xBA];
            const GET_ADDRESS_SELECTOR: [u8; 4] = ink::selector_bytes!("get_address");
 
            let result = build_call::<Environment>()
                .call(router_addr)
                .exec_input(
                    ExecutionInput::new(Selector::new(GET_ADDRESS_SELECTOR))
                        .push_arg(domain),
                )
                .returns::<core::result::Result<AccountId, u8>>()
                .params()
                .invoke();
 
            // Use the result as per the need
            result.ok()
        }
    }
}

Using a trait

You can also use a more abstract approach, wrapping the low-level call into a trait. Getting the resolving wallet address corresponding to a domain name is similar to the above example:

azns_router.rs
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
    /// Caller is not allowed to call privileged calls.
    NotAdmin,
    /// Not a contract address
    InvalidRegistryAddress,
    /// Given TLD already points to a registry
    TldAlreadyInUse(String),
    /// Given Tld not found
    TldNotFound(String),
    /// Cannot find the resolved address
    CouldNotResolveDomain,
    /// Domain does not contain valid name and/or tld
    InvalidDomainName,
}
 
#[ink::trait_definition]
pub trait AznsContract {
    #[ink(message, selector = 0xe6da7bf0)]
    fn get_all_registries(&self) -> Vec<AccountId>;
 
    #[ink(message, selector = 0x15a5d20a)]
    fn get_registry(&self, tld: String) -> Option<AccountId>;
 
    #[ink(message, selector = 0xd259f7ba)]
    fn get_address(&self, domain: String) -> Result<AccountId, Error>;
 
    #[ink(message, selector = 0xdf3a358e)]
    fn get_primary_domains(
        &self,
        account: AccountId,
        tld: Option<String>,
    ) -> Vec<(AccountId, String)>;
}
lib.rs
mod azns_router;
 
use crate::azns_router::{
    AznsContract,
    Error as AznsRouterError,
};
 
#[ink(message)]
pub fn get_address(&self, router_addr: AccountId, domain: String) -> Result<AccountId, AznsRouterError> {
    // Can also store it in contract storage
    let router: ink::contract_ref!(AznsContract) = router_addr.into();
 
    router.get_address(domain)
}

How to get the selector

Selectors in ink! help developers identify constructors and messages, and consist of four hexadecimal bytes. As shown in the examples above, you need to specify the selector_id in the macro before the contract function. Read more in the official ink! documentation here (opens in a new tab).

There are several ways to get the selector bytes:

Evaluating selector bytes within the contract (recommended)

Call ink::selector_bytes each time in the contract.

const SELECTOR_ID: [u8; 4] = ink::selector_bytes!("get_all_registries");
Get it from the metadata

Open the ABI/metadata JSON file of the corresponding contract and search for the function name.

{
  "args": [],
  "default": false,
  "docs": [],
  "label": "get_all_registries",
  "mutates": false,
  "payable": false,
  "returnType": {
    "displayName": ["ink", "MessageResult"],
    "type": 12
  },
  "selector": "0xe6da7bf0" // ← This is the selector id
}
Calculate using BLAKE2

Call BLAKE2 and then take the first four bytes (i.e. 0xe6da7bf0 in the example below).

BLAKE2("get_all_registries") = 0xe6da7bf0bde8f5e77ee5362f258c76034d2b474b45b1b7382872f7130e0f2f89

Example usage

The implementation below is independent of our protocol, but rather a proof of concept of how to use contract-level resolving with a unified AccountIdOrDomain enum type.

lib.rs
#[derive(scale::Encode, scale::Decode, PartialEq)]
#[cfg_attr(
    feature = "std",
    derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout, Debug)
)]
pub enum AccountIdOrDomain {
    AccountId(AccountId),
    Domain(String),
}
 
#[ink(message)]
pub fn get_address(&self, router_addr: AccountId, id: AccountIdOrDomain) -> Result<AccountId, AznsRouterError> {
    match id {
        AccountIdOrDomain::AccountId(addr) => Ok(addr),
        AccountIdOrDomain::Domain(domain) => {
            let router: ink::contract_ref!(AznsContract) = router_addr.into();
            router.get_address(domain)
        }
    }
}