diff --git a/packages/network/README.md b/packages/network/README.md new file mode 100644 index 000000000..79055c5be --- /dev/null +++ b/packages/network/README.md @@ -0,0 +1,11 @@ +# `network` + +> TODO: description + +## Usage + +``` +const network = require('network'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/network/__tests__/network.test.js b/packages/network/__tests__/network.test.js new file mode 100644 index 000000000..3ef7f4b20 --- /dev/null +++ b/packages/network/__tests__/network.test.js @@ -0,0 +1,7 @@ +'use strict'; + +const network = require('..'); + +describe('network', () => { + it('needs tests'); +}); diff --git a/packages/network/package.json b/packages/network/package.json new file mode 100644 index 000000000..1813de26e --- /dev/null +++ b/packages/network/package.json @@ -0,0 +1,42 @@ +{ + "name": "@stacks/network", + "version": "1.0.0", + "description": "Library for Stacks network operations", + "keywords": [ + "stacks", + "blockstack", + "network" + ], + "author": "yknl ", + "homepage": "https://blockstack.org", + "license": "GPL-3.0-or-later", + "main": "./dist/index.js", + "umd:main": "./dist/common.umd.production.js", + "module": "./dist/common.esm.js", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/blockstack/blockstack.js.git" + }, + "scripts": { + "build": "cross-env NODE_ENV=production tsdx build --format=cjs,esm,umd", + "build-all": "run-p build:*", + "build:cjs": "tsc --outDir ./lib -m commonjs -t es2017", + "build:esm": "tsc --outDir ./lib-esm -m es6 -t es2017", + "build:cjs:watch": "tsc --outDir ./lib -m commonjs -t es2017 --watch", + "build:esm:watch": "tsc --outDir ./lib-esm -m es6 -t es2017 --watch", + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "bugs": { + "url": "https://github.com/blockstack/blockstack.js/issues" + }, + "dependencies": { + "@stacks/common": "^1.0.0" + } +} diff --git a/packages/network/src/index.ts b/packages/network/src/index.ts new file mode 100644 index 000000000..e5c125a30 --- /dev/null +++ b/packages/network/src/index.ts @@ -0,0 +1,113 @@ +import { TransactionVersion, ChainID, fetchPrivate } from '@stacks/common'; + +export interface StacksNetwork { + version: TransactionVersion; + + chainId: ChainID; + + coreApiUrl: string; + + broadcastEndpoint: string; + + transferFeeEstimateEndpoint: string; + + accountEndpoint: string; + + contractAbiEndpoint: string; + + readOnlyFunctionCallEndpoint: string; + + getBroadcastApiUrl: () => string; + + getTransferFeeEstimateApiUrl: () => string; + + getAccountApiUrl: (address: string) => string; + + getAbiApiUrl: (address: string, contract: string) => string; + + getReadOnlyFunctionCallApiUrl: ( + contractAddress: string, + contractName: string, + functionName: 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; + + coreApiUrl = 'https://core.blockstack.org'; + + broadcastEndpoint = '/v2/transactions'; + + transferFeeEstimateEndpoint = '/v2/fees/transfer'; + + accountEndpoint = '/v2/accounts'; + + contractAbiEndpoint = '/v2/contracts/interface'; + + readOnlyFunctionCallEndpoint = '/v2/contracts/call-read'; + + getBroadcastApiUrl = () => `${this.coreApiUrl}${this.broadcastEndpoint}`; + + getTransferFeeEstimateApiUrl = () => `${this.coreApiUrl}${this.transferFeeEstimateEndpoint}`; + + getAccountApiUrl = (address: string) => + `${this.coreApiUrl}${this.accountEndpoint}/${address}?proof=0`; + + getAbiApiUrl = (address: string, contract: string) => + `${this.coreApiUrl}${this.contractAbiEndpoint}/${address}/${contract}`; + + getReadOnlyFunctionCallApiUrl = ( + contractAddress: string, + contractName: string, + functionName: string + ) => + `${this.coreApiUrl}${ + this.readOnlyFunctionCallEndpoint + }/${contractAddress}/${contractName}/${encodeURIComponent(functionName)}`; + + getNameInfo(fullyQualifiedName: string) { + /* + TODO: Update to v2 API URL for name lookups + */ + const nameLookupURL = `${this.coreApiUrl}/v1/names/${fullyQualifiedName}` + return fetchPrivate(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; + coreApiUrl = 'http://testnet-master.blockstack.org:20443'; +} diff --git a/packages/network/tsconfig.json b/packages/network/tsconfig.json new file mode 100644 index 000000000..029987752 --- /dev/null +++ b/packages/network/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "./src/**/*" + ] +}