Usage
Once the SDK is installed and the AirService
successfully initialized, it can be used to authenticate users. Further, the native provider given by the service instance can be used to let users interact with the blockchain.
The AirService
instance offers following:
init(…)
The init method should generally be called right after creating the AirService
instance.
login(…)
This will login the user with their Global ID.
getUserInfo()
Retrieves general user information.
goToPartner(...)
Share the user's session from your dApp to another AIR Kit-enabled dApp.
getProvider()
getAccessToken()
Returns the current access token or a new one in case of expiry.
preloadWallet()
Loads the wallet in the background. Needs the user to be logged in.
setupOrUpdateMfa()
Triggers the MFA enrollment screen where the user can set up or update the Passkey.
claimAirId(…)
After the user is logged in, this will start the mint flow. During this call the wallet will be loaded if not yet happened.
isSmartAccountDeployed()
Check if the AA has been deployed. During this call the wallet will be loaded if not yet happened.
deploySmartAccount()
Deploy the AA if not yet deployed. It will also automatically be deployed when minting an Air Id. During this call the wallet will be loaded if not yet happened.
logout()
Logs out the user and clears up the session
clearInit()
Clears the initialization of the AirService
on(…) / off(…)
To keep in sync with the service's state, you can subscribe to it and receive events such as initialized
, logged_in
, wallet_initialized
*, air_id_minting_started
*, air_id_minting_failed
* and logged_out
.
Login & Mint
login(options?: { authToken?: string; })
: Promise<AirLoginResult>
Without any parameters, this will trigger the default Air login dialog which provides different login methods for the user to choose from.
We recommend to always require passing in a signed JWT via the authToken
parameter with at minimum your partnerId
inside the payload. This would also trigger the default login but enhances security. If you're having your users already authenticated on your side, you can add the email
and partnerUserId
to the payload as well.
If an email is provided, we will verify via a one-time password sent to this email since it is used as identifier on our side.
An example JWT could look like following:
{
"partnerId": "YOUR PARTNER ID",
"partnerUserId": "YOUR USER ID", // optional
"email": "YOUR USER EMAIL", // optional
"exp": 1728973684,
"iat": 1728970084
}
In order to validate the JWTs on our end, we need to know your JWKS endpoint with following JSON:
{
"keys": [
{
"kty": "EC",
"use": "sig",
"alg": "ES256",
"kid": "{your_kid}",
"crv": "P-256",
"x": "{your_x}",
"y": "{your_y}"
}
]
}
The above JWKS should contain the public key only, but the JWT generated on your end needs to be signed with the private key.
After successful login an AirLoginResult
will be returned which also contains a property token
generated by us containing following information:
{
sub: string,
abstractAccountAddress: string,
partnerId: string,
partnerUserId?: string,
sourcePartnerId?: string
}
This token can be used in the future to query some of our partner endpoints.
User Session Across AIR Kit dApps
To maintain a user's logged in state across dApps in our ecosystem, we offer a convenient way to share the user's session from your dApp to another AIR Kit-enabled dApp.
From your dApp, call goToPartner(partnerUrl: string)
and pass in the target URL of the other dApp to receive an updated url which can be used to carry over the user. Once the user lands on the target dApp, the user session will automatically be rehydrated during SDK initialization.
Example:
const { urlWithToken } = await airService.goToPartner('https://www.partner-dapp.xyz/stake');
document.window.href = urlWithToken;
This method should be called at the time of navigating to the target url and not when displaying the link because the token attached to the url is short-lived. The target url also cannot be localhost
.
Additionally, the target dApp needs to make sure to allow automatic rehydration by not setting skipRehydration
to true
when calling init(...)
.
Handling MFA Requirements
In order to make use of any wallet functionality, the user needs to have set up MFA which currently requires Passkey. After the user has set up the Passkey, any wallet action which requires signing or confirming a transaction will require users to verify their Passkey. This ensures funds are protected and only accessible by the user.
By default, the MFA enrollment screen will automatically pop up whenever the user has not set up MFA yet and a wallet action is triggered. This also includes getting the accounts and balance checks.
The MFA screen can also be programmatically triggered by calling setupOrUpdateMfa()
before doing any wallet related actions if more control over the time of the MFA enrollment is needed.
The example below illustrates a potential way of handling the MFA setup:
const loggedInUser = await airService.login();
// The wallet initialization can take a few seconds, so we can preload the wallet
// in the background before we need it
airService.preloadWallet();
// Some time later...
// If the user has not set up MFA yet and we want to trigger it now, we can do
if(!loggedInUser.isMFASetup) {
await airService.setupOrUpdateMfa();
}
// At this point we're ready to use the wallet
As long as the user has not set up MFA, the wallet address will not be returned via login, user info or token.
Minting
Mint Settings
In case a partner wants to control certain mint behaviors (e.g. gated minting) a JWT token can be passed in with following properties:
{
"partnerId": "YOUR PARTNER ID",
"partnerUserId": "YOUR USER ID",
"eligibility": "none" | "normal" | "specific",
"mintName": "name" | null
}
Eligibility cases:
none
: User is not eligible to mint new names (can login only)
normal
: User can mint any name (except our internally reserved names)
specific
: User can only mint the specified name in mintName
Reserved Names
During the mint flow we are doing a profanity check on the name and also a check against our internal reserved names list. If a partner has its own reserved names list, we can call a specified url on the partner side to additionally check the eligibility of each mint.
The partners needs to provide following url and optionally api key and name:
GET partner_url
Header { [api_key_name]: api_key }
Query params:
partnerId
name
email?
walletAddress?
token?
Response:
{
"eligible": true | false;
}
We call the provided partner endpoint after we have done the profanity and reserved names check on our side. In case we cannot reach the endpoint, the mint request will be aborted.
Interact with blockchain
The provider returned from the service is a compatible Eip1193Provider
and can be used with web3, ethers, Viem, etc.
To interact with blockchain data, libraries such as Ethers.js, Viem, and Web3.js can be used. Here are some small snippets for signing, querying, and mutating simple functions on the blockchain.
Sample smart contract
Consider a sample ERC20 smart contract with a minimal setup, including balanceOf
, symbol
, and mint
functions
Make sure to replace {CONTRACT_ADDRESS}
and {CHAIN_ID}
with your corresponding values.
const MOCK_TOKEN_CONTRACT = {
address: "{CONTRACT_ADDRESS}",
abi: [
"function balanceOf(address account) view returns (uint256)",
"function symbol() view returns (string)",
"function mint(address to, uint256 amount)",
],
chainId: {CHAIN_ID},
};
Wallet client
const airProvider = await service.getProvider();
const walletClient = createWalletClient({
transport: custom(airProvider),
chain: baseSepolia,
});
Sign a Message
const airProvider = await service.getProvider();
const ethProvider = new BrowserProvider(airProvider, "any");
const signer = await ethProvider.getSigner();
const signedMessage = await signer.signMessage(message);
Query the Blockchain
const airProvider = await service.getProvider();
const ethProvider = new BrowserProvider(airProvider, "any");
const signer = await ethProvider.getSigner();
const contract = new ethers.Contract(
MOCK_TOKEN_CONTRACT.address,
MOCK_TOKEN_CONTRACT.abi,
signer
);
const result = await contract.balanceOf(await signer.getAddress());
Mutation (Send a Transaction)
const airProvider = await service.getProvider();
const ethProvider = new BrowserProvider(airProvider, "any");
const accounts = await ethProvider.listAccounts();
const signer = accounts[0]; // The first account will be the AA account address, and the second will be the EOA/MPC signer
const contractAddress = MOCK_TOKEN_CONTRACT.address;
const abi = MOCK_TOKEN_CONTRACT.abi;
const contract = new ethers.Contract(contractAddress, abi, signer);
const tx = await contract.mint(await signer.getAddress(), ethers.parseUnits(amount, 18)); // Assuming 18 decimals
await tx.wait(); // Wait for the transaction to be mined\
Last updated