UNPKG

bns-v2-sdk

Version:

The official BNS V2 SDK for interacting with Stacks Blockchain

845 lines (748 loc) 21.3 kB
import { utf8ToBytes } from "@stacks/common"; import { bufferCV, hash160, standardPrincipalCV, uintCV, someCV, noneCV, bufferCVFromString, boolCV, contractPrincipalCV, Pc, } from "@stacks/transactions"; import { BnsContractName, getBnsContractAddress, ZonefileContractName, getZonefileContractAddress, } from "./config"; import { ContractCallPayload, FlexibleUpdateZonefileOptions, FormattedUpdateZonefileOptions, } from "./interfaces"; import * as Types from "./interfaces"; import { decodeFQN, createZonefileData, stringifyZonefile, createFormattedZonefileData, } from "./utils"; import { getBnsFromId, getIdFromBns, getNamespaceProperties, getNameTradingStatus, getOwner, getOwnerById, } from "./readOnlyCalls"; export async function buildTransferNameTx({ fullyQualifiedName, newOwnerAddress, senderAddress, network, }: Types.TransferNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "transfer"; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot transfer a subdomain using transferName()"); } const id = await getIdFromBns({ fullyQualifiedName, network, }); const functionArgs = [ uintCV(id), standardPrincipalCV(senderAddress), standardPrincipalCV(newOwnerAddress), ]; const postConditionSender = Pc.principal(senderAddress) .willSendAsset() .nft( `${getBnsContractAddress(network)}.${BnsContractName}::BNS-V2`, uintCV(id) ); const postConditionReceiver = Pc.principal(newOwnerAddress) .willNotSendAsset() .nft( `${getBnsContractAddress(network)}.${BnsContractName}::BNS-V2`, uintCV(id) ); return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs, postConditions: [postConditionSender, postConditionReceiver], network, }; } export async function buildListInUstxTx({ id, price, commissionTraitAddress, commissionTraitName, senderAddress, network, }: Types.ListInUstxOptions): Promise<ContractCallPayload> { const bnsFunctionName = "list-in-ustx"; // Check if name can be traded const nameInfo = await getBnsFromId({ id: BigInt(id), network }); if (!nameInfo) { throw new Error("Name not found"); } const fullyQualifiedName = `${nameInfo.name}.${nameInfo.namespace}`; const tradingStatus = await getNameTradingStatus({ fullyQualifiedName, network, }); if (!tradingStatus.canTrade) { throw new Error( `Name ${fullyQualifiedName} ${tradingStatus.reason || "cannot be traded"}` ); } return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ uintCV(id), uintCV(price), contractPrincipalCV(commissionTraitAddress, commissionTraitName), ], postConditions: [], network, }; } export async function buildUnlistInUstxTx({ id, senderAddress, network, }: Types.UnlistInUstxOptions): Promise<ContractCallPayload> { const bnsFunctionName = "unlist-in-ustx"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [uintCV(id)], postConditions: [], network, }; } export async function buildBuyInUstxTx({ id, expectedPrice, commissionTraitAddress, commissionTraitName, senderAddress, network, }: Types.BuyInUstxOptions): Promise<ContractCallPayload> { const bnsFunctionName = "buy-in-ustx"; // Check if name can be traded const nameInfo = await getBnsFromId({ id: BigInt(id), network }); if (!nameInfo) { throw new Error("Name not found"); } const fullyQualifiedName = `${nameInfo.name}.${nameInfo.namespace}`; const tradingStatus = await getNameTradingStatus({ fullyQualifiedName, network, }); if (!tradingStatus.canTrade) { throw new Error( `Name ${fullyQualifiedName} ${tradingStatus.reason || "cannot be traded"}` ); } const currentOwner = await getOwnerById({ id, network }); if (!currentOwner) { throw new Error("Failed to fetch current owner of the name"); } const senderPostCondition = Pc.principal(senderAddress) .willSendLte(expectedPrice) .ustx(); const ownerPostCondition = Pc.principal(currentOwner) .willSendAsset() .nft( `${getBnsContractAddress(network)}.${BnsContractName}::BNS-V2`, uintCV(id) ); const postConditions = [senderPostCondition, ownerPostCondition]; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ uintCV(id), contractPrincipalCV(commissionTraitAddress, commissionTraitName), ], postConditions, network, }; } export async function buildSetPrimaryNameTx({ fullyQualifiedName, senderAddress, network, }: Types.SetPrimaryNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "set-primary-name"; const { subdomain } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot set a subdomain as primary name"); } const id = await getIdFromBns({ fullyQualifiedName, network, }); const currentOwner = await getOwner({ fullyQualifiedName, network, }); if (!currentOwner) { throw new Error("Failed to fetch current owner of the name"); } const postConditions = [ Pc.principal(currentOwner) .willNotSendAsset() .nft( `${getBnsContractAddress(network)}.${BnsContractName}::BNS-V2`, uintCV(id) ), ]; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [uintCV(id)], postConditions, network, }; } export async function buildFreezeManagerTx({ namespace, senderAddress, network, }: Types.FreezeManagerOptions): Promise<ContractCallPayload> { const bnsFunctionName = "freeze-manager"; const namespaceProperties = await getNamespaceProperties({ namespace, network, }); if (!namespaceProperties) { throw new Error("Failed to fetch namespace properties"); } if (!namespaceProperties.properties["namespace-manager"]) { throw new Error("This namespace does not have a manager"); } if (namespaceProperties.properties["manager-frozen"]) { throw new Error("Manager is already frozen for this namespace"); } return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCVFromString(namespace)], postConditions: [], network, }; } export async function buildPreorderNamespaceTx({ namespace, salt, stxToBurn, senderAddress, network, }: Types.PreorderNamespaceOptions): Promise<ContractCallPayload> { const bnsFunctionName = "namespace-preorder"; const saltedNamespaceBytes = utf8ToBytes(`${namespace}.${salt}`); const hashedSaltedNamespace = hash160(saltedNamespaceBytes); const burnSTXPostCondition = Pc.principal(senderAddress) .willSendEq(stxToBurn) .ustx(); return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCV(hashedSaltedNamespace), uintCV(stxToBurn)], postConditions: [burnSTXPostCondition], network, }; } export async function buildRevealNamespaceTx({ namespace, salt, priceFunction, lifetime = 0n, namespaceImportAddress, namespaceManagerAddress, canUpdatePrice, managerTransfer = false, managerFrozen = true, senderAddress, network, }: Types.RevealNamespaceOptions): Promise<ContractCallPayload> { const bnsFunctionName = "namespace-reveal"; let managerAddressCV; if (namespaceManagerAddress) { if (namespaceManagerAddress.includes(".")) { const [contractAddress, contractName] = namespaceManagerAddress.split("."); managerAddressCV = someCV( contractPrincipalCV(contractAddress, contractName) ); } else { managerAddressCV = someCV(standardPrincipalCV(namespaceManagerAddress)); } } else { managerAddressCV = noneCV(); } const pf = priceFunction || defaultPriceFunction; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(namespace), bufferCVFromString(salt), uintCV(pf.base), uintCV(pf.coefficient), uintCV(pf.b1), uintCV(pf.b2), uintCV(pf.b3), uintCV(pf.b4), uintCV(pf.b5), uintCV(pf.b6), uintCV(pf.b7), uintCV(pf.b8), uintCV(pf.b9), uintCV(pf.b10), uintCV(pf.b11), uintCV(pf.b12), uintCV(pf.b13), uintCV(pf.b14), uintCV(pf.b15), uintCV(pf.b16), uintCV(pf.nonAlphaDiscount), uintCV(pf.noVowelDiscount), uintCV(lifetime), standardPrincipalCV(namespaceImportAddress), managerAddressCV, boolCV(canUpdatePrice), boolCV(managerTransfer), boolCV(managerFrozen), ], postConditions: [], network, }; } export async function buildLaunchNamespaceTx({ namespace, senderAddress, network, }: Types.LaunchNamespaceOptions): Promise<ContractCallPayload> { const bnsFunctionName = "namespace-launch"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCVFromString(namespace)], postConditions: [], network, }; } export async function buildTurnOffManagerTransfersTx({ namespace, senderAddress, network, }: Types.TurnOffManagerTransfersOptions): Promise<ContractCallPayload> { const bnsFunctionName = "turn-off-manager-transfers"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCVFromString(namespace)], postConditions: [], network, }; } export async function buildImportNameTx({ namespace, name, beneficiary, senderAddress, network, }: Types.ImportNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-import"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(namespace), bufferCVFromString(name), standardPrincipalCV(beneficiary), ], postConditions: [], network, }; } export async function buildNamespaceUpdatePriceTx({ namespace, priceFunction, senderAddress, network, }: Types.NamespaceUpdatePriceOptions): Promise<ContractCallPayload> { const bnsFunctionName = "namespace-update-price"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(namespace), uintCV(priceFunction.base), uintCV(priceFunction.coefficient), uintCV(priceFunction.b1), uintCV(priceFunction.b2), uintCV(priceFunction.b3), uintCV(priceFunction.b4), uintCV(priceFunction.b5), uintCV(priceFunction.b6), uintCV(priceFunction.b7), uintCV(priceFunction.b8), uintCV(priceFunction.b9), uintCV(priceFunction.b10), uintCV(priceFunction.b11), uintCV(priceFunction.b12), uintCV(priceFunction.b13), uintCV(priceFunction.b14), uintCV(priceFunction.b15), uintCV(priceFunction.b16), uintCV(priceFunction.nonAlphaDiscount), uintCV(priceFunction.noVowelDiscount), ], postConditions: [], network, }; } export async function buildNamespaceFreezePriceTx({ namespace, senderAddress, network, }: Types.NamespaceFreezePriceOptions): Promise<ContractCallPayload> { const bnsFunctionName = "namespace-freeze-price"; return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCVFromString(namespace)], postConditions: [], network, }; } export async function buildNameClaimFastTx({ fullyQualifiedName, stxToBurn, sendTo, senderAddress, network, }: Types.NameFastClaimOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-claim-fast"; const { namespace, name, subdomain } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot register a subdomain using registerName()"); } const principalCV = sendTo.includes(".") ? contractPrincipalCV(sendTo.split(".")[0], sendTo.split(".")[1]) : standardPrincipalCV(sendTo); let postConditions = []; if (stxToBurn > 0) { const burnSTXPostCondition = Pc.principal(senderAddress) .willSendEq(stxToBurn) .ustx(); postConditions.push(burnSTXPostCondition); } return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCV(Buffer.from(name)), bufferCV(Buffer.from(namespace)), principalCV, ], postConditions, network, }; } export async function buildPreorderNameTx({ fullyQualifiedName, salt, stxToBurn, senderAddress, network, }: Types.PreorderNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-preorder"; const { subdomain } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot preorder a subdomain using preorderName()"); } const saltedNamesBytes = Buffer.from(`${fullyQualifiedName}${salt}`); const hashedSaltedName = hash160(saltedNamesBytes); let postConditions = []; if (stxToBurn > 0) { const burnSTXPostCondition = Pc.principal(senderAddress) .willSendEq(stxToBurn) .ustx(); postConditions.push(burnSTXPostCondition); } return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCV(hashedSaltedName), uintCV(stxToBurn)], postConditions, network, }; } export async function buildRegisterNameTx({ fullyQualifiedName, salt, stxToBurn, senderAddress, network, }: Types.RegisterNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-register"; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot register a subdomain using registerName()"); } let postConditions = []; if (stxToBurn > 0) { const burnSTXPostCondition = Pc.principal( `${getBnsContractAddress(network)}.${BnsContractName}` ) .willSendEq(stxToBurn) .ustx(); postConditions.push(burnSTXPostCondition); } return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCV(Buffer.from(namespace)), bufferCV(Buffer.from(name)), bufferCV(Buffer.from(salt)), ], postConditions, network, }; } export async function buildPreviousRegisterNameTx({ fullyQualifiedName, salt, stxToBurn, senderAddress, network, }: Types.RegisterNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-register"; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot register a subdomain using registerName()"); } const nameId = await getIdFromBns({ fullyQualifiedName, network, }); const currentOwner = await getOwner({ fullyQualifiedName, network }); if (!currentOwner) { throw new Error("Failed to fetch current owner of the name"); } let postConditions = []; if (stxToBurn > 0) { const burnSTXPostCondition = Pc.principal( `${getBnsContractAddress(network)}.${BnsContractName}` ) .willSendEq(stxToBurn) .ustx(); postConditions.push(burnSTXPostCondition); } const transferNFTPostCondition = Pc.principal(currentOwner) .willSendAsset() .nft( `${getBnsContractAddress(network)}.${BnsContractName}::BNS-V2`, uintCV(nameId) ); postConditions.push(transferNFTPostCondition); return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [ bufferCV(Buffer.from(namespace)), bufferCV(Buffer.from(name)), bufferCV(Buffer.from(salt)), ], postConditions, network, }; } export async function buildClaimPreorderTx({ fullyQualifiedName, salt, stxToClaim, senderAddress, network, }: Types.ClaimPreorderOptions): Promise<ContractCallPayload> { const bnsFunctionName = "claim-preorder"; const { subdomain } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot claim a subdomain using claim-preorder()"); } const saltedNamesBytes = utf8ToBytes(`${fullyQualifiedName}${salt}`); const hashedSaltedName = hash160(saltedNamesBytes); const returnSTXPostCondition = Pc.principal( `${getBnsContractAddress(network)}.${BnsContractName}` ) .willSendEq(stxToClaim) .ustx(); return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCV(hashedSaltedName)], postConditions: [returnSTXPostCondition], network, }; } export async function buildRenewNameTx({ fullyQualifiedName, stxToBurn, senderAddress, network, }: Types.RenewNameOptions): Promise<ContractCallPayload> { const bnsFunctionName = "name-renewal"; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { throw new Error("Cannot renew a subdomain using renewName()"); } const burnSTXPostCondition = Pc.principal(senderAddress) .willSendEq(stxToBurn) .ustx(); return { contractAddress: getBnsContractAddress(network), contractName: BnsContractName, functionName: bnsFunctionName, functionArgs: [bufferCVFromString(namespace), bufferCVFromString(name)], postConditions: [burnSTXPostCondition], network, }; } export async function buildUpdateZonefileTx({ fullyQualifiedName, zonefileInputs, senderAddress, network, }: Types.UpdateZonefileOptions): Promise<ContractCallPayload> { const bnsFunctionName = "update-zonefile"; const { name, namespace } = decodeFQN(fullyQualifiedName); let zonefileCV; if (zonefileInputs) { const zonefileData = createZonefileData(zonefileInputs); const zonefileString = stringifyZonefile(zonefileData); zonefileCV = someCV(bufferCVFromString(zonefileString)); } else { zonefileCV = noneCV(); } return { contractAddress: getZonefileContractAddress(network), contractName: ZonefileContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(name), bufferCVFromString(namespace), zonefileCV, ], postConditions: [], network, }; } const defaultPriceFunction: Types.PriceFunction = { base: 1n, coefficient: 1n, b1: 1n, b2: 1n, b3: 1n, b4: 1n, b5: 1n, b6: 1n, b7: 1n, b8: 1n, b9: 1n, b10: 1n, b11: 1n, b12: 1n, b13: 1n, b14: 1n, b15: 1n, b16: 1n, nonAlphaDiscount: 1n, noVowelDiscount: 1n, }; export async function buildUpdateZonefileFlexibleTx({ fullyQualifiedName, zonefileData, senderAddress, network, }: FlexibleUpdateZonefileOptions): Promise<ContractCallPayload> { const bnsFunctionName = "update-zonefile"; const { name, namespace } = decodeFQN(fullyQualifiedName); let zonefileCV; if (zonefileData) { const zonefileString = JSON.stringify(zonefileData); zonefileCV = someCV(bufferCVFromString(zonefileString)); } else { zonefileCV = noneCV(); } return { contractAddress: getZonefileContractAddress(network), contractName: ZonefileContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(name), bufferCVFromString(namespace), zonefileCV, ], postConditions: [], network, }; } export async function buildUpdateZonefileFormattedTx({ fullyQualifiedName, zonefileData, senderAddress, network, }: FormattedUpdateZonefileOptions): Promise<ContractCallPayload> { const bnsFunctionName = "update-zonefile"; const { name, namespace } = decodeFQN(fullyQualifiedName); const formattedZonefileData = createFormattedZonefileData(zonefileData); let zonefileCV; if (formattedZonefileData) { const zonefileString = JSON.stringify(formattedZonefileData); zonefileCV = someCV(bufferCVFromString(zonefileString)); } else { zonefileCV = noneCV(); } return { contractAddress: getZonefileContractAddress(network), contractName: ZonefileContractName, functionName: bnsFunctionName, functionArgs: [ bufferCVFromString(name), bufferCVFromString(namespace), zonefileCV, ], postConditions: [], network, }; }