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:
- Instead of using Blockfrost, host a Cardano node and db-sync.
- 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:
- 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).
- Use this link from Sean's answer to find the correct policy ID for ADA Handles. I'm using mainnet, so this is
f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a
.
- 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.
- 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));
- 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.
- 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;
});
- 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.
- That's it!