bns-v2-sdk
Version:
The official BNS V2 SDK for interacting with Stacks Blockchain
991 lines (892 loc) • 31 kB
text/typescript
import {
bufferCV,
ClarityType,
ClarityValue,
cvToString,
getCVTypeString,
ResponseErrorCV,
standardPrincipalCV,
uintCV,
bufferCVFromString,
BufferCV,
TupleCV,
UIntCV,
BooleanCV,
PrincipalCV,
SomeCV,
} from "@stacks/transactions";
import axios from "axios";
import {
asciiToUtf8,
createFormattedZonefileData,
decodeFQN,
generateRandomAddress,
parsePriceFunction,
parseZonefile,
} from "./utils";
import { BnsContractName, getBnsContractAddress } from "./config";
import {
CanNamespaceBeRegisteredOptions,
CanRegisterNameOptions,
CanResolveNameOptions,
FetchUserOwnedNamesOptions,
GetBnsFromIdOptions,
GetIdFromBnsOptions,
GetLastTokenIdOptions,
GetNamePriceOptions,
GetNamespacePriceOptions,
GetNamespacePropertiesOptions,
GetOwnerOptions,
GetPrimaryNameOptions,
GetRenewalHeightOptions,
NamespaceProperties,
NameInfo,
ResolveNameOptions,
ZonefileData,
GetOwnerByIdOptions,
NewZonefileData,
} from "./interfaces";
import { bnsV2ReadOnlyCall, zonefileReadOnlyCall } from "./callersHelper";
import { debug } from "./debug";
const API_BASE_URL = "https://api.bnsv2.com";
const callApi = async (endpoint: string, network: string) => {
try {
const networkPrefix = network === "testnet" ? "/testnet" : "";
const url = `${API_BASE_URL}${networkPrefix}${endpoint}`;
debug.log("Making API call to:", url);
const response = await axios.get(url);
return response.data;
} catch (error) {
debug.error("API call failed:", error);
throw error;
}
};
export async function getLastTokenId({
network,
}: GetLastTokenIdOptions): Promise<bigint | string | number> {
try {
const response = await callApi("/token/last-id", network);
return response.last_token_id;
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-last-token-id",
senderAddress: randomAddress,
functionArgs: [],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.UInt) {
return responseCV.value.value;
}
throw new Error("Response did not contain a UInt");
}
if (responseCV.type === ClarityType.ResponseErr) {
throw new Error(cvToString(responseCV.value));
}
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
}
export async function getRenewalHeight({
fullyQualifiedName,
network,
}: GetRenewalHeightOptions): Promise<bigint | string | number> {
try {
const response = await callApi(
`/names/${fullyQualifiedName}/renewal`,
network
);
return BigInt(response.renewal_height);
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const { subdomain } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get renewal height for a subdomain");
}
const randomAddress = generateRandomAddress();
const nameId = await getIdFromBns({ fullyQualifiedName, network });
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-renewal-height",
senderAddress: randomAddress,
functionArgs: [uintCV(nameId)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.UInt) {
return responseCV.value.value;
}
throw new Error("Response did not contain a UInt");
}
if (responseCV.type === ClarityType.ResponseErr) {
throw new Error(cvToString(responseCV.value));
}
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
}
export async function canResolveName({
fullyQualifiedName,
network,
}: CanResolveNameOptions): Promise<{
renewal: bigint | string | number;
owner: string;
}> {
try {
const response = await callApi(
`/names/${fullyQualifiedName}/can-resolve`,
network
);
return {
renewal: BigInt(response.renewal_height),
owner: response.owner,
};
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot check resolution for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "can-resolve-name",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(namespace), bufferCVFromString(name)],
network,
});
if (
responseCV.type === ClarityType.ResponseOk &&
responseCV.value.type === ClarityType.Tuple
) {
const renewalCV = responseCV.value.value["renewal"];
const ownerCV = responseCV.value.value["owner"];
if (
renewalCV.type === ClarityType.UInt &&
(ownerCV.type === ClarityType.PrincipalStandard ||
ownerCV.type === ClarityType.PrincipalContract)
) {
return {
renewal: renewalCV.value,
owner: cvToString(ownerCV),
};
}
throw new Error("Unexpected data types in response tuple");
}
throw new Error("Invalid response from contract");
}
}
export async function getOwner({
fullyQualifiedName,
network,
}: GetOwnerOptions): Promise<string | null> {
try {
const response = await callApi(
`/names/${fullyQualifiedName}/owner`,
network
);
return response.owner;
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot check resolution for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-owner-name",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.OptionalSome) {
if (
responseCV.value.value.type === ClarityType.PrincipalStandard ||
responseCV.value.value.type === ClarityType.PrincipalContract
) {
return cvToString(responseCV.value.value);
}
throw new Error("Owner is not a principal");
}
if (responseCV.value.type === ClarityType.OptionalNone) {
return null;
}
}
throw new Error("Invalid response from contract");
}
}
export async function getOwnerById({
id,
network,
}: GetOwnerByIdOptions): Promise<string | null> {
try {
const response = await callApi(`/tokens/${id}/owner`, network);
return response.owner;
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-owner",
senderAddress: randomAddress,
functionArgs: [uintCV(id)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.OptionalSome) {
if (
responseCV.value.value.type === ClarityType.PrincipalStandard ||
responseCV.value.value.type === ClarityType.PrincipalContract
) {
return cvToString(responseCV.value.value);
}
throw new Error("Owner is not a principal");
}
if (responseCV.value.type === ClarityType.OptionalNone) {
return null;
}
}
throw new Error("Invalid response from contract");
}
}
export async function getIdFromBns({
fullyQualifiedName,
network,
}: GetIdFromBnsOptions): Promise<bigint | string | number> {
try {
const response = await callApi(`/names/${fullyQualifiedName}/id`, network);
return BigInt(response.id);
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get info for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-id-from-bns",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (responseCV.type === ClarityType.OptionalSome) {
if (responseCV.value.type === ClarityType.UInt) {
return responseCV.value.value;
}
throw new Error("Response did not contain a UInt");
}
if (responseCV.type === ClarityType.OptionalNone) {
throw new Error("Name not found");
}
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
}
export async function getBnsFromId({
id,
network,
}: GetBnsFromIdOptions): Promise<{ name: string; namespace: string } | null> {
try {
const response = await callApi(`/tokens/${id}/name`, network);
return {
name: response.name,
namespace: response.namespace,
};
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-bns-from-id",
senderAddress: randomAddress,
functionArgs: [uintCV(id)],
network,
});
if (responseCV.type === ClarityType.OptionalSome) {
if (responseCV.value.type === ClarityType.Tuple) {
const nameCV = responseCV.value.value["name"] as BufferCV;
const namespaceCV = responseCV.value.value["namespace"] as BufferCV;
return {
name: nameCV.value.toString(),
namespace: nameCV.value.toString(),
};
}
throw new Error("Response did not contain a Tuple");
}
if (responseCV.type === ClarityType.OptionalNone) {
return null;
}
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
}
export async function canRegisterName({
fullyQualifiedName,
network,
}: CanRegisterNameOptions): Promise<boolean> {
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot register a subdomain using registerName");
}
try {
const response = await callApi(
`/names/${namespace}/${name}/can-register`,
network
);
return response.can_register;
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
return fallbackContractCall(name, namespace, network);
}
}
async function fallbackContractCall(
name: string,
namespace: string,
network: "mainnet" | "testnet"
): Promise<boolean> {
const randomAddress = generateRandomAddress();
debug.log("Falling back to contract call:", { name, namespace, network });
return bnsV2ReadOnlyCall({
functionName: "get-bns-info",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
}).then((responseCV: ClarityValue) => {
if (responseCV.type === ClarityType.OptionalSome) {
debug.log("Name exists in contract");
return false;
} else if (responseCV.type === ClarityType.OptionalNone) {
debug.log("Name available in contract");
return true;
} else {
throw new Error(`Unexpected response type: ${responseCV.type}`);
}
});
}
export async function getNamespacePrice({
namespace,
network,
}: GetNamespacePriceOptions): Promise<bigint | string | number> {
const bnsFunctionName = "get-namespace-price";
const randomAddress = generateRandomAddress();
return bnsV2ReadOnlyCall({
functionName: bnsFunctionName,
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(namespace)],
network,
}).then((responseCV: ClarityValue) => {
if (responseCV.type === ClarityType.ResponseOk) {
if (
responseCV.value.type === ClarityType.Int ||
responseCV.value.type === ClarityType.UInt
) {
return responseCV.value.value;
} else {
throw new Error("Response did not contain a number");
}
} else if (responseCV.type === ClarityType.ResponseErr) {
throw new Error(cvToString(responseCV.value));
} else {
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
});
}
export async function getNamePrice({
fullyQualifiedName,
network,
}: GetNamePriceOptions): Promise<bigint | string | number> {
const bnsFunctionName = "get-name-price";
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get subdomain price");
}
const randomAddress = generateRandomAddress();
return bnsV2ReadOnlyCall({
functionName: bnsFunctionName,
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(namespace), bufferCVFromString(name)],
network,
})
.then((responseCV: ClarityValue) => {
if (responseCV.type === ClarityType.ResponseOk) {
const responseOkValue = responseCV.value;
if (responseOkValue.type === ClarityType.ResponseOk) {
const nestedResponseOkValue = responseOkValue.value;
if (
nestedResponseOkValue.type === ClarityType.Int ||
nestedResponseOkValue.type === ClarityType.UInt
) {
return nestedResponseOkValue.value;
} else {
throw new Error("Nested response did not contain a number");
}
} else if (
responseOkValue.type === ClarityType.Int ||
responseOkValue.type === ClarityType.UInt
) {
return responseOkValue.value;
} else {
throw new Error("Response did not contain a number");
}
} else {
const errorResponse = responseCV as ResponseErrorCV;
throw new Error(cvToString(errorResponse.value));
}
})
.catch((error) => {
throw error;
});
}
export async function canNamespaceBeRegistered({
namespace,
network,
}: CanNamespaceBeRegisteredOptions): Promise<boolean> {
const bnsFunctionName = "can-namespace-be-registered";
const randomAddress = generateRandomAddress();
return bnsV2ReadOnlyCall({
functionName: bnsFunctionName,
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(namespace)],
network,
}).then((responseCV: ClarityValue) => {
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.BoolTrue) {
return true;
} else if (responseCV.value.type === ClarityType.BoolFalse) {
return false;
} else {
throw new Error("Response did not contain a boolean");
}
} else if (responseCV.type === ClarityType.ResponseErr) {
throw new Error(cvToString(responseCV.value));
} else {
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
});
}
export async function getNamespaceProperties({
namespace,
network,
}: GetNamespacePropertiesOptions): Promise<NamespaceProperties> {
try {
const response = await callApi(`/namespaces/${namespace}`, network);
const namespaceData = response.namespace;
return {
namespace: namespace,
properties: {
"namespace-manager": namespaceData.namespace_manager || null,
"manager-transferable": namespaceData.manager_transferable,
"manager-frozen": namespaceData.manager_frozen || false,
"namespace-import": namespaceData.namespace_import,
"revealed-at": BigInt(namespaceData.revealed_at || 0),
"launched-at": namespaceData.launched_at
? BigInt(namespaceData.launched_at)
: null,
lifetime: BigInt(namespaceData.lifetime || 0),
"can-update-price-function": namespaceData.can_update_price_function,
"price-function": {
base: BigInt(namespaceData.price_function_base),
coefficient: BigInt(namespaceData.price_function_coeff),
b1: BigInt(namespaceData.price_function_buckets[0] || 0),
b2: BigInt(namespaceData.price_function_buckets[1] || 0),
b3: BigInt(namespaceData.price_function_buckets[2] || 0),
b4: BigInt(namespaceData.price_function_buckets[3] || 0),
b5: BigInt(namespaceData.price_function_buckets[4] || 0),
b6: BigInt(namespaceData.price_function_buckets[5] || 0),
b7: BigInt(namespaceData.price_function_buckets[6] || 0),
b8: BigInt(namespaceData.price_function_buckets[7] || 0),
b9: BigInt(namespaceData.price_function_buckets[8] || 0),
b10: BigInt(namespaceData.price_function_buckets[9] || 0),
b11: BigInt(namespaceData.price_function_buckets[10] || 0),
b12: BigInt(namespaceData.price_function_buckets[11] || 0),
b13: BigInt(namespaceData.price_function_buckets[12] || 0),
b14: BigInt(namespaceData.price_function_buckets[13] || 0),
b15: BigInt(namespaceData.price_function_buckets[14] || 0),
b16: BigInt(namespaceData.price_function_buckets[15] || 0),
nonAlphaDiscount: BigInt(
namespaceData.price_function_nonalpha_discount
),
noVowelDiscount: BigInt(
namespaceData.price_function_no_vowel_discount
),
},
},
};
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-namespace-properties",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(namespace)],
network,
});
if (
responseCV.type === ClarityType.ResponseOk &&
responseCV.value.type === ClarityType.Tuple
) {
const namespaceCV = responseCV.value.value["namespace"] as BufferCV;
const propertiesCV = responseCV.value.value["properties"] as TupleCV;
const properties = propertiesCV.value;
return {
namespace: namespaceCV.value.toString(),
properties: {
"namespace-manager":
properties["namespace-manager"].type === ClarityType.OptionalNone
? null
: cvToString(
(properties["namespace-manager"] as SomeCV<PrincipalCV>).value
),
"manager-transferable":
(properties["manager-transferable"] as BooleanCV).type ===
ClarityType.BoolTrue,
"manager-frozen":
(properties["manager-frozen"] as BooleanCV).type ===
ClarityType.BoolTrue,
"namespace-import": cvToString(
properties["namespace-import"] as PrincipalCV
),
"revealed-at": (properties["revealed-at"] as UIntCV).value,
"launched-at":
properties["launched-at"].type === ClarityType.OptionalNone
? null
: (properties["launched-at"] as SomeCV<UIntCV>).value.value,
lifetime: (properties["lifetime"] as UIntCV).value,
"can-update-price-function":
(properties["can-update-price-function"] as BooleanCV).type ===
ClarityType.BoolTrue,
"price-function": parsePriceFunction(
(properties["price-function"] as TupleCV).value
),
},
};
}
throw new Error("Invalid response from contract");
}
}
export async function getNameInfo({
fullyQualifiedName,
network,
}: CanRegisterNameOptions): Promise<NameInfo> {
try {
const response = await callApi(`/names/${fullyQualifiedName}`, network);
const data = response.data;
return {
owner: data.owner,
registeredAt: data.registered_at ? BigInt(data.registered_at) : null,
renewalHeight: BigInt(data.renewal_height || 0),
stxBurn: BigInt(data.stx_burn || 0),
importedAt: data.imported_at ? BigInt(data.imported_at) : null,
preorderedBy: data.preordered_by,
hashedSaltedFqnPreorder: data.hashedSaltedFqnPreorder,
};
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get info for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await bnsV2ReadOnlyCall({
functionName: "get-bns-info",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (
responseCV.type === ClarityType.OptionalSome &&
responseCV.value.type === ClarityType.Tuple
) {
const tupleCV = responseCV.value as TupleCV;
const properties = tupleCV.value;
return {
owner: cvToString(properties.owner as PrincipalCV),
registeredAt:
properties["registered-at"].type === ClarityType.OptionalNone
? null
: (properties["registered-at"] as SomeCV<UIntCV>).value.value,
renewalHeight: (properties["renewal-height"] as UIntCV).value,
stxBurn: (properties["stx-burn"] as UIntCV).value,
importedAt:
properties["imported-at"].type === ClarityType.OptionalNone
? null
: (properties["imported-at"] as SomeCV<UIntCV>).value.value,
preorderedBy:
properties["preordered-by"].type === ClarityType.OptionalNone
? null
: cvToString(
(properties["preordered-by"] as SomeCV<PrincipalCV>).value
),
hashedSaltedFqnPreorder:
properties["hashed-salted-fqn-preorder"].type ===
ClarityType.OptionalNone
? null
: (
properties["hashed-salted-fqn-preorder"] as SomeCV<BufferCV>
).value.toString(),
};
}
throw new Error("Invalid response from contract");
}
}
export async function getPrimaryName({
address,
network,
}: GetPrimaryNameOptions): Promise<{ name: string; namespace: string } | null> {
const bnsFunctionName = "get-primary";
const randomAddress = generateRandomAddress();
return bnsV2ReadOnlyCall({
functionName: bnsFunctionName,
senderAddress: randomAddress,
functionArgs: [standardPrincipalCV(address)],
network,
}).then((responseCV: ClarityValue) => {
if (responseCV.type === ClarityType.ResponseOk) {
if (responseCV.value.type === ClarityType.Tuple) {
const nameCV = responseCV.value.value["name"] as BufferCV;
const namespaceCV = responseCV.value.value["namespace"] as BufferCV;
return {
name: Buffer.from(nameCV.value, "hex").toString(),
namespace: Buffer.from(namespaceCV.value, "hex").toString(),
};
} else if (responseCV.value.type === ClarityType.OptionalSome) {
const innerValue = responseCV.value.value;
if (innerValue.type === ClarityType.Tuple) {
const nameCV = innerValue.value["name"] as BufferCV;
const namespaceCV = innerValue.value["namespace"] as BufferCV;
return {
name: Buffer.from(nameCV.value, "hex").toString(),
namespace: Buffer.from(namespaceCV.value, "hex").toString(),
};
}
}
throw new Error("Unexpected response structure");
} else if (responseCV.type === ClarityType.ResponseErr) {
if (cvToString(responseCV.value) === "u131") {
return null;
}
throw new Error(cvToString(responseCV.value));
} else {
throw new Error(
`Unexpected Clarity Value type: ${getCVTypeString(responseCV)}`
);
}
});
}
export async function fetchUserOwnedNames({
senderAddress,
network,
}: FetchUserOwnedNamesOptions): Promise<
Array<{ name: string; namespace: string }>
> {
try {
let allNames: Array<{ name_string: string; namespace_string: string }> = [];
let offset = 0;
const limit = 50;
while (true) {
const response = await callApi(
`/names/address/${senderAddress}/valid?limit=${limit}&offset=${offset}`,
network
);
allNames = allNames.concat(response.names);
if (response.names.length < limit || allNames.length >= response.total) {
break;
}
offset += limit;
}
return allNames.map((name) => ({
name: name.name_string,
namespace: name.namespace_string,
}));
} catch (error) {
debug.error("API call failed, falling back to contract call:", error);
const contractAddress = getBnsContractAddress(network);
const assetIdentifier = `${contractAddress}.${BnsContractName}::BNS-V2`;
const apiUrl =
network === "mainnet"
? "https://api.hiro.so"
: "https://api.testnet.hiro.so";
let allAssets: number[] = [];
let offset = 0;
const limit = 50;
while (true) {
const response = await axios.get(
`${apiUrl}/extended/v1/tokens/nft/holdings?principal=${senderAddress}&asset_identifiers=${assetIdentifier}&limit=${limit}&offset=${offset}`
);
const assets = response.data.results.map(
(asset: { value: { repr: string } }) =>
parseInt(asset.value.repr.slice(1))
);
allAssets = allAssets.concat(assets);
if (response.data.total <= offset + limit) {
break;
}
offset += limit;
}
const bnsPromises = allAssets.map((id) =>
getBnsFromId({ id: BigInt(id), network })
);
const bnsResults = await Promise.all(bnsPromises);
return bnsResults
.filter(
(result): result is { name: string; namespace: string } =>
result !== null
)
.map((result) => ({
name: asciiToUtf8(result.name),
namespace: asciiToUtf8(result.namespace),
}))
.sort((a, b) => {
if (a.namespace !== b.namespace) {
return a.namespace.localeCompare(b.namespace);
}
return a.name.localeCompare(b.name);
});
}
}
export async function resolveNameZonefile({
fullyQualifiedName,
network,
}: ResolveNameOptions): Promise<ZonefileData | null> {
try {
const response = await callApi(
`/resolve-name/${fullyQualifiedName}`,
network
);
return response.zonefile || null;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
return null;
}
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot resolve a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await zonefileReadOnlyCall({
functionName: "resolve-name",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (
responseCV.value.type === ClarityType.OptionalSome &&
responseCV.value.value.type === ClarityType.Buffer
) {
const zonefileString = Buffer.from(
responseCV.value.value.value
).toString("utf8");
return parseZonefile(zonefileString);
}
if (responseCV.value.type === ClarityType.OptionalNone) {
return null;
}
}
throw new Error("Invalid response from contract");
}
}
export async function getZonefileRaw({
fullyQualifiedName,
network,
}: ResolveNameOptions): Promise<any | null> {
try {
const response = await callApi(
`/zonefile/${fullyQualifiedName}/raw`,
network
);
return response.zonefile || null;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
return null;
}
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get raw zonefile for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await zonefileReadOnlyCall({
functionName: "resolve-name",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (
responseCV.value.type === ClarityType.OptionalSome &&
responseCV.value.value.type === ClarityType.Buffer
) {
const zonefileString = Buffer.from(
responseCV.value.value.value
).toString("utf8");
try {
return JSON.parse(zonefileString);
} catch {
return zonefileString;
}
}
if (responseCV.value.type === ClarityType.OptionalNone) {
return null;
}
}
throw new Error("Invalid response from contract");
}
}
export async function getZonefileProfile({
fullyQualifiedName,
network,
}: ResolveNameOptions): Promise<NewZonefileData | null> {
try {
const response = await callApi(
`/zonefile/${fullyQualifiedName}/profile`,
network
);
return response.profile || null;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
return null;
}
debug.error("API call failed, falling back to contract call:", error);
const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName);
if (subdomain) {
throw new Error("Cannot get profile zonefile for a subdomain");
}
const randomAddress = generateRandomAddress();
const responseCV = await zonefileReadOnlyCall({
functionName: "resolve-name",
senderAddress: randomAddress,
functionArgs: [bufferCVFromString(name), bufferCVFromString(namespace)],
network,
});
if (responseCV.type === ClarityType.ResponseOk) {
if (
responseCV.value.type === ClarityType.OptionalSome &&
responseCV.value.value.type === ClarityType.Buffer
) {
const zonefileString = Buffer.from(
responseCV.value.value.value
).toString("utf8");
try {
const parsed = JSON.parse(zonefileString);
return createFormattedZonefileData(parsed);
} catch (error) {
debug.error("Failed to parse zonefile as profile format:", error);
return null;
}
}
if (responseCV.value.type === ClarityType.OptionalNone) {
return null;
}
}
throw new Error("Invalid response from contract");
}
}