Skip to main content

Resolve handles

Use resolve before every send-by-name flow. The SafHandle contract maps john / john.saf to an addr_safro address. Phone linking is Phase 2 on testnet v1 (names only today).

Contract on testnet

safro-testnet-1 contract: addr_safro1j6n2q333gy80pmpd6avss32y4nhd8deayv8m9x4uazt6zkdczk9sxjfun6 (config)

Resolution flow

Always show the resolved address (truncated) on the confirm screen before signing MsgSend.

SDK: getAddress

import { SafHandle } from '@safrochaindev/safhandle';

const safHandle = new SafHandle({ network: 'safrochain-testnet' });

const result = await safHandle.getAddress('john');
// {
// address: 'addr_safro1...',
// recordType: 'name',
// normalizedKey: 'john.saf',
// verified: null
// }

const recipient = result.address;

ResolveResult fields

FieldMeaning
addressResolved addr_safro bech32
recordType'name' or 'phone' (phone when Phase 2 is live)
normalizedKeyCanonical key, e.g. john.saf
verifiednull for names; boolean for phones (Phase 2)

Specialized queries

await safHandle.resolveName('john'); // name-only
await safHandle.resolvePhone('+243899123456'); // Phase 2
await safHandle.getConfig(); // fees + dev wallet

CosmJS without SDK

Use when the npm package is not yet wired into your app:

import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';

const RPC = 'https://rpc.testnet.safrochain.com:443';
const CONTRACT = 'addr_safro1j6n2q333gy80pmpd6avss32y4nhd8deayv8m9x4uazt6zkdczk9sxjfun6';

const client = await CosmWasmClient.connect(RPC);
const result = await client.queryContractSmart(CONTRACT, {
get_address: { input: 'john' },
});
// { address, record_type, normalized_key }

Classify user input

Detect whether the user typed a name, raw address, or (later) phone:

import { isValidPhone, isValidName, isSafroAddress } from '@safrochaindev/safhandle';

function classifyInput(input: string): 'name' | 'phone' | 'address' {
const trimmed = input.trim().replace(/^@/, '');
if (isSafroAddress(trimmed)) return 'address';
if (trimmed.startsWith('+') && isValidPhone(trimmed)) return 'phone';
if (isValidName(trimmed)) return 'name';
return 'name'; // let getAddress return INVALID_INPUT if bad
}

async function resolveRecipient(input: string): Promise<string> {
const kind = classifyInput(input);
if (kind === 'address') return input.trim();
const { address, verified } = await safHandle.getAddress(input);
if (kind === 'phone' && verified === false) {
throw new Error('Phone number is not verified');
}
return address;
}

Validation helpers

FunctionExample
normalizeName('John')john.saf
isValidName('john')true
normalizePhone('+243 899 123 456')E.164 digits for contract
isValidPhone('+243899123456')true

Name rules: contract NAME_RULES.

Caching

DataTTLNotes
Name resolution~60sRe-resolve before high-value sends
Config (fees)~5 minRefresh on registration screens
Phone status~30sDo not cache across send sessions

Stale resolution is a funds-loss risk if the owner transferred the name.

Errors

ErrorWhenUX
SafHandleNotFoundErrorName not registeredSuggest register or check spelling
SafHandleInvalidInputErrorFailed validationInline field error
SafHandleNetworkErrorRPC downRetry + endpoint health
SafHandleContractErrorOn-chain rejectShow contract message

Full reference: SDK error handling.

Next steps