Cardano Node
The benefit of the Handle Standard is that everything you might want to know about the Handle is openly available on-chain (such as an associated address, additional Handles, etc). This means that while the official Handle API might be more convenient, it is by no means required.
While documenting the process of setting up a Cardano Node and the Wallet REST & GraphQL APIs are beyond the scope of this article, you can find detailed instructions here:

Get a Handle Address

Node
GraphQL
const handleName = 'web3';
const policyID = 'f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a';
// A blank Handle name should always be ignored.
if (handleName.length === 0) {
// Handle error.
}
// Convert handleName to hex encoding.
const assetName = Buffer.from(handleName).toString('hex');
// Fetch matching address for the asset.
const data = await fetch(
`https://cardano-mainnet.blockfrost.io/api/v0/assets/${policyID}${assetName}/addresses`,
{
headers: {
// Your Blockfrost API key
project_id: process.env.BLOCKFROST_API_KEY,
'Content-Type': 'application/json'
}
}
).then(res => res.json());
if (data?.error) {
// Handle error.
}
const [{ address }] = data;
console.log(address); // addr1qx3c9...
query GetHandleByFingerprint {
assets(
limit:2
where: {
fingerprint: {
_eq: "asset1na7l64eeutwm96gyzl3rkgknwpu8qz4fpuzah0"
}
}
) {
tokenMints(
# Always expect a single result.
# Getting 2 results would indicate a double-mint.
# This is a security check, but not required.
limit: 2
) {
transaction {
outputs (
limit: 1
where: {
tokens: {
asset: {
fingerprint: {
_eq: "asset1na7l64eeutwm96gyzl3rkgknwpu8qz4fpuzah0"
}
}
}
}
) {
address
}
}
}
}
}

Get All Handles by Address

Node
GraphQL
const policyID = 'f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a';
const address = 'addr1...';
// Fetch data about an address.
const data = await fetch(
`https://cardano-mainnet.blockfrost.io/api/v0/addresses/${address}`,
{
headers: {
// Your Blockfrost API key
project_id: process.env.BLOCKFROST_API_KEY,
'Content-Type': 'application/json'
}
}
).then(res => res.json());
if (data?.error) {
// Handle error.
}
const handles = data.amount
.filter(({ unit }) => unit.includes(policyID))
.map(({ unit }) => {
const hexName = unit.replace(policyID, '');
const utf8Name = Buffer.from(hexName, 'hex').toString('utf8');
return utf8Name;
});
console.log(handles); // ['handle1', 'handle2', ...]
query GetHandlesByAddress {
utxos (
where: {
address: {
_eq: "addr1..."
}
}
) {
tokens {
asset {
# Filter results by matching policyIDs to get Handles.
policyId
assetName
}
}
}
}