All files / src/network index.ts

59.67% Statements 37/62
42.85% Branches 9/21
44.44% Functions 8/18
47.72% Lines 21/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 17422x       22x 22x 22x                                                                                                     68x                         5x       1x           68x 68x     68x 68x   23x   6x 6x 5x   4x         2x                                                                                                               22x           21x     22x           1x        
import { ChainID, fetchPrivate, TransactionVersion } from 'micro-stacks/common';
 
type Fetcher = (input: RequestInfo, init?: RequestInit) => Promise<Response>;
 
export const HIRO_MAINNET_DEFAULT = 'https://stacks-node-api.mainnet.stacks.co';
export const HIRO_TESTNET_DEFAULT = 'https://stacks-node-api.testnet.stacks.co';
export const HIRO_MOCKNET_DEFAULT = 'http://localhost:3999';
 
export interface NetworkConfig {
  // TODO: deprecate
  url?: string;
  coreApiUrl?: string;
  bnsLookupUrl?: string;
  fetcher?: Fetcher;
}
 
export interface StacksNetwork {
  version: TransactionVersion;
  chainId: ChainID;
  bnsLookupUrl: string;
  broadcastEndpoint: string;
  transferFeeEstimateEndpoint: string;
  accountEndpoint: string;
  contractAbiEndpoint: string;
  readOnlyFunctionCallEndpoint: string;
 
  isMainnet(): boolean;
 
  getBroadcastApiUrl: () => string;
  getTransferFeeEstimateApiUrl: () => string;
  getAccountApiUrl: (address: string) => string;
  getAbiApiUrl: (address: string, contract: string) => string;
  getReadOnlyFunctionCallApiUrl: (
    contractAddress: string,
    contractName: string,
    functionName: string
  ) => string;
  getCoreApiUrl: () => string;
  getInfoUrl: () => string;
  getBlockTimeInfoUrl: () => string;
  getPoxInfoUrl: () => string;
  getRewardsUrl: (address: string, options?: any) => string;
  getRewardHoldersUrl: (address: string, options?: any) => string;
  getRewardsTotalUrl: (address: string) => string;
  getStackerInfoUrl: (contractAddress: string, contractName: string) => string;
 
  /**
   * Get WHOIS-like information for a name, including the address that owns it,
   * the block at which it expires, and the zone file anchored to it (if available).
   *
   * This is intended for use in third-party wallets or in DApps that register names.
   * @param fullyQualifiedName the name to query.  Can be on-chain of off-chain.
   * @return a promise that resolves to the WHOIS-like information
   */
  getNameInfo: (fullyQualifiedName: string) => any;
}
 
export class StacksMainnet implements StacksNetwork {
  version = TransactionVersion.Mainnet;
  chainId = ChainID.Mainnet;
  broadcastEndpoint = '/v2/transactions';
  transferFeeEstimateEndpoint = '/v2/fees/transfer';
  accountEndpoint = '/v2/accounts';
  contractAbiEndpoint = '/v2/contracts/interface';
  readOnlyFunctionCallEndpoint = '/v2/contracts/call-read';
  bnsLookupUrl: string;
  private _coreApiUrl: string;
  private fetcher: Fetcher;
 
  get coreApiUrl() {
    return this._coreApiUrl;
  }
 
  set coreApiUrl(_url: string) {
    throw new Error('Cannot modify property `coreApiUrl` after object initialization');
  }
 
  constructor(networkConfig: NetworkConfig = { url: HIRO_MAINNET_DEFAULT }) {
    if (!networkConfig.url && !networkConfig.coreApiUrl)
      throw Error('[miro-stacks] Network initialized with no api url');
    Ithis._coreApiUrl = (networkConfig.url || networkConfig.coreApiUrl) as string;
    this.bnsLookupUrl = (networkConfig.bnsLookupUrl ||
      networkConfig.url ||
      networkConfig.coreApiUrl) as string;
    this.fetcher = networkConfig.fetcher || fetchPrivate;
  }
 
  getCoreApiUrl = () => this._coreApiUrl;
  isMainnet = () => this.version === TransactionVersion.Mainnet;
  getBroadcastApiUrl = () => `${this.getCoreApiUrl()}${this.broadcastEndpoint}`;
  getTransferFeeEstimateApiUrl = () => `${this.getCoreApiUrl()}${this.transferFeeEstimateEndpoint}`;
  getAccountApiUrl = (address: string) =>
    `${this.getCoreApiUrl()}${this.accountEndpoint}/${address}?proof=0`;
  getAbiApiUrl = (address: string, contract: string) =>
    `${this.getCoreApiUrl()}${this.contractAbiEndpoint}/${address}/${contract}`;
  getReadOnlyFunctionCallApiUrl = (
    contractAddress: string,
    contractName: string,
    functionName: string
  ) =>
    `${this.getCoreApiUrl()}${
      this.readOnlyFunctionCallEndpoint
    }/${contractAddress}/${contractName}/${encodeURIComponent(functionName)}`;
  getInfoUrl = () => `${this.getCoreApiUrl()}/v2/info`;
  getBlockTimeInfoUrl = () => `${this.getCoreApiUrl()}/extended/v1/info/network_block_times`;
  getPoxInfoUrl = () => `${this.getCoreApiUrl()}/v2/pox`;
  getRewardsUrl = (address: string, options?: any) => {
    let url = `${this.getCoreApiUrl()}/extended/v1/burnchain/rewards/${address}`;
    Iif (options) {
      url = `${url}?limit=${options.limit}&offset=${options.offset}`;
    }
    return url;
  };
  getRewardsTotalUrl = (address: string) =>
    `${this.getCoreApiUrl()}/extended/v1/burnchain/rewards/${address}/total`;
  getRewardHoldersUrl = (address: string, options?: any) => {
    let url = `${this.getCoreApiUrl()}/extended/v1/burnchain/reward_slot_holders/${address}`;
    Iif (options) {
      url = `${url}?limit=${options.limit}&offset=${options.offset}`;
    }
    return url;
  };
  getStackerInfoUrl = (contractAddress: string, contractName: string) =>
    `${this.getCoreApiUrl()}${this.readOnlyFunctionCallEndpoint}
    ${contractAddress}/${contractName}/get-stacker-info`;
 
  getNameInfo(fullyQualifiedName: string) {
    /*
      TODO: Update to v2 API URL for name lookups
    */
    const nameLookupURL = `${this.bnsLookupUrl}/v1/names/${fullyQualifiedName}`;
    return this.fetcher(nameLookupURL)
      .then(resp => {
        if (resp.status === 404) {
          throw new Error('Name not found');
        } else if (resp.status !== 200) {
          throw new Error(`Bad response status: ${resp.status}`);
        } else {
          return resp.json();
        }
      })
      .then(nameInfo => {
        // the returned address _should_ be in the correct network ---
        //  blockstackd gets into trouble because it tries to coerce back to mainnet
        //  and the regtest transaction generation libraries want to use testnet addresses
        if (nameInfo.address) {
          return Object.assign({}, nameInfo, { address: nameInfo.address });
        } else {
          return nameInfo;
        }
      });
  }
}
 
export class StacksTestnet extends StacksMainnet implements StacksNetwork {
  version = TransactionVersion.Testnet;
  chainId = ChainID.Testnet;
 
  constructor(networkConfig: NetworkConfig = { url: HIRO_TESTNET_DEFAULT, fetcher: fetchPrivate }) {
    super(networkConfig);
  }
}
 
export class StacksMocknet extends StacksMainnet implements StacksNetwork {
  version = TransactionVersion.Testnet;
  chainId = ChainID.Testnet;
 
  constructor(networkConfig: NetworkConfig = { url: HIRO_MOCKNET_DEFAULT, fetcher: fetchPrivate }) {
    super(networkConfig);
  }
}
 
export { ChainID, TransactionVersion };