diff --git a/.github/MIGRATION.md b/.github/MIGRATION.md index 7d70ea1f8..eeb205f77 100644 --- a/.github/MIGRATION.md +++ b/.github/MIGRATION.md @@ -2,13 +2,12 @@ - [Stacks.js (\>=5.x.x) → (7.x.x)](#stacksjs-5xx--7xx) - [Breaking Changes](#breaking-changes) + - [Reducing Wrapper Types](#reducing-wrapper-types) - [Stacks Network](#stacks-network) - [Impacts](#impacts) - - [Fetch Methods](#fetch-methods) - - [Reducing Wrapper Types](#reducing-wrapper-types) - - [Stacks Network](#stacks-network-1) - [Clarity Representation](#clarity-representation) - [Post-conditions](#post-conditions) + - [Fetch Methods](#fetch-methods) - [`serialize` methods](#serialize-methods) - [Asset Helper Methods](#asset-helper-methods) - [CLI](#cli) @@ -47,6 +46,15 @@ - **Advanced:** Removes two's complement compatibilty from `intToBigInt` parser method. [Read more...](#advanced-signed-bigint) - **Advanced:** Refactorings and less visible updates. [Read more...](#advanced-refactorings) +### Reducing Wrapper Types + +With this release we are aiming to reduce unnecessary "wrapper" types, which are used in the internals of the codebase, but shouldn't be pushed onto the user/developer. + +This breaks the signatures of many functions: + +- `signMessageHashRsv`, `signWithKey` now return the message signature as a `string` directly. +- `nextSignature`, `nextVerification`, `publicKeyFromSignatureVrs`, `publicKeyFromSignatureRsv` now take in the message signature as a `string`. + ### Stacks Network From now on "network" objects are static (aka constants) and don't require instantiation. @@ -62,51 +70,26 @@ The `@stacks/network` package exports the following network objects: import { STACKS_MAINNET } from '@stacks/network'; import { STACKS_TESTNET } from '@stacks/network'; import { STACKS_DEVNET } from '@stacks/network'; -``` - -#### Impacts - -- @stacks/bns: `BnsContractAddress` was removed, since `.bootAddress` is now a part of the network objects. -- @stacks/transactions: `AddressVersion` was moved to `@stacks/network`. - -### Fetch Methods - -The following methods were renamed: - -- `estimateFee` → `fetchFeeEstimate` -- `estimateTransfer` → `fetchFeeEstimateTransfer` -- `estimateTransaction` → `fetchFeeEstimateTransaction` -- `getAbi` → `fetchAbi` -- `getNonce` → `fetchNonce` -- `getContractMapEntry` → `fetchContractMapEntry` -- `callReadOnlyFunction` → `fetchCallReadOnlyFunction` - -`broadcastTransaction` wasn't renamed to highlight the uniqueness of the method. -Namely, the node/API it is sent to will "broadcast" the transaction to the mempool. - -### Reducing Wrapper Types - -With this release we are aiming to reduce unnecessary "wrapper" types, which are used in the internals of the codebase, but shouldn't be pushed onto the user/developer. - -This breaks the signatures of many functions: - -- `signMessageHashRsv`, `signWithKey` now return the message signature as a `string` directly. -- `nextSignature`, `nextVerification`, `publicKeyFromSignatureVrs`, `publicKeyFromSignatureRsv` now take in the message signature as a `string`. - -### Stacks Network -Stacks network objects are now exported by the `@stacks/common` package. -They are used to specify network settings for other functions and don't require instantiation (like the `@stacks/network` approach did). - -```ts -import { STACKS_MAINNET } from '@stacks/transactions'; +console.log(STACKS_MAINNET); +// { +// chainId: 1, +// transactionVersion: 0, +// peerNetworkId: 385875968, +// magicBytes: 'X2', +// bootAddress: 'SP000000000000000000002Q6VF78', +// addressVersion: { singleSig: 22, multiSig: 20 }, +// client: { baseUrl: 'https://api.mainnet.hiro.so' } +// } ``` -After importing the network object (e.g. `STACKS_MAINNET` here), you can use it in other functions like so: +After importing the network object (e.g. `STACKS_MAINNET` here), you can use it in other functions as the `network` parameter. +Most of the time it's easier to just set the `network` parameter to a string literal (`'mainnet'`, `'testnet'`, or `'devnet'`). - +As part of the network object, the `client` property was added. +This contains a `baseUrl` property and can optionally contain a `fetch` property. -For easing the transition, the functions which depended on a network instance now accept an `client` parameter. +For easing the transition, the functions which depended on a network instance now accept an optional `client` parameter. The `client` parameter can be any object containing a `baseUrl` and `fetch` property. - The `baseUrl` property should be a string containing the base URL of the Stacks node you want to use. @@ -139,7 +122,7 @@ const transaction = await makeSTXTokenTransfer({ ``` > [!NOTE] -> Custom URLs and fetch functions are still supported via the `client` parameter. +> Custom URLs and fetch functions are still supported via the `client` parameter or via `network.client`. ```diff const transaction = await makeSTXTokenTransfer({ @@ -150,6 +133,22 @@ const transaction = await makeSTXTokenTransfer({ }); ``` +```diff +const transaction = await makeSTXTokenTransfer({ + // ... +- network: new StacksTestnet({ url: "mynode-optional.com", fetchFn: myFetch }), // optional options ++ network: { ++ ...STACKS_TESTNET, ++ client: { baseUrl: "mynode-optional.com", fetch: myFetch } // optional params ++ }, +}); +``` + +#### Impacts + +- @stacks/bns: `BnsContractAddress` was removed, since `.bootAddress` is now a part of the network objects. +- @stacks/transactions: `AddressVersion` was moved to `@stacks/network`. + ### Clarity Representation The `ClarityType` enum was replaced by a readable version. @@ -213,6 +212,21 @@ const nftPostCondition: NonFungiblePostCondition = { }; ``` +### Fetch Methods + +The following methods were renamed: + +- `estimateFee` → `fetchFeeEstimate` +- `estimateTransfer` → `fetchFeeEstimateTransfer` +- `estimateTransaction` → `fetchFeeEstimateTransaction` +- `getAbi` → `fetchAbi` +- `getNonce` → `fetchNonce` +- `getContractMapEntry` → `fetchContractMapEntry` +- `callReadOnlyFunction` → `fetchCallReadOnlyFunction` + +`broadcastTransaction` wasn't renamed to highlight the uniqueness of the method. +Namely, the node/API it is sent to will "broadcast" the transaction to the mempool. + ### `serialize` methods Existing methods now take or return **hex-encoded strings** _instead_ of `Uint8Array`s. diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index c8935c167..a05ae035b 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -1,5 +1,6 @@ import { FetchFn, Hex, createFetchFn } from '@stacks/common'; import { + NetworkParam, STACKS_MAINNET, StacksNetwork, StacksNetworkName, @@ -46,11 +47,9 @@ export class StacksNodeApi { }: { /** The base API/node URL for the network fetch calls */ baseUrl?: string; - /** Stacks network object (defaults to {@link STACKS_MAINNET}) */ - network?: StacksNetworkName | StacksNetwork; /** An optional custom fetch function to override default behaviors */ fetch?: FetchFn; - } = {}) { + } & NetworkParam = {}) { this.baseUrl = baseUrl ?? defaultUrlFromNetwork(network); this.fetch = fetch ?? createFetchFn(); this.network = networkFrom(network); @@ -67,10 +66,10 @@ export class StacksNodeApi { */ broadcastTransaction = async ( transaction: StacksTransaction, - attachment?: Uint8Array | string + attachment?: Uint8Array | string, + network?: StacksNetworkName | StacksNetwork ): Promise => { - // todo: should we use a opts object instead of positional args here? - return broadcastTransaction({ transaction, attachment, client: this }); + return broadcastTransaction({ transaction, attachment, network }); }; /** diff --git a/packages/auth/src/profile.ts b/packages/auth/src/profile.ts index 4a59c004c..9712b3af3 100644 --- a/packages/auth/src/profile.ts +++ b/packages/auth/src/profile.ts @@ -1,10 +1,9 @@ -import { ClientOpts, ClientParam, defaultClientOpts } from '@stacks/common'; +import { NetworkClientParam, clientFromNetwork, networkFrom } from '@stacks/network'; import { resolveZoneFileToProfile } from '@stacks/profile'; export interface ProfileLookupOptions { username: string; zoneFileLookupURL?: string; - client?: ClientOpts; } /** @@ -16,19 +15,22 @@ export interface ProfileLookupOptions { * blockstack.js [[getNameInfo]] function. * @returns {Promise} that resolves to a profile object */ -export function lookupProfile(options: ProfileLookupOptions): Promise> { +export function lookupProfile( + options: ProfileLookupOptions & NetworkClientParam +): Promise> { if (!options.username) { return Promise.reject(new Error('No username provided')); } - const client = defaultClientOpts(options.client); + const network = networkFrom(options.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), options.client); let lookupPromise; if (options.zoneFileLookupURL) { const url = `${options.zoneFileLookupURL.replace(/\/$/, '')}/${options.username}`; lookupPromise = client.fetch(url).then(response => response.json()); } else { - lookupPromise = getNameInfo({ name: options.username, client: client }); + lookupPromise = getNameInfo({ name: options.username }); } return lookupPromise.then((responseJSON: any) => { if (responseJSON.hasOwnProperty('zonefile') && responseJSON.hasOwnProperty('address')) { @@ -49,12 +51,13 @@ export function getNameInfo( opts: { /** Fully qualified name */ name: string; - } & ClientParam + } & NetworkClientParam ) { - const api = defaultClientOpts(opts.client); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); - const nameLookupURL = `${api.baseUrl}/v1/names/${opts.name}`; - return api + const nameLookupURL = `${client.baseUrl}/v1/names/${opts.name}`; + return client .fetch(nameLookupURL) .then((resp: any) => { if (resp.status === 404) { diff --git a/packages/bns/src/index.ts b/packages/bns/src/index.ts index 388e80ca0..9b09234df 100644 --- a/packages/bns/src/index.ts +++ b/packages/bns/src/index.ts @@ -1,5 +1,5 @@ -import { ClientParam, IntegerType, PublicKey, intToBigInt, utf8ToBytes } from '@stacks/common'; -import { StacksNetwork } from '@stacks/network'; +import { IntegerType, PublicKey, intToBigInt, utf8ToBytes } from '@stacks/common'; +import { NetworkClientParam, StacksNetwork } from '@stacks/network'; import { ClarityType, ClarityValue, @@ -83,15 +83,12 @@ export interface BnsReadOnlyOptions { } async function callReadOnlyBnsFunction( - options: BnsReadOnlyOptions & ClientParam + options: BnsReadOnlyOptions & NetworkClientParam ): Promise { return fetchCallReadOnlyFunction({ + ...options, contractAddress: options.network.bootAddress, contractName: BNS_CONTRACT_NAME, - functionName: options.functionName, - senderAddress: options.senderAddress, - functionArgs: options.functionArgs, - client: options.client, }); } diff --git a/packages/bns/tests/bns.test.ts b/packages/bns/tests/bns.test.ts index a36c7d2d6..d86c2f105 100644 --- a/packages/bns/tests/bns.test.ts +++ b/packages/bns/tests/bns.test.ts @@ -57,7 +57,7 @@ test('canRegisterName true', async () => { bufferCV(utf8ToBytes(fullyQualifiedName.split('.')[0])), ], senderAddress: notRandomAddress, - client: undefined, + network: STACKS_TESTNET, }; expect(result).toEqual(true); @@ -96,7 +96,7 @@ test('canRegisterName false', async () => { bufferCV(utf8ToBytes(fullyQualifiedName.split('.')[0])), ], senderAddress: notRandomAddress, - client: undefined, + network: STACKS_TESTNET, }; expect(result).toEqual(false); @@ -135,7 +135,7 @@ test('canRegisterName error', async () => { bufferCV(utf8ToBytes(fullyQualifiedName.split('.')[0])), ], senderAddress: notRandomAddress, - client: undefined, + network: STACKS_TESTNET, }; expect(result).toEqual(false); @@ -171,7 +171,7 @@ test('getNamespacePrice', async () => { functionName: bnsFunctionName, senderAddress: address, functionArgs: [bufferCVFromString(namespace)], - client: undefined, + network: STACKS_TESTNET, }; expect(result.toString()).toEqual('10'); @@ -206,7 +206,7 @@ test('getNamespacePrice error', async () => { functionName: bnsFunctionName, senderAddress: address, functionArgs: [bufferCVFromString(namespace)], - client: undefined, + network: STACKS_TESTNET, }; await expect(getNamespacePrice({ namespace, network })).rejects.toEqual(new Error('u1001')); @@ -244,7 +244,7 @@ test('getNamePrice', async () => { functionName: bnsFunctionName, senderAddress: address, functionArgs: [bufferCVFromString(namespace), bufferCVFromString(name)], - client: undefined, + network: STACKS_TESTNET, }; expect(result.toString()).toEqual('10'); @@ -281,7 +281,7 @@ test('getNamePrice error', async () => { functionName: bnsFunctionName, senderAddress: address, functionArgs: [bufferCVFromString(namespace), bufferCVFromString(name)], - client: undefined, + network: STACKS_TESTNET, }; await expect(getNamePrice({ fullyQualifiedName, network })).rejects.toEqual(new Error('u2001')); diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 73a842900..3036c8b02 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -90,12 +90,7 @@ import { CLI_NETWORK_OPTS, CLINetworkAdapter, getNetwork, NameInfoType } from '. import { gaiaAuth, gaiaConnect, gaiaUploadProfileAll, getGaiaAddressFromProfile } from './data'; -import { - defaultClientOptsFromNetwork, - defaultUrlFromNetwork, - STACKS_MAINNET, - STACKS_TESTNET, -} from '@stacks/network'; +import { defaultUrlFromNetwork, STACKS_MAINNET, STACKS_TESTNET } from '@stacks/network'; import { generateNewAccount, generateWallet, @@ -698,7 +693,6 @@ async function sendTokens(_network: CLINetworkAdapter, args: string[]): Promise< } const network = _network.isMainnet() ? STACKS_MAINNET : STACKS_TESTNET; - const client = defaultClientOptsFromNetwork(network); const options: SignedTokenTransferOptions = { recipient: recipientAddress, @@ -713,7 +707,7 @@ async function sendTokens(_network: CLINetworkAdapter, args: string[]): Promise< const tx: StacksTransaction = await makeSTXTokenTransfer(options); if (estimateOnly) { - return fetchFeeEstimateTransfer({ transaction: tx, client }).then(cost => { + return fetchFeeEstimateTransfer({ transaction: tx, network }).then(cost => { return cost.toString(10); }); } @@ -722,7 +716,7 @@ async function sendTokens(_network: CLINetworkAdapter, args: string[]): Promise< return Promise.resolve(tx.serialize()); } - return broadcastTransaction({ transaction: tx, client }) + return broadcastTransaction({ transaction: tx, network }) .then((response: TxBroadcastResult) => { if (response.hasOwnProperty('error')) { return response; @@ -815,13 +809,12 @@ async function contractFunctionCall(_network: CLINetworkAdapter, args: string[]) // temporary hack to use network config from stacks-transactions lib const network = _network.isMainnet() ? STACKS_MAINNET : STACKS_TESTNET; - const client = defaultClientOptsFromNetwork(network); let abi: ClarityAbi; let abiArgs: ClarityFunctionArg[]; let functionArgs: ClarityValue[] = []; - return fetchAbi({ contractAddress, contractName, client }) + return fetchAbi({ contractAddress, contractName, network }) .then(responseAbi => { abi = responseAbi; const filtered = abi.functions.filter(fn => fn.name === functionName); @@ -846,7 +839,6 @@ async function contractFunctionCall(_network: CLINetworkAdapter, args: string[]) nonce, network, postConditionMode: PostConditionMode.Allow, - client, }; return makeContractCall(options); @@ -867,7 +859,7 @@ async function contractFunctionCall(_network: CLINetworkAdapter, args: string[]) return Promise.resolve(tx.serialize()); } - return broadcastTransaction({ transaction: tx, client }) + return broadcastTransaction({ transaction: tx, network }) .then(response => { if (response.hasOwnProperty('error')) { return response; @@ -901,13 +893,12 @@ async function readOnlyContractFunctionCall( const senderAddress = args[3]; const network = _network.isMainnet() ? STACKS_MAINNET : STACKS_TESTNET; - const client = defaultClientOptsFromNetwork(network); let abi: ClarityAbi; let abiArgs: ClarityFunctionArg[]; let functionArgs: ClarityValue[] = []; - return fetchAbi({ contractAddress, contractName, client }) + return fetchAbi({ contractAddress, contractName, network }) .then(responseAbi => { abi = responseAbi; const filtered = abi.functions.filter(fn => fn.name === functionName); @@ -1634,8 +1625,7 @@ async function stackingStatus(_network: CLINetworkAdapter, args: string[]): Prom const address = args[0]; const network = _network.isMainnet() ? STACKS_MAINNET : STACKS_TESTNET; - const client = defaultClientOptsFromNetwork(network); - const stacker = new StackingClient({ address, network, client }); + const stacker = new StackingClient({ address, network }); return stacker .getStatus() @@ -1667,11 +1657,10 @@ async function canStack(_network: CLINetworkAdapter, args: string[]): Promise { if (response.hasOwnProperty('error')) { return response; @@ -1839,7 +1826,6 @@ async function preorder(_network: CLINetworkAdapter, args: string[]): Promise { if (response.hasOwnProperty('error')) { return response; diff --git a/packages/common/src/fetch.ts b/packages/common/src/fetch.ts index f0e73a8fb..fb0dfa54b 100644 --- a/packages/common/src/fetch.ts +++ b/packages/common/src/fetch.ts @@ -1,7 +1,4 @@ -// Define a default request options and allow modification using getters, setters - -import { HIRO_MAINNET_URL } from './constants'; - +// Define default request options and allow modification using getters, setters // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request const defaultFetchOpts: RequestInit = { // By default referrer value will be client:origin: above reference link @@ -55,16 +52,16 @@ export type FetchFn = (url: string, init?: RequestInit) => Promise; * @ignore Internally used for letting networking functions specify "API" options. * Should be compatible with the `client`s created by the API and RPC packages. */ -export type ClientOpts = { +export interface ClientOpts { baseUrl?: string; fetch?: FetchFn; -}; +} /** @ignore Internally used for letting networking functions specify "API" options */ -export type ClientParam = { - /** Optional API object (for `.url` and `.fetch`) used for API/Node, defaults to use mainnet */ +export interface ClientParam { + /** Optional API object (for `.baseUrl` and `.fetch`) used for API/Node, defaults to use mainnet */ client?: ClientOpts; -}; +} export interface RequestContext { fetch: FetchFn; @@ -195,11 +192,10 @@ export function createFetchFn(...args: any[]): FetchFn { return fetchFn; } -/** @ignore Creates a client-like object, which can be used without circular dependencies */ -export function defaultClientOpts(opts?: { baseUrl?: string; fetch?: FetchFn }) { - return { - // todo: do we want network here as well? - baseUrl: opts?.baseUrl ?? HIRO_MAINNET_URL, - fetch: opts?.fetch ?? createFetchFn(), - }; -} +// /** @ignore Creates a client-like object, which can be used without circular dependencies */ +// export function defaultClientOpts(opts?: { baseUrl?: string; fetch?: FetchFn }) { +// return { +// baseUrl: opts?.baseUrl ?? HIRO_MAINNET_URL, +// fetch: opts?.fetch ?? createFetchFn(), +// }; +// } diff --git a/packages/network/src/network.ts b/packages/network/src/network.ts index 2133290d8..db2d819f9 100644 --- a/packages/network/src/network.ts +++ b/packages/network/src/network.ts @@ -1,13 +1,15 @@ import { ClientOpts, DEVNET_URL, + FetchFn, HIRO_MAINNET_URL, HIRO_TESTNET_URL, createFetchFn, } from '@stacks/common'; import { AddressVersion, ChainId, PeerNetworkId, TransactionVersion } from './constants'; +import { ClientParam } from '@stacks/common'; -export interface StacksNetwork { +export type StacksNetwork = { chainId: number; transactionVersion: number; // todo: txVersion better? peerNetworkId: number; @@ -18,8 +20,18 @@ export interface StacksNetwork { multiSig: number; }; // todo: add check32 character bytes string + client: { + baseUrl: string; // URL is always required + fetch?: FetchFn; // fetch is optional and will be created by default in fetch helpers + }; +}; + +export interface NetworkParam { + network?: StacksNetworkName | StacksNetwork; } +export type NetworkClientParam = NetworkParam & ClientParam; + export const STACKS_MAINNET: StacksNetwork = { chainId: ChainId.Mainnet, transactionVersion: TransactionVersion.Mainnet, @@ -30,6 +42,7 @@ export const STACKS_MAINNET: StacksNetwork = { singleSig: AddressVersion.MainnetSingleSig, multiSig: AddressVersion.MainnetMultiSig, }, + client: { baseUrl: HIRO_MAINNET_URL }, }; export const STACKS_TESTNET: StacksNetwork = { @@ -42,19 +55,37 @@ export const STACKS_TESTNET: StacksNetwork = { singleSig: AddressVersion.TestnetSingleSig, multiSig: AddressVersion.TestnetMultiSig, }, + client: { baseUrl: HIRO_TESTNET_URL }, }; export const STACKS_DEVNET: StacksNetwork = { - ...STACKS_TESTNET, + ...STACKS_TESTNET, // todo: ensure deep copy + addressVersion: { ...STACKS_TESTNET.addressVersion }, // deep copy magicBytes: 'id', // todo: comment bytes version of magic bytes + client: { baseUrl: DEVNET_URL }, +}; + +export const STACKS_MOCKNET: StacksNetwork = { + ...STACKS_DEVNET, + addressVersion: { ...STACKS_DEVNET.addressVersion }, // deep copy + client: { ...STACKS_DEVNET.client }, // deep copy }; -export const STACKS_MOCKNET: StacksNetwork = { ...STACKS_DEVNET }; /** @ignore internal */ export const StacksNetworks = ['mainnet', 'testnet', 'devnet', 'mocknet'] as const; /** The enum-style names of different common Stacks networks */ export type StacksNetworkName = (typeof StacksNetworks)[number]; +/** + * Returns the default network for a given name + * @example + * ```ts + * networkFromName('mainnet') // same as STACKS_MAINNET + * networkFromName('testnet') // same as STACKS_TESTNET + * networkFromName('devnet') // same as STACKS_DEVNET + * networkFromName('mocknet') // same as STACKS_MOCKNET + * ``` + */ export function networkFromName(name: StacksNetworkName) { switch (name) { case 'mainnet': @@ -77,7 +108,7 @@ export function networkFrom(network: StacksNetworkName | StacksNetwork) { } /** @ignore */ -export function defaultUrlFromNetwork(network?: StacksNetwork | StacksNetworkName) { +export function defaultUrlFromNetwork(network?: StacksNetworkName | StacksNetwork) { if (!network) return HIRO_MAINNET_URL; // default to mainnet if no network is given network = networkFrom(network); @@ -89,17 +120,13 @@ export function defaultUrlFromNetwork(network?: StacksNetwork | StacksNetworkNam : HIRO_TESTNET_URL; } -/** @ignore */ -export const defaultClientOptsFromNetwork = ( - network?: StacksNetworkName | StacksNetwork, - override?: ClientOpts -): Required => { - return Object.assign( - {}, - { - baseUrl: defaultUrlFromNetwork(network), - fetch: createFetchFn(), - }, - override - ); -}; +/** + * Returns the client of a network, creating a new fetch function if none is available + */ +export function clientFromNetwork(network: StacksNetwork): Required { + if (network.client.fetch) return network.client as Required; + return { + ...network.client, + fetch: createFetchFn(), + }; +} diff --git a/packages/profile/src/profile.ts b/packages/profile/src/profile.ts index 04b40198a..9cd4e1e3d 100644 --- a/packages/profile/src/profile.ts +++ b/packages/profile/src/profile.ts @@ -22,7 +22,8 @@ import { makeZoneFile, parseZoneFile } from 'zone-file'; // @ts-ignore import * as inspector from 'schema-inspector'; -import { ClientParam, Logger, defaultClientOpts } from '@stacks/common'; +import { Logger } from '@stacks/common'; +import { NetworkClientParam, clientFromNetwork, networkFrom } from '@stacks/network'; import { PublicPersonProfile } from './types'; const schemaDefinition: { [key: string]: any } = { @@ -340,9 +341,10 @@ export function resolveZoneFileToProfile( opts: { zoneFile: any; publicKeyOrAddress: string; - } & ClientParam + } & NetworkClientParam ): Promise> { - const api = defaultClientOpts(opts.client); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); return new Promise((resolve, reject) => { let zoneFileJson = null; @@ -367,7 +369,7 @@ export function resolveZoneFileToProfile( } if (tokenFileUrl) { - api + client .fetch(tokenFileUrl) .then(response => response.text()) .then(responseText => JSON.parse(responseText)) diff --git a/packages/stacking/src/index.ts b/packages/stacking/src/index.ts index 5fb88e642..dfba015d1 100644 --- a/packages/stacking/src/index.ts +++ b/packages/stacking/src/index.ts @@ -1,9 +1,9 @@ import { ClientOpts, IntegerType, PrivateKey, hexToBytes, intToBigInt } from '@stacks/common'; import { ChainId, + NetworkClientParam, StacksNetwork, - StacksNetworkName, - defaultClientOptsFromNetwork, + clientFromNetwork, networkFrom, } from '@stacks/network'; import { @@ -334,14 +334,10 @@ export class StackingClient { public client: Required; // todo: make more constructor opts optional - constructor(opts: { - address: string; - network: StacksNetworkName | StacksNetwork; - client?: ClientOpts; - }) { + constructor(opts: { address: string } & NetworkClientParam) { this.address = opts.address; - this.network = networkFrom(opts.network); - this.client = defaultClientOptsFromNetwork(this.network, opts.client); + this.network = networkFrom(opts.network ?? 'mainnet'); + this.client = Object.assign({}, clientFromNetwork(this.network), opts.client); } get baseUrl() { diff --git a/packages/stacking/src/utils.ts b/packages/stacking/src/utils.ts index 5a78ed82a..95ec521ae 100644 --- a/packages/stacking/src/utils.ts +++ b/packages/stacking/src/utils.ts @@ -306,7 +306,7 @@ function _poxAddressToBtcAddress_ClarityValue( export function poxAddressToBtcAddress( version: number, hashBytes: Uint8Array, - network: StacksNetworkName + network: StacksNetworkName // todo: allow NetworkParam in the future (minor) ): string; /** * Converts a PoX address to a Bitcoin address. diff --git a/packages/stacking/tests/stacking-2.4.test.ts b/packages/stacking/tests/stacking-2.4.test.ts index 29ecaa7dd..749934668 100644 --- a/packages/stacking/tests/stacking-2.4.test.ts +++ b/packages/stacking/tests/stacking-2.4.test.ts @@ -1,4 +1,4 @@ -import { defaultClientOpts, hexToBytes } from '@stacks/common'; +import { hexToBytes } from '@stacks/common'; import { MOCK_EMPTY_ACCOUNT, MOCK_FULL_ACCOUNT, @@ -55,7 +55,7 @@ describe('2.4 activation', () => { const client = new StackingClient({ address: '', network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const poxInfo = await client.getPoxInfo(); @@ -82,7 +82,7 @@ test('in period 3, pox-3 stacking works', async () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -130,7 +130,7 @@ describe('stacking eligibility', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const cycles = 1; @@ -152,7 +152,7 @@ describe('stacking eligibility', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const cycles = 1; @@ -176,7 +176,7 @@ describe('normal stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -216,7 +216,7 @@ describe('normal stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -274,7 +274,7 @@ describe('normal stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -331,7 +331,7 @@ describe('delegated stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -376,7 +376,7 @@ describe('delegated stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ @@ -420,7 +420,7 @@ describe('delegated stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const delegatorClient = new StackingClient({ address: delegatorAddress, @@ -489,7 +489,7 @@ describe('delegated stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const delegatorClient = new StackingClient({ address: delegatorAddress, @@ -580,7 +580,7 @@ describe('delegated stacking', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); const delegatorClient = new StackingClient({ address: delegatorAddress, @@ -667,7 +667,7 @@ describe('delegated stacking', () => { // * The pool commits a total stacking amount (covering all of its stackers) // * This is required for a pools pox-address to be "commited" into the reward-set - const client = defaultClientOpts({ baseUrl: API_URL }); + const client = { baseUrl: API_URL }; const stackerAKey = 'cb3df38053d132895220b9ce471f6b676db5b9bf0b4adefb55f2118ece2478df01'; const stackerAAddress = 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6'; @@ -832,7 +832,7 @@ describe('delegated stacking', () => { // * The pool realizes the mistake and increases the amount to all of its stackers' funds // * This will only work if the reward cycle anchor block hasn't been reached yet! - const clientOpts = defaultClientOpts({ baseUrl: API_URL }); + const clientOpts = { baseUrl: API_URL }; const stackerAKey = 'cb3df38053d132895220b9ce471f6b676db5b9bf0b4adefb55f2118ece2478df01'; const stackerAAddress = 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6'; @@ -1021,7 +1021,7 @@ describe('btc addresses', () => { const client = new StackingClient({ address, network: STACKS_TESTNET, - client: defaultClientOpts({ baseUrl: API_URL }), + client: { baseUrl: API_URL }, }); setApiMocks({ diff --git a/packages/stacking/tests/stacking-2.5.test.ts b/packages/stacking/tests/stacking-2.5.test.ts index e9f9afc5a..dba3ae8c9 100644 --- a/packages/stacking/tests/stacking-2.5.test.ts +++ b/packages/stacking/tests/stacking-2.5.test.ts @@ -1,5 +1,5 @@ import { getPublicKeyFromPrivate, publicKeyToBtcAddress } from '@stacks/encryption'; -import { STACKS_MOCKNET } from '@stacks/network/src'; +import { STACKS_MOCKNET } from '@stacks/network'; import { makeRandomPrivKey } from '@stacks/transactions'; import { V2_POX_REGTEST_POX_4, setApiMocks } from '../../internal/src'; import { StackingClient } from '../src'; diff --git a/packages/stacking/tests/stacking.test.ts b/packages/stacking/tests/stacking.test.ts index 785f50c69..ed564cbc0 100644 --- a/packages/stacking/tests/stacking.test.ts +++ b/packages/stacking/tests/stacking.test.ts @@ -1,11 +1,5 @@ import { sha256 } from '@noble/hashes/sha256'; -import { - HIRO_TESTNET_URL, - bigIntToBytes, - bytesToHex, - defaultClientOpts, - hexToBytes, -} from '@stacks/common'; +import { HIRO_TESTNET_URL, bigIntToBytes, bytesToHex, hexToBytes } from '@stacks/common'; import { base58CheckDecode, getPublicKeyFromPrivate } from '@stacks/encryption'; import { V2_POX_REGTEST_POX_3, setApiMocks } from '@stacks/internal'; import { STACKS_MAINNET, STACKS_TESTNET, defaultUrlFromNetwork } from '@stacks/network'; @@ -1235,7 +1229,7 @@ test('getSecondsUntilStackingDeadline', async () => { const client = new StackingClient({ address: '', network: STACKS_MAINNET, - client: defaultClientOpts({ baseUrl: 'http://localhost:3999' }), + client: { baseUrl: 'http://localhost:3999' }, }); setApiMocks({ diff --git a/packages/transactions/src/builders.ts b/packages/transactions/src/builders.ts index cfd081fd0..3cc91dd6e 100644 --- a/packages/transactions/src/builders.ts +++ b/packages/transactions/src/builders.ts @@ -1,13 +1,9 @@ -import { ClientOpts, ClientParam, IntegerType, PrivateKey, PublicKey } from '@stacks/common'; +import { IntegerType, PrivateKey, PublicKey } from '@stacks/common'; import { + NetworkClientParam, STACKS_MAINNET, - STACKS_TESTNET, - StacksNetwork, - StacksNetworkName, - TransactionVersion, - defaultClientOptsFromNetwork, + clientFromNetwork, networkFrom, - whenTransactionVersion, } from '@stacks/network'; import { c32address } from 'c32check'; import { @@ -40,7 +36,7 @@ import { import { postConditionToWire } from './postcondition'; import { PostCondition } from './postcondition-types'; import { TransactionSigner } from './signer'; -import { StacksTransaction } from './transaction'; +import { StacksTransaction, deriveNetworkFromTx } from './transaction'; import { omit } from './utils'; import { PostConditionWire, @@ -93,13 +89,11 @@ export type TokenTransferOptions = { fee?: IntegerType; /** the transaction nonce, which must be increased monotonically with each new transaction */ nonce?: IntegerType; - /** the network that the transaction will ultimately be broadcast to */ - network?: StacksNetworkName | StacksNetwork; /** an arbitrary string to include in the transaction, must be less than 34 bytes */ memo?: string; /** set to true if another account is sponsoring the transaction (covering the transaction fee) */ sponsored?: boolean; -} & ClientParam; +} & NetworkClientParam; export interface UnsignedTokenTransferOptions extends TokenTransferOptions { publicKey: PublicKey; @@ -134,12 +128,11 @@ export async function makeUnsignedSTXTokenTransfer( }; const options = Object.assign(defaultOptions, txOptions); - options.client = defaultClientOptsFromNetwork(options.network, txOptions.client); + options.network = networkFrom(options.network); + options.client = Object.assign({}, clientFromNetwork(options.network), txOptions.client); const payload = createTokenTransferPayload(options.recipient, options.amount, options.memo); - const network = networkFrom(options.network); - let spendingCondition: SpendingCondition | null = null; if ('publicKey' in options) { @@ -179,24 +172,24 @@ export async function makeUnsignedSTXTokenTransfer( : createStandardAuth(spendingCondition); const transaction = new StacksTransaction( - network.transactionVersion, + options.network.transactionVersion, authorization, payload, undefined, // no post conditions on STX transfers (see SIP-005) undefined, // no post conditions on STX transfers (see SIP-005) AnchorMode.Any, - network.chainId + options.network.chainId ); if (txOptions.fee == null) { - const fee = await fetchFeeEstimate({ transaction, client: options.client }); + const fee = await fetchFeeEstimate({ transaction, ...options }); transaction.setFee(fee); } if (txOptions.nonce == null) { - const addressVersion = network.addressVersion.singleSig; + const addressVersion = options.network.addressVersion.singleSig; const address = c32address(addressVersion, transaction.auth.spendingCondition!.signer); - const txNonce = await fetchNonce({ address, client: options.client }); + const txNonce = await fetchNonce({ address, ...options }); transaction.setNonce(txNonce); } @@ -245,7 +238,7 @@ export async function makeSTXTokenTransfer( /** * Contract deploy transaction options */ -export interface BaseContractDeployOptions { +export type BaseContractDeployOptions = { clarityVersion?: ClarityVersion; contractName: string; /** the Clarity code to be deployed */ @@ -254,10 +247,6 @@ export interface BaseContractDeployOptions { fee?: IntegerType; /** the transaction nonce, which must be increased monotonically with each new transaction */ nonce?: IntegerType; - /** the network that the transaction will ultimately be broadcast to */ - network?: StacksNetworkName | StacksNetwork; - /** the node/API used for estimating fee & nonce (using the `api.fetchFn` */ - client?: ClientOpts; /** the post condition mode, specifying whether or not post-conditions must fully cover all * transfered assets */ postConditionMode?: PostConditionMode; @@ -265,7 +254,7 @@ export interface BaseContractDeployOptions { postConditions?: PostConditionWire[]; /** set to true if another account is sponsoring the transaction (covering the transaction fee) */ sponsored?: boolean; -} +} & NetworkClientParam; export interface UnsignedContractDeployOptions extends BaseContractDeployOptions { /** a hex string of the public key of the transaction sender */ @@ -336,7 +325,8 @@ export async function makeUnsignedContractDeploy( }; const options = Object.assign(defaultOptions, txOptions); - options.client = defaultClientOptsFromNetwork(options.network, txOptions.client); + options.network = networkFrom(options.network); + options.client = Object.assign({}, clientFromNetwork(options.network), txOptions.client); const payload = createSmartContractPayload( options.contractName, @@ -344,8 +334,6 @@ export async function makeUnsignedContractDeploy( options.clarityVersion ); - const network = networkFrom(options.network); - let spendingCondition: SpendingCondition | null = null; if ('publicKey' in options) { @@ -393,24 +381,24 @@ export async function makeUnsignedContractDeploy( const lpPostConditions = createLPList(postConditions); const transaction = new StacksTransaction( - network.transactionVersion, + options.network.transactionVersion, authorization, payload, lpPostConditions, options.postConditionMode, AnchorMode.Any, - network.chainId + options.network.chainId ); if (txOptions.fee === undefined || txOptions.fee === null) { - const fee = await fetchFeeEstimate({ transaction, client: options.client }); + const fee = await fetchFeeEstimate({ transaction, ...options }); transaction.setFee(fee); } if (txOptions.nonce === undefined || txOptions.nonce === null) { - const addressVersion = network.addressVersion.singleSig; + const addressVersion = options.network.addressVersion.singleSig; const address = c32address(addressVersion, transaction.auth.spendingCondition!.signer); - const txNonce = await fetchNonce({ address, client: options.client }); + const txNonce = await fetchNonce({ address, ...options }); transaction.setNonce(txNonce); } @@ -420,7 +408,7 @@ export async function makeUnsignedContractDeploy( /** * Contract function call transaction options */ -export interface ContractCallOptions { +export type ContractCallOptions = { /** the Stacks address of the contract */ contractAddress: string; contractName: string; @@ -430,10 +418,6 @@ export interface ContractCallOptions { fee?: IntegerType; /** the transaction nonce, which must be increased monotonically with each new transaction */ nonce?: IntegerType; - /** the Stacks blockchain network that will ultimately be used to broadcast this transaction */ - network?: StacksNetworkName | StacksNetwork; - /** the node/API used for estimating fee & nonce (using the `api.fetchFn` */ - client?: ClientOpts; /** the post condition mode, specifying whether or not post-conditions must fully cover all * transfered assets */ postConditionMode?: PostConditionMode; @@ -444,7 +428,7 @@ export interface ContractCallOptions { validateWithAbi?: boolean | ClarityAbi; /** set to true if another account is sponsoring the transaction (covering the transaction fee) */ sponsored?: boolean; -} +} & NetworkClientParam; export interface UnsignedContractCallOptions extends ContractCallOptions { publicKey: PrivateKey; @@ -477,7 +461,8 @@ export async function makeUnsignedContractCall( }; const options = Object.assign(defaultOptions, txOptions); - options.client = defaultClientOptsFromNetwork(options.network, txOptions.client); + options.network = networkFrom(options.network); + options.client = Object.assign({}, clientFromNetwork(options.network), options.client); const payload = createContractCallPayload( options.contractAddress, @@ -501,8 +486,6 @@ export async function makeUnsignedContractCall( validateContractCall(payload, abi); } - const network = networkFrom(options.network); - let spendingCondition: SpendingCondition | null = null; if ('publicKey' in options) { @@ -547,24 +530,24 @@ export async function makeUnsignedContractCall( const lpPostConditions = createLPList(postConditions); const transaction = new StacksTransaction( - network.transactionVersion, + options.network.transactionVersion, authorization, payload, lpPostConditions, options.postConditionMode, AnchorMode.Any, - network.chainId + options.network.chainId ); if (txOptions.fee === undefined || txOptions.fee === null) { - const fee = await fetchFeeEstimate({ transaction, client: options.client }); + const fee = await fetchFeeEstimate({ transaction, ...options }); transaction.setFee(fee); } if (txOptions.nonce === undefined || txOptions.nonce === null) { - const addressVersion = network.addressVersion.singleSig; + const addressVersion = options.network.addressVersion.singleSig; const address = c32address(addressVersion, transaction.auth.spendingCondition!.signer); - const txNonce = await fetchNonce({ address, client: options.client }); + const txNonce = await fetchNonce({ address, ...options }); transaction.setNonce(txNonce); } @@ -613,7 +596,7 @@ export async function makeContractCall( /** * Sponsored transaction options */ -export interface SponsorOptionsOpts { +export type SponsorOptionsOpts = { /** the origin-signed transaction */ transaction: StacksTransaction; /** the sponsor's private key */ @@ -624,11 +607,7 @@ export interface SponsorOptionsOpts { sponsorNonce?: IntegerType; /** the hashmode of the sponsor's address */ sponsorAddressHashmode?: AddressHashMode; - /** the Stacks blockchain network that this transaction will ultimately be broadcast to */ - network?: StacksNetworkName | StacksNetwork; - /** the node/API used for estimating fee & nonce (using the `api.fetchFn` */ - client?: ClientOpts; -} +} & NetworkClientParam; /** * Constructs and signs a sponsored transaction as the sponsor @@ -642,22 +621,17 @@ export interface SponsorOptionsOpts { export async function sponsorTransaction( sponsorOptions: SponsorOptionsOpts ): Promise { - const defaultNetwork = whenTransactionVersion(sponsorOptions.transaction.version)({ - [TransactionVersion.Mainnet]: STACKS_MAINNET, - [TransactionVersion.Testnet]: STACKS_TESTNET, - }); // detect network from transaction version - const defaultOptions = { fee: 0 as IntegerType, sponsorNonce: 0 as IntegerType, sponsorAddressHashmode: AddressHashMode.P2PKH as SingleSigHashMode, - network: defaultNetwork, + network: deriveNetworkFromTx(sponsorOptions.transaction), }; const options = Object.assign(defaultOptions, sponsorOptions); - options.client = defaultClientOptsFromNetwork(options.network, sponsorOptions.client); + options.network = networkFrom(options.network); + options.client = Object.assign({}, clientFromNetwork(options.network), options.client); - const network = networkFrom(options.network); const sponsorPubKey = privateKeyToPublic(options.sponsorPrivateKey); if (sponsorOptions.fee == null) { @@ -681,9 +655,9 @@ export async function sponsorTransaction( } if (sponsorOptions.sponsorNonce == null) { - const addressVersion = network.addressVersion.singleSig; + const addressVersion = options.network.addressVersion.singleSig; const address = publicKeyToAddress(addressVersion, sponsorPubKey); - const sponsorNonce = await fetchNonce({ address, client: options.client }); + const sponsorNonce = await fetchNonce({ address, ...options }); options.sponsorNonce = sponsorNonce; } diff --git a/packages/transactions/src/fetch.ts b/packages/transactions/src/fetch.ts index 636ad7280..8d3421fc7 100644 --- a/packages/transactions/src/fetch.ts +++ b/packages/transactions/src/fetch.ts @@ -1,12 +1,5 @@ -import { - ClientParam, - bytesToHex, - createFetchFn, - defaultClientOpts, - validateHash256, - with0x, -} from '@stacks/common'; -import { defaultClientOptsFromNetwork, defaultUrlFromNetwork } from '@stacks/network'; +import { bytesToHex, validateHash256, with0x } from '@stacks/common'; +import { NetworkClientParam, clientFromNetwork, networkFrom } from '@stacks/network'; import { ClarityValue, NoneCV, deserializeCV, serializeCV } from './clarity'; import { ClarityAbi } from './contract-abi'; import { NoEstimateAvailableError } from './errors'; @@ -43,13 +36,14 @@ export const MAP_ENTRY_PATH = '/v2/map_entry'; export async function broadcastTransaction({ transaction: txOpt, attachment: attachOpt, - client: apiOpt, + network: _network, + client: _client, }: { /** The transaction to broadcast */ transaction: StacksTransaction; /** Optional attachment in bytes or encoded as a hex string */ attachment?: Uint8Array | string; -} & ClientParam): Promise { +} & NetworkClientParam): Promise { const tx = txOpt.serialize(); const attachment = attachOpt ? typeof attachOpt === 'string' @@ -63,9 +57,10 @@ export async function broadcastTransaction({ body: JSON.stringify(json), }; - const api = defaultClientOptsFromNetwork(deriveNetworkFromTx(txOpt), apiOpt); - const url = `${api.baseUrl}${BROADCAST_PATH}`; - const response = await api.fetch(url, options); + const network = _network ?? deriveNetworkFromTx(txOpt); + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + const url = `${client.baseUrl}${BROADCAST_PATH}`; + const response = await client.fetch(url, options); if (!response.ok) { try { @@ -85,11 +80,12 @@ export async function broadcastTransaction({ /** @internal */ async function _getNonceApi({ address, - client: apiOpt, -}: { address: string } & ClientParam): Promise { - const api = defaultClientOpts(apiOpt); - const url = `${api.baseUrl}/extended/v1/address/${address}/nonces`; - const response = await api.fetch(url); + network = 'mainnet', + client: _client, +}: { address: string } & NetworkClientParam): Promise { + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + const url = `${client.baseUrl}/extended/v1/address/${address}/nonces`; + const response = await client.fetch(url); const result = await response.json(); return BigInt(result.possible_next_nonce); } @@ -100,21 +96,21 @@ async function _getNonceApi({ * @param opts.api - Optional API info (`.url` & `.fetch`) used for fetch call * @return A promise that resolves to an integer */ -export async function fetchNonce({ - address, - client: apiOpt, -}: { - /** The Stacks address to look up the next nonce for */ - address: string; -} & ClientParam): Promise { +export async function fetchNonce( + opts: { + /** The Stacks address to look up the next nonce for */ + address: string; + } & NetworkClientParam +): Promise { // Try API first try { - return await _getNonceApi({ address, client: apiOpt }); + return await _getNonceApi(opts); } catch (e) {} - const api = defaultClientOpts(apiOpt); - const url = `${api.baseUrl}${ACCOUNT_PATH}/${address}?proof=0`; - const response = await api.fetch(url); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); + const url = `${client.baseUrl}${ACCOUNT_PATH}/${opts.address}?proof=0`; + const response = await client.fetch(url); if (!response.ok) { const msg = await response.text().catch(() => ''); @@ -139,21 +135,17 @@ export async function fetchNonce({ */ export async function fetchFeeEstimateTransfer({ transaction: txOpt, - client: apiOpt, + network: _network, + client: _client, }: { /** The token transfer transaction to estimate fees for */ transaction: StacksTransaction; -} & ClientParam): Promise { - const api = Object.assign( - {}, - { - url: defaultUrlFromNetwork(deriveNetworkFromTx(txOpt)), - fetch: createFetchFn(), - }, - apiOpt - ); - const url = `${api.baseUrl}${TRANSFER_FEE_ESTIMATE_PATH}`; - const response = await api.fetch(url, { +} & NetworkClientParam): Promise { + const network = _network ?? deriveNetworkFromTx(txOpt); + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + + const url = `${client.baseUrl}${TRANSFER_FEE_ESTIMATE_PATH}`; + const response = await client.fetch(url, { headers: { Accept: 'application/text' }, }); @@ -181,11 +173,12 @@ export async function fetchFeeEstimateTransfer({ export async function fetchFeeEstimateTransaction({ payload, estimatedLength, - client: apiOpt, + network = 'mainnet', + client: _client, }: { payload: string; estimatedLength?: number; -} & ClientParam): Promise<[FeeEstimation, FeeEstimation, FeeEstimation]> { +} & NetworkClientParam): Promise<[FeeEstimation, FeeEstimation, FeeEstimation]> { const json = { transaction_payload: payload, estimated_len: estimatedLength, @@ -196,9 +189,9 @@ export async function fetchFeeEstimateTransaction({ body: JSON.stringify(json), }; - const api = defaultClientOpts(apiOpt); - const url = `${api.baseUrl}${TRANSACTION_FEE_ESTIMATE_PATH}`; - const response = await api.fetch(url, options); + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + const url = `${client.baseUrl}${TRANSACTION_FEE_ESTIMATE_PATH}`; + const response = await client.fetch(url, options); if (!response.ok) { const body = await response.json().catch(() => ({})); @@ -225,18 +218,13 @@ export async function fetchFeeEstimateTransaction({ */ export async function fetchFeeEstimate({ transaction: txOpt, - client: apiOpt, + network: _network, + client: _client, }: { transaction: StacksTransaction; -} & ClientParam): Promise { - const api = Object.assign( - {}, - { - url: defaultUrlFromNetwork(deriveNetworkFromTx(txOpt)), - fetch: createFetchFn(), - }, - apiOpt - ); +} & NetworkClientParam): Promise { + const network = _network ?? deriveNetworkFromTx(txOpt); + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); try { const estimatedLength = estimateTransactionByteLength(txOpt); @@ -244,12 +232,13 @@ export async function fetchFeeEstimate({ await fetchFeeEstimateTransaction({ payload: bytesToHex(serializePayloadBytes(txOpt.payload)), estimatedLength, - client: api, + network, + client, }) )[1].fee; } catch (error) { if (!(error instanceof NoEstimateAvailableError)) throw error; - return await fetchFeeEstimateTransfer({ transaction: txOpt, client: api }); + return await fetchFeeEstimateTransfer({ transaction: txOpt, network }); } } @@ -263,14 +252,15 @@ export async function fetchFeeEstimate({ export async function fetchAbi({ contractAddress: address, contractName: name, - client: apiOpt, + network = 'mainnet', + client: _client, }: { contractAddress: string; contractName: string; -} & ClientParam): Promise { - const api = defaultClientOpts(apiOpt); - const url = `${api.baseUrl}${CONTRACT_ABI_PATH}/${address}/${name}`; - const response = await api.fetch(url); +} & NetworkClientParam): Promise { + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + const url = `${client.baseUrl}${CONTRACT_ABI_PATH}/${address}/${name}`; + const response = await client.fetch(url); if (!response.ok) { const msg = await response.text().catch(() => ''); @@ -300,7 +290,8 @@ export async function fetchCallReadOnlyFunction({ functionName, functionArgs, senderAddress, - client: apiOpt, + network = 'mainnet', + client: _client, }: { contractName: string; contractAddress: string; @@ -308,7 +299,7 @@ export async function fetchCallReadOnlyFunction({ functionArgs: ClarityValue[]; /** address of the sender */ senderAddress: string; -} & ClientParam): Promise { +} & NetworkClientParam): Promise { const json = { sender: senderAddress, arguments: functionArgs.map(arg => cvToHex(arg)), @@ -323,9 +314,9 @@ export async function fetchCallReadOnlyFunction({ const name = encodeURIComponent(functionName); - const api = defaultClientOpts(apiOpt); - const url = `${api.baseUrl}${READONLY_FUNCTION_CALL_PATH}/${contractAddress}/${contractName}/${name}`; - const response = await api.fetch(url, options); + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); + const url = `${client.baseUrl}${READONLY_FUNCTION_CALL_PATH}/${contractAddress}/${contractName}/${name}`; + const response = await client.fetch(url, options); if (!response.ok) { const msg = await response.text().catch(() => ''); @@ -352,13 +343,14 @@ export async function fetchContractMapEntry { +} & NetworkClientParam): Promise { const keyHex = with0x(serializeCV(mapKey)); const options = { @@ -370,9 +362,9 @@ export async function fetchContractMapEntry ''); @@ -387,7 +379,7 @@ export async function fetchContractMapEntry => { +} & NetworkParam): Promise<{ username: string | undefined; stxDerivationType: DerivationType }> => { if (network) network = networkFrom(network); if (username) { @@ -178,10 +177,10 @@ const selectUsernameForAccount = async ( opts: { rootNode: HDKey; index: number; - network?: StacksNetwork; - } & ClientParam + } & NetworkClientParam ): Promise<{ username: string | undefined; derivationType: DerivationType }> => { - const client = defaultClientOptsFromNetwork(opts.network, opts.client); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); // try to find existing usernames owned by stx derivation path if (opts.network) { @@ -210,17 +209,16 @@ export const fetchUsernameForAccountByDerivationType = async ( rootNode: HDKey; index: number; derivationType: DerivationType.Wallet | DerivationType.Data; - network?: StacksNetworkName | StacksNetwork; - } & ClientParam + } & NetworkClientParam ): Promise<{ username: string | undefined; }> => { - const client = defaultClientOptsFromNetwork(opts.network, opts.client); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); // try to find existing usernames owned by given derivation path - const selectedNetwork = opts.network ? networkFrom(opts.network) : STACKS_MAINNET; const privateKey = derivePrivateKeyByType(opts); - const address = getAddressFromPrivateKey(privateKey, selectedNetwork); + const address = getAddressFromPrivateKey(privateKey, network); const username = await fetchFirstName({ address, client }); return { username }; }; diff --git a/packages/wallet-sdk/src/models/wallet.ts b/packages/wallet-sdk/src/models/wallet.ts index dc5ee5e01..6c83bcf37 100644 --- a/packages/wallet-sdk/src/models/wallet.ts +++ b/packages/wallet-sdk/src/models/wallet.ts @@ -1,5 +1,4 @@ -import { ClientParam, defaultClientOpts } from '@stacks/common'; -import { StacksNetwork } from '@stacks/network'; +import { NetworkClientParam, clientFromNetwork, networkFrom } from '@stacks/network'; import { DerivationType, deriveStxPrivateKey, fetchUsernameForAccountByDerivationType } from '..'; import { deriveAccount, deriveLegacyConfigPrivateKey } from '../derive'; import { connectToGaiaHubWithConfig, getHubInfo } from '../utils'; @@ -21,16 +20,15 @@ export interface LockedWallet { export async function restoreWalletAccounts({ wallet, gaiaHubUrl, - network, - client: clientOpts, + network = 'mainnet', + client: _client, }: { wallet: Wallet; gaiaHubUrl: string; - network: StacksNetwork; -} & ClientParam): Promise { - const api = defaultClientOpts(clientOpts); +} & NetworkClientParam): Promise { + const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); - const hubInfo = await getHubInfo(gaiaHubUrl, api.fetch); + const hubInfo = await getHubInfo(gaiaHubUrl, client.fetch); const rootNode = getRootNode(wallet); const legacyGaiaConfig = connectToGaiaHubWithConfig({ hubInfo, @@ -44,8 +42,8 @@ export async function restoreWalletAccounts({ }); const [walletConfig, legacyWalletConfig] = await Promise.all([ - fetchWalletConfig({ wallet, gaiaHubConfig: currentGaiaConfig, fetchFn: api.fetch }), - fetchLegacyWalletConfig({ wallet, gaiaHubConfig: legacyGaiaConfig, fetchFn: api.fetch }), + fetchWalletConfig({ wallet, gaiaHubConfig: currentGaiaConfig, fetchFn: client.fetch }), + fetchLegacyWalletConfig({ wallet, gaiaHubConfig: legacyGaiaConfig, fetchFn: client.fetch }), ]); // Restore from existing config if ( diff --git a/packages/wallet-sdk/src/usernames.ts b/packages/wallet-sdk/src/usernames.ts index 432576a29..e2daaa753 100644 --- a/packages/wallet-sdk/src/usernames.ts +++ b/packages/wallet-sdk/src/usernames.ts @@ -1,11 +1,12 @@ -import { ClientParam, defaultClientOpts } from '@stacks/common'; +import { NetworkClientParam, clientFromNetwork, networkFrom } from '@stacks/network'; export const fetchFirstName = async ( opts: { address: string; - } & ClientParam + } & NetworkClientParam ): Promise => { - const client = defaultClientOpts(opts.client); + const network = networkFrom(opts.network ?? 'mainnet'); + const client = Object.assign({}, clientFromNetwork(network), opts.client); try { const namesResponse = await client.fetch( `${client.baseUrl}/v1/addresses/stacks/${opts.address}`