My project allows for users to authenticate with one or more wallets. I would like them to be able to set an ADA Handle they own as their username. What would be the best way to do this?

More specifically, I'm looking for a way to either:

  • query all ADA Handles owned by a staking address, or
  • verify that an ADA Handle belongs to a staking address

Added answer for what is possible now (without setting up a full node and db-sync). Can find handles associated with wallet, but there's no way to detect when the handles are moved to a different wallet. In 2 weeks, it should be possible with Tango, so I will complete my answer then :)

2 Answers 2


To me your plan sounds like the way to go. Just in case you missed it, theres some documentation here on how to resolve handles to addresses and get all handles by address:


But since you're specifically asking about stake addresses (which I think makes good sense for your case of user authentication), your approach should be better suited.

Also just to add you can find the official policy ids here (might make your life easier for testnet at least, since you already found the mainnet policy 😉)


  • 1
    Thanks, I did entirely miss those docs😅 I was checking on their discord for something like that, but couldn't find it Commented Apr 6, 2022 at 12:57

I've (sort of) figured it out now. This approach is able to query the Handles associated with a wallet. However, there doesn't seem to be a clean way to check when the Handle has been moved to a different wallet (besides querying again every x hours). For changes to be real time, we have 2 options:

  1. Instead of using Blockfrost, host a Cardano node and db-sync.
  2. Wait for Tango to release NFT webhooks, which are supposedly being released in 2 weeks!

Once I complete one of those options, I will update this answer to make it more useful.

With that disclaimer out of the way, here is the process:

  1. Create a Blockfrost API key and store it in your .env file. If you're using docker, you must also add this key as an environment variable to the correct container in docker-compose.yaml (or Dockerfile).
  2. Use this link from Sean's answer to find the correct policy ID for ADA Handles. I'm using mainnet, so this is f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a.
  3. Authenticate the user's wallet with your backend, and store the wallet's staking key in your database. I have detailed instructions here for how to do this.
  4. Convert the staking key to Bech32. I do this using cardano-serialization-lib, which was set up in step 1:
import * as Serialization from '@emurgo/cardano-serialization-lib-nodejs';
export const serializedAddressToBech32 = (address: string) => {
   const addressBytes = Serialization.Address.from_bytes(Buffer.from(address, 'hex'));
   return addressBytes.to_bech32();
const bech32Wallets = wallets.map(wallet => serializedAddressToBech32(wallet.stakingAddress));
  1. Query Blockfrost for all assets belonging to the user's staking address. If the user's account has multiple wallets, this must be called for each one:
import { BlockFrostAPI } from '@blockfrost/blockfrost-js';
// Initialize blockfrost client
const Blockfrost = new BlockFrostAPI({
    projectId: process.env.BLOCKFROST_API_KEY ?? '',
const handles: string[][] = await Promise.all(bech32Wallets.map(async bech32Wallet => {
    let handles: string[] = [];
    try {
        const data = await Blockfrost.accountsAddressesAssets(bech32Wallet);
    } catch (err: any) {
        if (err.status_code !== 404) {
    } finally {
        return handles;

Note that 404 errors are ignored. These are thrown when a wallet has no transactions.

  1. Filter assets and return the names of each one with the matching ADA Handle policy (done inside the try-catch):
// Each asset will look something like this:
// {"unit":"de95598bb370b6d289f42dfc1de656d65c250ec4cdc930d32b1dc0e5474f4f5345","quantity":"1"}
// The unit is the concatenation of the policy ID (56 characters long) and the asset name (hex encoded)
handles = data
    .filter(({ unit }: any) => unit.startsWith(policyID))
    .map(({ unit }: any) => {
        const hexName = unit.replace(policyID, '');
        const utf8Name = Buffer.from(hexName, 'hex').toString('utf8');
        return utf8Name;
  1. Store these handles somewhere, so when the user selects which one they'd like to use, you don't have to query again for verification.
  2. That's it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.