import { Buffer , IntegerType } from '@stacks/common' ; import { AnchorMode , bufferCV , callReadOnlyFunction , ClarityType , ClarityValue , cvToString , getAddressFromPrivateKey , getCVTypeString , hash160 , makeRandomPrivKey , makeUnsignedContractCall , privateKeyToString , ResponseErrorCV , StacksTransaction , standardPrincipalCV , uintCV , someCV , noneCV , UnsignedContractCallOptions , PostCondition , createSTXPostCondition , createStacksPublicKey , publicKeyToAddress , FungibleConditionCode , AddressVersion , createNonFungiblePostCondition , NonFungibleConditionCode , parseAssetInfoString , tupleCV , } from '@stacks/transactions' ; import { StacksNetwork } from '@stacks/network' ; import { bufferCVFromString , decodeFQN , getZonefileHash } from './utils' ; import { ChainID } from '@stacks/common' ; export const BNS_CONTRACT_NAME = 'bns' ; export const enum BnsContractAddress { mainnet = 'SP000000000000000000002Q6VF78' , testnet = 'ST000000000000000000002AMW42H' } function getBnsContractAddress ( network ) { if ( network . chainId === ChainID . Mainnet ) return BnsContractAddress . mainnet ; else if ( network . chainId == ChainID . Testnet ) return BnsContractAddress . testnet ; else throw new Error ( `Unexpected ChainID: ${ network . chainId }` ) ; } function getAddressVersion ( network ) { return network . chainId === ChainID . Mainnet ? AddressVersion . MainnetSingleSig : AddressVersion . TestnetSingleSig ; } export interface PriceFunction { base : IntegerType ; coefficient : IntegerType ; b1 : IntegerType ; b2 : IntegerType ; b3 : IntegerType ; b4 : IntegerType ; b5 : IntegerType ; b6 : IntegerType ; b7 : IntegerType ; b8 : IntegerType ; b9 : IntegerType ; b10 : IntegerType ; b11 : IntegerType ; b12 : IntegerType ; b13 : IntegerType ; b14 : IntegerType ; b15 : IntegerType ; b16 : IntegerType ; nonAlphaDiscount : IntegerType ; noVowelDiscount : IntegerType ; } export interface BnsContractCallOptions { functionName : string ; functionArgs : ClarityValue [ ] ; publicKey : string ; network : StacksNetwork ; attachment ? : Buffer ; postConditions ? : PostCondition [ ] ; } async function makeBnsContractCall ( options ) { const txOptions = { contractAddress : getBnsContractAddress ( options . network ) , contractName : BNS_CONTRACT_NAME , functionName : options . functionName , functionArgs : options . functionArgs , publicKey : options . publicKey , validateWithAbi : false , network : options . network , anchorMode : AnchorMode . Any , postConditions : options . postConditions , } ; return makeUnsignedContractCall ( txOptions ) ; } export interface BnsReadOnlyOptions { functionName : string ; functionArgs : ClarityValue [ ] ; senderAddress : string ; network : StacksNetwork ; } async function callReadOnlyBnsFunction ( options ) { return callReadOnlyFunction ( { contractAddress : getBnsContractAddress ( options . network ) , contractName : BNS_CONTRACT_NAME , functionName : options . functionName , senderAddress : options . senderAddress , functionArgs : options . functionArgs , network : options . network , } ) ; } param { String } fullyQualifiedName param { StacksNetwork } network export interface CanRegisterNameOptions { fullyQualifiedName : string ; network : StacksNetwork ; } param { string } fullyQualifiedName param { StacksNetwork } network returns { Promise } export async function canRegisterName ( { fullyQualifiedName , network , } ) { const bnsFunctionName = 'can-name-be-registered' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot register a subdomain using registerName' ) ; } const randomPrivateKey = privateKeyToString ( makeRandomPrivKey ( ) ) ; const randomAddress = getAddressFromPrivateKey ( randomPrivateKey ) ; return callReadOnlyBnsFunction ( { functionName : bnsFunctionName , senderAddress : randomAddress , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) ] , network , } ) . then ( ( responseCV ) => { if ( responseCV . type === ClarityType . ResponseOk ) { return responseCV . value . type === ClarityType . BoolTrue ; } else { return false ; } } ) ; } param { String } namespace param { StacksNetwork } network export interface GetNamespacePriceOptions { namespace : string ; network : StacksNetwork ; } param { string } namespace param { StacksNetwork } network returns { Promise } export async function getNamespacePrice ( { namespace , network , } ) { const bnsFunctionName = 'get-namespace-price' ; const randomPrivateKey = privateKeyToString ( makeRandomPrivKey ( ) ) ; const randomAddress = getAddressFromPrivateKey ( randomPrivateKey ) ; return callReadOnlyBnsFunction ( { functionName : bnsFunctionName , senderAddress : randomAddress , functionArgs : [ bufferCVFromString ( namespace ) ] , network , } ) . then ( ( responseCV ) => { 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 ) }` ) ; } } ) ; } param { String } fullyQualifiedName param { StacksNetwork } network export interface GetNamePriceOptions { fullyQualifiedName : string ; network : StacksNetwork ; } param { string } fullyQualifiedName param { StacksNetwork } network returns { Promise } export async function getNamePrice ( { fullyQualifiedName , network , } ) { const bnsFunctionName = 'get-name-price' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot get subdomain name price' ) ; } const randomPrivateKey = privateKeyToString ( makeRandomPrivKey ( ) ) ; const randomAddress = getAddressFromPrivateKey ( randomPrivateKey ) ; return callReadOnlyBnsFunction ( { functionName : bnsFunctionName , senderAddress : randomAddress , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) ] , network , } ) . then ( ( responseCV ) => { 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 { const errorResponse = responseCV as ResponseErrorCV ; throw new Error ( cvToString ( errorResponse . value ) ) ; } } ) ; } /**
 * Preorder namespace options
 */ export interface PreorderNamespaceOptions { /** the namespace to preorder */ namespace : string ; /** salt used to generate the preorder namespace hash */ salt : string ; /** amount of STX to burn for the registration */ stxToBurn : IntegerType ; /** the private key to sign the transaction */ publicKey : string ; /** the Stacks blockchain network to use */ network : StacksNetwork ; } param { PreorderNamespaceOptions } options return { Promise < StacksTransaction > } export async function buildPreorderNamespaceTx ( { namespace , salt , stxToBurn , publicKey , network , } ) { const bnsFunctionName = 'namespace-preorder' ; const saltedNamespaceBuffer = Buffer . from ( `${ namespace }${ salt }` ) ; const hashedSaltedNamespace = hash160 ( saltedNamespaceBuffer ) ; const burnSTXPostCondition = createSTXPostCondition ( publicKeyToAddress ( getAddressVersion ( network ) , createStacksPublicKey ( publicKey ) ) , FungibleConditionCode . Equal , stxToBurn ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCV ( hashedSaltedNamespace ) , uintCV ( stxToBurn ) ] , publicKey , network , postConditions : [ burnSTXPostCondition ] , } ) ; } /**
 * Reveal namespace options
 */ export interface RevealNamespaceOptions { /** the namespace to reveal */ namespace : string ; /** salt used to generate the preorder namespace hash */ salt : string ; /** an object containing the price function for the namespace */ priceFunction : PriceFunction ; /** the number of blocks name registrations are valid for in the namespace */ lifetime : IntegerType ; /** the STX address used for name imports */ namespaceImportAddress : string ; /** the key to sign the transaction */ publicKey : string ; /** the Stacks blockchain network to use */ network : StacksNetwork ; } param { RevealNamespaceOptions } options return { Promise < StacksTransaction > } export async function buildRevealNamespaceTx ( { namespace , salt , priceFunction , lifetime , namespaceImportAddress , publicKey , network , } ) { const bnsFunctionName = 'namespace-reveal' ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( salt ) , 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 ) , uintCV ( lifetime ) , standardPrincipalCV ( namespaceImportAddress ) , ] , publicKey , network , } ) ; } param { String } namespace param { String } name param { String } beneficiary param { String } zonefileHash param { String } publicKey param { StacksNetwork } network export interface ImportNameOptions { namespace : string ; name : string ; beneficiary : string ; zonefile : string ; publicKey : string ; network : StacksNetwork ; } param { ImportNameOptions } options return { Promise < StacksTransaction > } export async function buildImportNameTx ( { namespace , name , beneficiary , zonefile , publicKey , network , } ) { const bnsFunctionName = 'name-import' ; const zonefileHash = getZonefileHash ( zonefile ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) , standardPrincipalCV ( beneficiary ) , bufferCV ( zonefileHash ) , ] , publicKey , network , attachment : Buffer . from ( zonefile ) , } ) ; } param { String } namespace param { String } publicKey param { StacksNetwork } network export interface ReadyNamespaceOptions { namespace : string ; publicKey : string ; network : StacksNetwork ; } param { ReadyNamespaceOptions } options return { Promise < StacksTransaction > } export async function buildReadyNamespaceTx ( { namespace , publicKey , network , } ) { const bnsFunctionName = 'namespace-ready' ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) ] , publicKey , network , } ) ; } /**
 * Preorder name options
 */ export interface PreorderNameOptions { /** the fully qualified name to preorder including the namespace (myName.id) */ fullyQualifiedName : string ; /** salt used to generate the preorder name hash */ salt : string ; /** amount of STX to burn for the registration */ stxToBurn : IntegerType ; /** the private key to sign the transaction */ publicKey : string ; /** the Stacks blockchain network to use */ network : StacksNetwork ; } param { PreorderNameOptions } options return { Promise < StacksTransaction > } export async function buildPreorderNameTx ( { fullyQualifiedName , salt , stxToBurn , publicKey , network , } ) { const bnsFunctionName = 'name-preorder' ; const { subdomain } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot preorder a subdomain using preorderName()' ) ; } const saltedNamesBuffer = Buffer . from ( `${ fullyQualifiedName }${ salt }` ) ; const hashedSaltedName = hash160 ( saltedNamesBuffer ) ; const burnSTXPostCondition = createSTXPostCondition ( publicKeyToAddress ( getAddressVersion ( network ) , createStacksPublicKey ( publicKey ) ) , FungibleConditionCode . Equal , stxToBurn ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCV ( hashedSaltedName ) , uintCV ( stxToBurn ) ] , publicKey , network , postConditions : [ burnSTXPostCondition ] , } ) ; } param { String } fullyQualifiedName param { String } salt param { String } zonefile param { String } publicKey param { StacksNetwork } network export interface RegisterNameOptions { fullyQualifiedName : string ; salt : string ; zonefile : string ; publicKey : string ; network : StacksNetwork ; } param { RegisterNameOptions } options return { Promise < StacksTransaction > } export async function buildRegisterNameTx ( { fullyQualifiedName , salt , zonefile , publicKey , network , } ) { const bnsFunctionName = 'name-register' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot register a subdomain using registerName()' ) ; } const zonefileHash = getZonefileHash ( zonefile ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) , bufferCVFromString ( salt ) , bufferCV ( zonefileHash ) , ] , network , publicKey , attachment : Buffer . from ( zonefile ) , } ) ; } param { String } fullyQualifiedName param { String } zonefile param { String } publicKey param { StacksNetwork } network export interface UpdateNameOptions { fullyQualifiedName : string ; zonefile : string ; publicKey : string ; network : StacksNetwork ; } param { UpdateNameOptions } options return { Promise < StacksTransaction > } export async function buildUpdateNameTx ( { fullyQualifiedName , zonefile , publicKey , network , } ) { const bnsFunctionName = 'name-update' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot update a subdomain using updateName()' ) ; } const zonefileHash = getZonefileHash ( zonefile ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) , bufferCV ( zonefileHash ) ] , publicKey , network , attachment : Buffer . from ( zonefile ) , } ) ; } param { String } fullyQualifiedName param { String } newOwnerAddress param { String } zonefile param { String } publicKey param { StacksNetwork } network export interface TransferNameOptions { fullyQualifiedName : string ; newOwnerAddress : string ; publicKey : string ; network : StacksNetwork ; zonefile ? : string ; } param { TransferNameOptions } options return { Promise < StacksTransaction > } export async function buildTransferNameTx ( { fullyQualifiedName , newOwnerAddress , zonefile , publicKey , network , } ) { const bnsFunctionName = 'name-transfer' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot transfer a subdomain using transferName()' ) ; } const functionArgs = [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) , standardPrincipalCV ( newOwnerAddress ) , zonefile ? someCV ( bufferCV ( getZonefileHash ( zonefile ) ) ) : noneCV ( ) , ] ; const postConditionSender = createNonFungiblePostCondition ( publicKeyToAddress ( getAddressVersion ( network ) , createStacksPublicKey ( publicKey ) ) , NonFungibleConditionCode . DoesNotOwn , parseAssetInfoString ( `${ getBnsContractAddress ( network ) }.bns::names` ) , tupleCV ( { name : bufferCVFromString ( name ) , namespace : bufferCVFromString ( namespace ) , } ) ) ; const postConditionReceiver = createNonFungiblePostCondition ( newOwnerAddress , NonFungibleConditionCode . Owns , parseAssetInfoString ( `${ getBnsContractAddress ( network ) }.bns::names` ) , tupleCV ( { name : bufferCVFromString ( name ) , namespace : bufferCVFromString ( namespace ) , } ) ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs , publicKey , network , attachment : zonefile ? Buffer . from ( zonefile ) : undefined , postConditions : [ postConditionSender , postConditionReceiver ] , } ) ; } param { String } fullyQualifiedName param { String } publicKey param { StacksNetwork } network export interface RevokeNameOptions { fullyQualifiedName : string ; publicKey : string ; network : StacksNetwork ; } param { RevokeNameOptions } options return { Promise < StacksTransaction > } export async function buildRevokeNameTx ( { fullyQualifiedName , publicKey , network , } ) { const bnsFunctionName = 'name-revoke' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot revoke a subdomain using revokeName()' ) ; } return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs : [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) ] , publicKey , network , } ) ; } /**
 * Renew name options
 */ export interface RenewNameOptions { /** the fully qualified name to renew including the namespace (myName.id) */ fullyQualifiedName : string ; /** amount of STX to burn for the registration */ stxToBurn : IntegerType ; /** the private key to sign the transaction */ publicKey : string ; /** the Stacks blockchain network to use */ network : StacksNetwork ; /** optionally choose a new owner address */ newOwnerAddress ? : string ; /** optionally update the zonefile hash */ zonefile ? : string ; } param { RenewNameOptions } options return { Promise < StacksTransaction > } export async function buildRenewNameTx ( { fullyQualifiedName , stxToBurn , newOwnerAddress , zonefile , publicKey , network , } ) { const bnsFunctionName = 'name-renewal' ; const { subdomain , namespace , name } = decodeFQN ( fullyQualifiedName ) ; if ( subdomain ) { throw new Error ( 'Cannot renew a subdomain using renewName()' ) ; } const functionArgs = [ bufferCVFromString ( namespace ) , bufferCVFromString ( name ) , uintCV ( stxToBurn ) , newOwnerAddress ? someCV ( standardPrincipalCV ( newOwnerAddress ) ) : noneCV ( ) , zonefile ? someCV ( bufferCV ( getZonefileHash ( zonefile ) ) ) : noneCV ( ) , ] ; const burnSTXPostCondition = createSTXPostCondition ( publicKeyToAddress ( getAddressVersion ( network ) , createStacksPublicKey ( publicKey ) ) , FungibleConditionCode . Equal , stxToBurn ) ; return makeBnsContractCall ( { functionName : bnsFunctionName , functionArgs , publicKey , network , attachment : zonefile ? Buffer . from ( zonefile ) : undefined , postConditions : [ burnSTXPostCondition ] , } ) ; }