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.
[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
#[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:
#[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:
#[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)>;
}
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.
#[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)
}
}
}