diff --git a/packages/apps/chainweaver-pact-console-plugin/src/network.ts b/packages/apps/chainweaver-pact-console-plugin/src/network.ts index 3d5ab8c018..aa84ef9aec 100644 --- a/packages/apps/chainweaver-pact-console-plugin/src/network.ts +++ b/packages/apps/chainweaver-pact-console-plugin/src/network.ts @@ -61,7 +61,8 @@ export const hostUrlGenerator = (networks: INetwork[]) => { checkNetworks().catch((er) => console.log('Error while checking networks', er), ); - return ({ networkId, chainId }: INetworkOptions) => { + return ({ networkId, chainId, type }: INetworkOptions) => { + // TODO: for the devnet we should consider rate limiting as well and pause the process for some time if (Date.now() - lastCheckTime > 30000) { checkNetworks().catch((er) => console.log('Error while checking networks', er), @@ -75,6 +76,6 @@ export const hostUrlGenerator = (networks: INetwork[]) => { if (!host) { throw new Error('No healthy host found'); } - return getHostUrl(host.url)({ networkId, chainId }); + return getHostUrl(host.url)({ networkId, chainId, type }); }; }; diff --git a/packages/apps/dev-wallet/src/modules/network/network.service.ts b/packages/apps/dev-wallet/src/modules/network/network.service.ts index c890996eba..fc41e878a0 100644 --- a/packages/apps/dev-wallet/src/modules/network/network.service.ts +++ b/packages/apps/dev-wallet/src/modules/network/network.service.ts @@ -45,7 +45,7 @@ export const hostUrlGenerator = (networks: INetwork[]) => { checkNetworks().catch((er) => console.log('Error while checking networks', er), ); - return ({ networkId, chainId }: INetworkOptions) => { + return ({ networkId, chainId, type }: INetworkOptions) => { if (Date.now() - lastCheckTime > 10000) { checkNetworks().catch((er) => console.log('Error while checking networks', er), @@ -59,6 +59,6 @@ export const hostUrlGenerator = (networks: INetwork[]) => { if (!host) { throw new Error('No healthy host found'); } - return getHostUrl(host.url)({ networkId, chainId }); + return getHostUrl(host.url)({ networkId, chainId, type }); }; }; diff --git a/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx index a4bc0f283b..9dda7489b8 100644 --- a/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx +++ b/packages/apps/dev-wallet/src/modules/wallet/wallet.provider.tsx @@ -251,7 +251,7 @@ export const WalletProvider: FC = ({ children }) => { const hostUrl = networkHostFunctionRef.current; if (hostUrl && ctx.activeNetwork) { try { - hostUrl({ networkId: ctx.activeNetwork.networkId, chainId: '0' }); + hostUrl({ networkId: ctx.activeNetwork.networkId, chainId: '0', type: 'local' }); if (ctx.activeNetwork.isHealthy === true) return ctx; return { ...ctx, diff --git a/packages/libs/client-examples/src/example-contract/poll-status.ts b/packages/libs/client-examples/src/example-contract/poll-status.ts index 065c097139..f563580d51 100644 --- a/packages/libs/client-examples/src/example-contract/poll-status.ts +++ b/packages/libs/client-examples/src/example-contract/poll-status.ts @@ -16,20 +16,5 @@ export async function pollRequestsAndWaitForEachPromiseExample(): Promise }); // await for each individual request - await Promise.all( - Object.entries(results.requests).map(([requestKey, promise]) => - promise - .then((data) => { - console.log('the request ', requestKey, 'result:', data); - }) - .catch((error) => { - console.log( - 'error while getting the status of ', - requestKey, - 'error:', - error, - ); - }), - ), - ); + console.log('waiting for results', await results); } diff --git a/packages/libs/client/etc/client.api.md b/packages/libs/client/etc/client.api.md index c661993ca7..63937a56f8 100644 --- a/packages/libs/client/etc/client.api.md +++ b/packages/libs/client/etc/client.api.md @@ -131,7 +131,7 @@ export interface IClient extends IBaseClient { getPoll: (transactionDescriptors: ITransactionDescriptor[] | ITransactionDescriptor, options?: ClientRequestInit) => Promise; pollOne: (transactionDescriptor: ITransactionDescriptor, options?: IPollOptions) => Promise; preflight: (transaction: ICommand | IUnsignedCommand, options?: ClientRequestInit) => Promise; - runPact: (code: string, data: Record, option: ClientRequestInit & INetworkOptions) => Promise; + runPact: (code: string, data: Record, option: ClientRequestInit & Omit) => Promise; // @deprecated send: ISubmit; signatureVerification: (transaction: ICommand, options?: ClientRequestInit) => Promise; @@ -171,16 +171,18 @@ export interface ICreateClient { (hostUrl: string, defaults?: { confirmationDepth?: number; }): IClient; - (hostAddressGenerator?: (options: { - chainId: ChainId; - networkId: string; - type?: 'local' | 'send' | 'poll' | 'listen' | 'spv'; - }) => string | { + (hostAddressGenerator?: (options: INetworkOptions) => string | { hostUrl: string; requestInit: ClientRequestInit; }, defaults?: { confirmationDepth?: number; }): IClient; + (hostAddressGenerator?: (options: INetworkOptions) => Promise, defaults?: { + confirmationDepth?: number; + }): IClient; } // @public @@ -230,6 +232,8 @@ export interface INetworkOptions { chainId: ChainId; // (undocumented) networkId: string; + // (undocumented) + type: 'local' | 'send' | 'poll' | 'listen' | 'spv'; } // @public @@ -307,9 +311,7 @@ export interface IPollOptions extends ClientRequestInit { } // @public (undocumented) -export type IPollRequestPromise = Promise> & { - requests: Record>; -}; +export type IPollRequestPromise = Promise>; export { IPollResponse } diff --git a/packages/libs/client/src/client/client.ts b/packages/libs/client/src/client/client.ts index 4f180fe8b7..a6d7972941 100644 --- a/packages/libs/client/src/client/client.ts +++ b/packages/libs/client/src/client/client.ts @@ -219,7 +219,7 @@ export interface IClient extends IBaseClient { runPact: ( code: string, data: Record, - option: ClientRequestInit & INetworkOptions, + option: ClientRequestInit & Omit, ) => Promise; /** @@ -286,11 +286,23 @@ export interface ICreateClient { * @param defaults - default options for the client it includes confirmationDepth that is used for polling */ ( - hostAddressGenerator?: (options: { - chainId: ChainId; - networkId: string; - type?: 'local' | 'send' | 'poll' | 'listen' | 'spv'; - }) => string | { hostUrl: string; requestInit: ClientRequestInit }, + hostAddressGenerator?: (options: INetworkOptions) => string | { hostUrl: string; requestInit: ClientRequestInit }, + defaults?: { confirmationDepth?: number }, + ): IClient; + + /** + * Generates a client instance by passing async hostUrlGenerator function. + * this function will be called for each request to get the latest host URL. + * You also can use this for more advanced use cases like load balancing or + * failover between multiple hosts or control rate limiting. + * + * Note: The default hostUrlGenerator creates a Kadena testnet or mainnet URL based on networkId. + * @param hostAddressGenerator - the function that generates the URL based on `chainId` and `networkId` from the transaction + * @param defaults - default options for the client it includes confirmationDepth that is used for polling + */ + + ( + hostAddressGenerator?: (options: INetworkOptions) => Promise, defaults?: { confirmationDepth?: number }, ): IClient; } @@ -317,11 +329,12 @@ export const createClient: ICreateClient = ( const getHost = typeof host === 'string' ? () => host : host; const client: IBaseClient = { - local(body, options) { + async local(body, options) { const cmd: IPactCommand = JSON.parse(body.cmd); - const hostObject = getHost({ + const hostObject = await getHost({ chainId: cmd.meta.chainId, networkId: cmd.networkId, + type: 'local', }); const { hostUrl, requestInit } = getHostData(hostObject); return local(body, hostUrl, mergeOptions(requestInit, options)); @@ -334,9 +347,10 @@ export const createClient: ICreateClient = ( throw new Error('EMPTY_COMMAND_LIST'); } const cmd: IPactCommand = JSON.parse(first.cmd); - const hostObject = getHost({ + const hostObject = await getHost({ chainId: cmd.meta.chainId, networkId: cmd.networkId, + type: 'send', }); const { hostUrl, requestInit } = getHostData(hostObject); @@ -364,18 +378,17 @@ export const createClient: ICreateClient = ( : [transactionDescriptors]; const results = groupByHost( requestsList.map(({ requestKey, chainId, networkId }) => { - const hostObject = getHost({ chainId, networkId, type: 'poll' }); - const { hostUrl, requestInit } = getHostData(hostObject); return { requestKey, - host: hostUrl, - requestInit, + host: JSON.stringify({ chainId, networkId}), }; }), - ).map(([host, requestKeys]) => { - const requestInit = requestKeys[0].requestInit; + ).map( async ([host, requestKeys]) => { + const { chainId, networkId} = JSON.parse(host) + const hostObject = await getHost({ chainId, networkId, type: 'poll' }); + const { hostUrl, requestInit } = getHostData(hostObject) return pollStatus( - host, + hostUrl, requestKeys.map((r) => r.requestKey), { confirmationDepth, @@ -396,17 +409,16 @@ export const createClient: ICreateClient = ( const results = await Promise.all( groupByHost( - requestsList.map(({ requestKey, chainId, networkId }) => { - const hostObject = getHost({ chainId, networkId, type: 'poll' }); - const { hostUrl, requestInit } = getHostData(hostObject); - return { - requestKey, - host: hostUrl, - requestInit, - }; - }), - ).map(([hostUrl, requestKeys]) => { - const requestInit = requestKeys[0].requestInit; + requestsList.map(({ requestKey, chainId, networkId }) => { + return { + requestKey, + host: JSON.stringify({ chainId, networkId}), + }; + }), + ).map(async ([host, requestKeys]) => { + const { chainId, networkId} = JSON.parse(host) + const hostObject = await getHost({ chainId, networkId, type: 'poll' }); + const { hostUrl, requestInit } = getHostData(hostObject) return poll( { requestKeys: requestKeys.map((r) => r.requestKey) }, @@ -424,7 +436,7 @@ export const createClient: ICreateClient = ( }, async listen({ requestKey, chainId, networkId }, options) { - const hostObject = getHost({ chainId, networkId, type: 'listen' }); + const hostObject = await getHost({ chainId, networkId, type: 'listen' }); const { hostUrl, requestInit } = getHostData(hostObject); const result = await listen( { listen: requestKey }, @@ -435,8 +447,8 @@ export const createClient: ICreateClient = ( return result; }, - pollCreateSpv({ requestKey, chainId, networkId }, targetChainId, options) { - const hostObject = getHost({ chainId, networkId, type: 'spv' }); + async pollCreateSpv({ requestKey, chainId, networkId }, targetChainId, options) { + const hostObject = await getHost({ chainId, networkId, type: 'spv' }); const { hostUrl, requestInit } = getHostData(hostObject); return pollSpv( hostUrl, @@ -451,7 +463,7 @@ export const createClient: ICreateClient = ( targetChainId, options, ) { - const hostObject = getHost({ chainId, networkId, type: 'spv' }); + const hostObject = await getHost({ chainId, networkId, type: 'spv' }); const { hostUrl, requestInit } = getHostData(hostObject); return getSpv( hostUrl, @@ -486,8 +498,8 @@ export const createClient: ICreateClient = ( signatureVerification: false, }); }, - runPact: (code, data, options) => { - const hostObject = getHost(options); + runPact: async (code, data, options) => { + const hostObject = await getHost({...options, type: 'local'}); const { hostUrl, requestInit } = getHostData(hostObject); if (hostUrl === '') throw new Error('NO_HOST_URL'); diff --git a/packages/libs/client/src/client/interfaces/interfaces.ts b/packages/libs/client/src/client/interfaces/interfaces.ts index 9d7d070aba..44edd713b9 100644 --- a/packages/libs/client/src/client/interfaces/interfaces.ts +++ b/packages/libs/client/src/client/interfaces/interfaces.ts @@ -10,6 +10,7 @@ import type { ChainId } from '@kadena/types'; export interface INetworkOptions { networkId: string; chainId: ChainId; + type: 'local' | 'send' | 'poll' | 'listen' | 'spv'; } /** @@ -32,9 +33,4 @@ export interface IPollOptions extends ClientRequestInit { /** * @public */ -export type IPollRequestPromise = Promise> & { - /** - * @deprecated pass callback to {@link IPollOptions.onResult} instead - */ - requests: Record>; -}; +export type IPollRequestPromise = Promise> diff --git a/packages/libs/client/src/client/utils/tests/utils.test.ts b/packages/libs/client/src/client/utils/tests/utils.test.ts index 77cc5ae112..2b2de235a4 100644 --- a/packages/libs/client/src/client/utils/tests/utils.test.ts +++ b/packages/libs/client/src/client/utils/tests/utils.test.ts @@ -56,18 +56,18 @@ describe('client utils', () => { describe('kadenaHostGenerator', () => { it('returns mainnet url with the correct chainId', () => { expect( - kadenaHostGenerator({ networkId: 'mainnet01', chainId: '14' }), + kadenaHostGenerator({ networkId: 'mainnet01', chainId: '14', type: 'local' }), ).toBe('https://api.chainweb.com/chainweb/0.0/mainnet01/chain/14/pact'); }); it('returns testnet url with the correct chainId', () => { expect( - kadenaHostGenerator({ networkId: 'testnet04', chainId: '14' }), + kadenaHostGenerator({ networkId: 'testnet04', chainId: '14',type: 'local' }), ).toBe( 'https://api.testnet.chainweb.com/chainweb/0.0/testnet04/chain/14/pact', ); expect( - kadenaHostGenerator({ networkId: 'testnet05', chainId: '14' }), + kadenaHostGenerator({ networkId: 'testnet05', chainId: '14', type: 'local' }), ).toBe( 'https://api.testnet05.chainweb.com/chainweb/0.0/testnet05/chain/14/pact', ); @@ -75,7 +75,7 @@ describe('client utils', () => { it('throes exception if networkId is not either mainnet01 nor testnet04 ', () => { expect(() => - kadenaHostGenerator({ networkId: 'incorrect-network', chainId: '14' }), + kadenaHostGenerator({ networkId: 'incorrect-network', chainId: '14', type: 'local' }), ).toThrowError(Error(`UNKNOWN_NETWORK_ID: incorrect-network`)); }); }); @@ -84,14 +84,14 @@ describe('client utils', () => { it('returns a function that generates host url based on the networkId and chainId', () => { const hostUrl = 'http://localhost:8080'; const getLocalHostUrl = getHostUrl(hostUrl); - expect(getLocalHostUrl({ networkId: 'mainnet01', chainId: '14' })).toBe( + expect(getLocalHostUrl({ networkId: 'mainnet01', chainId: '14', type:'local' })).toBe( 'http://localhost:8080/chainweb/0.0/mainnet01/chain/14/pact', ); }); it("removes the last '/' from the host url", () => { const hostUrl = 'http://localhost:8080/'; const getLocalHostUrl = getHostUrl(hostUrl); - expect(getLocalHostUrl({ networkId: 'mainnet01', chainId: '14' })).toBe( + expect(getLocalHostUrl({ networkId: 'mainnet01', chainId: '14', type:'local' })).toBe( 'http://localhost:8080/chainweb/0.0/mainnet01/chain/14/pact', ); }); @@ -199,23 +199,12 @@ describe('client utils', () => { requests: { key2: Promise.resolve('r2') }, }, ); - const mergedPr = mergeAllPollRequestPromises([pr1, pr2]); + const mergedPr = await mergeAllPollRequestPromises([pr1, pr2]); - expect(Object.keys(mergedPr.requests)).toEqual(['key1', 'key2']); - - const res = await Promise.all([ - mergedPr, - mergedPr.requests.key1, - mergedPr.requests.key2, - ]); - - expect(res[0]).toEqual({ + expect(mergedPr).toEqual({ key1: 'r1', key2: 'r2', }); - - expect(res[1]).toEqual('r1'); - expect(res[2]).toEqual('r2'); }); }); diff --git a/packages/libs/client/src/client/utils/utils.ts b/packages/libs/client/src/client/utils/utils.ts index a5628e9680..4b8a2b5423 100644 --- a/packages/libs/client/src/client/utils/utils.ts +++ b/packages/libs/client/src/client/utils/utils.ts @@ -55,19 +55,22 @@ export const getHostUrl = (hostBaseUrl: string) => { export const kadenaHostGenerator = ({ networkId, chainId, + type }: INetworkOptions): string => { switch (networkId) { case 'mainnet01': - return getHostUrl('https://api.chainweb.com')({ networkId, chainId }); + return getHostUrl('https://api.chainweb.com')({ networkId, chainId, type }); case 'testnet04': return getHostUrl('https://api.testnet.chainweb.com')({ networkId, chainId, + type }); case 'testnet05': return getHostUrl('https://api.testnet05.chainweb.com')({ networkId, chainId, + type }); default: throw new Error(`UNKNOWN_NETWORK_ID: ${networkId}`); @@ -119,12 +122,7 @@ export const mergeAll = (results: Array): T => export const mergeAllPollRequestPromises = ( results: Array>, ): IPollRequestPromise => { - return Object.assign(Promise.all(results).then(mergeAll), { - requests: results.reduce( - (acc, data) => ({ ...acc, ...data.requests }), - {} as Record>, - ), - }); + return Object.assign(Promise.all(results).then(mergeAll)); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -157,16 +155,15 @@ export const groupByHost = ( items: Array<{ requestKey: string; host: string; - requestInit?: ClientRequestInit; }>, ): [string, { requestInit?: ClientRequestInit; requestKey: string }[]][] => { const byHost = new Map< string, { requestInit?: ClientRequestInit; requestKey: string }[] >(); - items.forEach(({ host: hostUrl, requestKey, requestInit }) => { + items.forEach(({ host: hostUrl, requestKey }) => { const prev = byHost.get(hostUrl) ?? []; - byHost.set(hostUrl, [...prev, { requestInit, requestKey }]); + byHost.set(hostUrl, [...prev, { requestKey }]); }); return [...byHost.entries()]; };