From ce48afc41396d39a89724bbdf2a005822d538725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Mon, 13 May 2024 19:25:12 +0300 Subject: [PATCH 01/10] feat: rpc batch requests --- src/batch/default.ts | 75 +++++++++++++++++++++++++++++ src/batch/delayedAction.ts | 46 ++++++++++++++++++ src/batch/index.ts | 2 + src/batch/interface.ts | 32 ++++++++++++ src/channel/rpc_0_6.ts | 23 ++++++++- src/channel/rpc_0_7.ts | 22 +++++++++ src/types/provider/configuration.ts | 1 + 7 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/batch/default.ts create mode 100644 src/batch/delayedAction.ts create mode 100644 src/batch/index.ts create mode 100644 src/batch/interface.ts diff --git a/src/batch/default.ts b/src/batch/default.ts new file mode 100644 index 000000000..71b63b80c --- /dev/null +++ b/src/batch/default.ts @@ -0,0 +1,75 @@ +import { BatchClientOptions } from './interface'; +import { DelayedAction } from './delayedAction'; +import { stringify } from '../utils/json'; +import { RPC } from '../types'; +import { JRPC } from '../types/api'; + +export class BatchClient extends DelayedAction { + public nodeUrl: string; + + public headers: object; + + public interval: number; + + public requestId: number = 0; + + private pendingRequests: Record = {}; + + private batchPromise?: Promise; + + constructor(options: BatchClientOptions) { + super(options.interval); + + this.nodeUrl = options.nodeUrl; + this.headers = options.headers; + this.interval = options.interval; + } + + private addPendingRequest( + method: T, + params?: RPC.Methods[T]['params'], + id?: string | number + ) { + const request: JRPC.RequestBody = { + id: id ?? `batched_${(this.requestId += 1)}`, + jsonrpc: '2.0', + method, + params: params ?? undefined, + }; + + this.pendingRequests[request.id] = request; + + return request.id; + } + + private async sendBatch(requests: JRPC.RequestBody[]) { + const raw = await fetch(this.nodeUrl, { + method: 'POST', + body: stringify(requests), + headers: this.headers as Record, + }); + + return raw.json(); + } + + public async fetch( + method: T, + params?: RPC.Methods[T]['params'], + id?: string | number + ) { + const requestId = this.addPendingRequest(method, params, id); + + await this.wait(); + + const requests = this.pendingRequests; + this.pendingRequests = {}; + + if (!this.batchPromise) { + this.batchPromise = this.sendBatch(Object.values(requests)); + } + const results = await this.batchPromise; + this.batchPromise = undefined; + + return results.find((result: any) => result.id === requestId); + } +} diff --git a/src/batch/delayedAction.ts b/src/batch/delayedAction.ts new file mode 100644 index 000000000..fe9a86fa9 --- /dev/null +++ b/src/batch/delayedAction.ts @@ -0,0 +1,46 @@ +export class DelayedAction { + private delay: number; + + private timer: NodeJS.Timeout | null; + + private promise?: Promise; + + private promiseResolve?: () => void; + + constructor(delay: number = 5000) { + this.delay = delay; + this.timer = null; + } + + /** + * Waits for the delay to pass, then resolves the promise. + * All calls to this method will return the same promise until the delay has passed + * + * @returns {Promise} + */ + public async wait(): Promise { + // If the promise is not set, create a new one and store the resolve function + if (!this.promise || !this.promiseResolve) { + this.promise = new Promise((resolve) => { + this.promiseResolve = resolve; + }); + } + + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + this.timer = setTimeout(() => { + if (this.promiseResolve) { + this.promiseResolve(); + + // Reset the promise and resolve function so that a new promise is created next time + this.promise = undefined; + this.promiseResolve = undefined; + } + }, this.delay); + + return this.promise; + } +} diff --git a/src/batch/index.ts b/src/batch/index.ts new file mode 100644 index 000000000..7ef642374 --- /dev/null +++ b/src/batch/index.ts @@ -0,0 +1,2 @@ +export * from './interface'; +export * from './default'; diff --git a/src/batch/interface.ts b/src/batch/interface.ts new file mode 100644 index 000000000..de52cc405 --- /dev/null +++ b/src/batch/interface.ts @@ -0,0 +1,32 @@ +import { RPC } from '../types'; +import { JRPC } from '../types/api'; + +export type BatchClientOptions = { + nodeUrl: string; + headers: object; + interval: number; +}; + +export abstract class BatchClientInterface { + /** + * Fetch batched JSON-RPC requests + * + * @param body - JSON-RPC request body + * @returns JSON-RPC response + */ + public abstract fetch( + method: T, + params?: RPC.Methods[T]['params'], + id?: string | number + ): Promise< + JRPC.ResponseBody & + ( + | { + result?: RPC.Methods[T]['result']; + } + | { + error?: RPC.Methods[T] extends { error: infer E } ? E : never; + } + ) + >; +} diff --git a/src/channel/rpc_0_6.ts b/src/channel/rpc_0_6.ts index a4c66ca00..6c7aecc2e 100644 --- a/src/channel/rpc_0_6.ts +++ b/src/channel/rpc_0_6.ts @@ -1,3 +1,4 @@ +import { BatchClient } from '../batch'; import { NetworkName, StarknetChainId } from '../constants'; import { LibraryError } from '../provider/errors'; import { @@ -52,8 +53,10 @@ export class RpcChannel { readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed + private batchClient?: BatchClient; + constructor(optionsOrProvider?: RpcProviderOptions) { - const { nodeUrl, retries, headers, blockIdentifier, chainId, specVersion, waitMode } = + const { nodeUrl, retries, headers, blockIdentifier, chainId, specVersion, waitMode, batch } = optionsOrProvider || {}; if (Object.values(NetworkName).includes(nodeUrl as NetworkName)) { this.nodeUrl = getDefaultNodeUrl(nodeUrl as NetworkName, optionsOrProvider?.default); @@ -69,6 +72,14 @@ export class RpcChannel { this.specVersion = specVersion; this.waitMode = waitMode || false; this.requestId = 0; + + if (typeof batch === 'number') { + this.batchClient = new BatchClient({ + nodeUrl: this.nodeUrl, + headers: this.headers, + interval: batch, + }); + } } public setChainId(chainId: StarknetChainId) { @@ -110,6 +121,16 @@ export class RpcChannel { params?: RPC.Methods[T]['params'] ): Promise { try { + if (this.batchClient) { + const { error, result } = await this.batchClient.fetch( + method, + params, + (this.requestId += 1) + ); + this.errorHandler(method, params, error); + return result as RPC.Methods[T]['result']; + } + const rawResult = await this.fetch(method, params, (this.requestId += 1)); const { error, result } = await rawResult.json(); this.errorHandler(method, params, error); diff --git a/src/channel/rpc_0_7.ts b/src/channel/rpc_0_7.ts index 923e2bcfc..3072fdd8c 100644 --- a/src/channel/rpc_0_7.ts +++ b/src/channel/rpc_0_7.ts @@ -1,3 +1,4 @@ +import { BatchClient } from '../batch'; import { NetworkName, StarknetChainId } from '../constants'; import { LibraryError } from '../provider/errors'; import { @@ -54,6 +55,8 @@ export class RpcChannel { readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed + private batchClient?: BatchClient; + constructor(optionsOrProvider?: RpcProviderOptions) { const { nodeUrl, @@ -64,6 +67,7 @@ export class RpcChannel { specVersion, waitMode, transactionRetryIntervalFallback, + batch, } = optionsOrProvider || {}; if (Object.values(NetworkName).includes(nodeUrl as NetworkName)) { this.nodeUrl = getDefaultNodeUrl(nodeUrl as NetworkName, optionsOrProvider?.default); @@ -80,6 +84,14 @@ export class RpcChannel { this.waitMode = waitMode || false; this.requestId = 0; this.transactionRetryIntervalFallback = transactionRetryIntervalFallback; + + if (typeof batch === 'number') { + this.batchClient = new BatchClient({ + nodeUrl: this.nodeUrl, + headers: this.headers, + interval: batch, + }); + } } private get transactionRetryIntervalDefault() { @@ -125,6 +137,16 @@ export class RpcChannel { params?: RPC.Methods[T]['params'] ): Promise { try { + if (this.batchClient) { + const { error, result } = await this.batchClient.fetch( + method, + params, + (this.requestId += 1) + ); + this.errorHandler(method, params, error); + return result as RPC.Methods[T]['result']; + } + const rawResult = await this.fetch(method, params, (this.requestId += 1)); const { error, result } = await rawResult.json(); this.errorHandler(method, params, error); diff --git a/src/types/provider/configuration.ts b/src/types/provider/configuration.ts index db1825f42..7076ea2c8 100644 --- a/src/types/provider/configuration.ts +++ b/src/types/provider/configuration.ts @@ -18,4 +18,5 @@ export type RpcProviderOptions = { l1BoundMaxPricePerUnit: number; maxFee: number; }; + batch?: false | number; }; From 01f48ef2a85597afc53f3406ecd9a505b12da5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Tue, 14 May 2024 13:19:07 +0300 Subject: [PATCH 02/10] feat: rpc batch remove delayedAction class --- src/batch/default.ts | 55 +++++++++++++++++++++++++++++++------- src/batch/delayedAction.ts | 46 ------------------------------- 2 files changed, 46 insertions(+), 55 deletions(-) delete mode 100644 src/batch/delayedAction.ts diff --git a/src/batch/default.ts b/src/batch/default.ts index 71b63b80c..16225fce3 100644 --- a/src/batch/default.ts +++ b/src/batch/default.ts @@ -1,10 +1,9 @@ import { BatchClientOptions } from './interface'; -import { DelayedAction } from './delayedAction'; import { stringify } from '../utils/json'; import { RPC } from '../types'; import { JRPC } from '../types/api'; -export class BatchClient extends DelayedAction { +export class BatchClient { public nodeUrl: string; public headers: object; @@ -15,16 +14,46 @@ export class BatchClient extends DelayedAction { private pendingRequests: Record = {}; - private batchPromise?: Promise; + private batchPromises: Record> = {}; - constructor(options: BatchClientOptions) { - super(options.interval); + private delayTimer?: NodeJS.Timeout; + + private delayPromise?: Promise; + private delayPromiseResolve?: () => void; + + constructor(options: BatchClientOptions) { this.nodeUrl = options.nodeUrl; this.headers = options.headers; this.interval = options.interval; } + private async wait(): Promise { + // If the promise is not set, create a new one and store the resolve function + if (!this.delayPromise || !this.delayPromiseResolve) { + this.delayPromise = new Promise((resolve) => { + this.delayPromiseResolve = resolve; + }); + } + + if (this.delayTimer) { + clearTimeout(this.delayTimer); + this.delayTimer = undefined; + } + + this.delayTimer = setTimeout(() => { + if (this.delayPromiseResolve) { + this.delayPromiseResolve(); + + // Reset the promise and resolve function so that a new promise is created next time + this.delayPromise = undefined; + this.delayPromiseResolve = undefined; + } + }, this.interval); + + return this.delayPromise; + } + private addPendingRequest( method: T, params?: RPC.Methods[T]['params'], @@ -59,17 +88,25 @@ export class BatchClient extends DelayedAction { ) { const requestId = this.addPendingRequest(method, params, id); + // Wait for the interval to pass before sending the batch await this.wait(); + // Get the pending requests and clear the object const requests = this.pendingRequests; this.pendingRequests = {}; - if (!this.batchPromise) { - this.batchPromise = this.sendBatch(Object.values(requests)); + // If there is no promise for this batch, create one and send the batch + if (!this.batchPromises[requestId]) { + const promise = this.sendBatch(Object.values(requests)); + Object.keys(requests).forEach((key) => { + this.batchPromises[key] = promise; + }); } - const results = await this.batchPromise; - this.batchPromise = undefined; + const results = await this.batchPromises[requestId]; + delete this.batchPromises[requestId]; + + // Find this request in the results and return it return results.find((result: any) => result.id === requestId); } } diff --git a/src/batch/delayedAction.ts b/src/batch/delayedAction.ts deleted file mode 100644 index fe9a86fa9..000000000 --- a/src/batch/delayedAction.ts +++ /dev/null @@ -1,46 +0,0 @@ -export class DelayedAction { - private delay: number; - - private timer: NodeJS.Timeout | null; - - private promise?: Promise; - - private promiseResolve?: () => void; - - constructor(delay: number = 5000) { - this.delay = delay; - this.timer = null; - } - - /** - * Waits for the delay to pass, then resolves the promise. - * All calls to this method will return the same promise until the delay has passed - * - * @returns {Promise} - */ - public async wait(): Promise { - // If the promise is not set, create a new one and store the resolve function - if (!this.promise || !this.promiseResolve) { - this.promise = new Promise((resolve) => { - this.promiseResolve = resolve; - }); - } - - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - - this.timer = setTimeout(() => { - if (this.promiseResolve) { - this.promiseResolve(); - - // Reset the promise and resolve function so that a new promise is created next time - this.promise = undefined; - this.promiseResolve = undefined; - } - }, this.delay); - - return this.promise; - } -} From 04ad50d4a03a74595790cff235d4af26de5d4969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Tue, 14 May 2024 13:38:43 +0300 Subject: [PATCH 03/10] fix: rpc batch type errors --- src/batch/default.ts | 21 +++++++++++++-------- src/batch/interface.ts | 13 ++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/batch/default.ts b/src/batch/default.ts index 16225fce3..f0c44d8b6 100644 --- a/src/batch/default.ts +++ b/src/batch/default.ts @@ -1,9 +1,9 @@ -import { BatchClientOptions } from './interface'; +import { BatchClientInterface, BatchClientOptions } from './interface'; import { stringify } from '../utils/json'; import { RPC } from '../types'; import { JRPC } from '../types/api'; -export class BatchClient { +export class BatchClient implements BatchClientInterface { public nodeUrl: string; public headers: object; @@ -81,11 +81,13 @@ export class BatchClient { return raw.json(); } - public async fetch( - method: T, - params?: RPC.Methods[T]['params'], - id?: string | number - ) { + public async fetch< + T extends keyof RPC.Methods, + TResponse extends JRPC.ResponseBody & { + result?: RPC.Methods[T]['result']; + error?: JRPC.Error; + }, + >(method: T, params?: RPC.Methods[T]['params'], id?: string | number): Promise { const requestId = this.addPendingRequest(method, params, id); // Wait for the interval to pass before sending the batch @@ -107,6 +109,9 @@ export class BatchClient { delete this.batchPromises[requestId]; // Find this request in the results and return it - return results.find((result: any) => result.id === requestId); + const result = results.find((res: any) => res.id === requestId); + if (!result) throw new Error(`Couldn't find the result for the request. Method: ${method}`); + + return result as TResponse; } } diff --git a/src/batch/interface.ts b/src/batch/interface.ts index de52cc405..68277cbd7 100644 --- a/src/batch/interface.ts +++ b/src/batch/interface.ts @@ -19,14 +19,9 @@ export abstract class BatchClientInterface { params?: RPC.Methods[T]['params'], id?: string | number ): Promise< - JRPC.ResponseBody & - ( - | { - result?: RPC.Methods[T]['result']; - } - | { - error?: RPC.Methods[T] extends { error: infer E } ? E : never; - } - ) + JRPC.ResponseBody & { + result?: RPC.Methods[T]['result']; + error?: JRPC.Error; + } >; } From f49490206eaf396813b9662adefbce36ec219a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Sat, 6 Jul 2024 13:05:05 +0300 Subject: [PATCH 04/10] fix: move BatchClient to utils --- src/batch/index.ts | 2 -- src/batch/interface.ts | 27 ------------------- src/channel/rpc_0_6.ts | 2 +- src/channel/rpc_0_7.ts | 2 +- .../default.ts => utils/batch/index.ts} | 15 +++++++---- 5 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 src/batch/index.ts delete mode 100644 src/batch/interface.ts rename src/{batch/default.ts => utils/batch/index.ts} (92%) diff --git a/src/batch/index.ts b/src/batch/index.ts deleted file mode 100644 index 7ef642374..000000000 --- a/src/batch/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './interface'; -export * from './default'; diff --git a/src/batch/interface.ts b/src/batch/interface.ts deleted file mode 100644 index 68277cbd7..000000000 --- a/src/batch/interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { RPC } from '../types'; -import { JRPC } from '../types/api'; - -export type BatchClientOptions = { - nodeUrl: string; - headers: object; - interval: number; -}; - -export abstract class BatchClientInterface { - /** - * Fetch batched JSON-RPC requests - * - * @param body - JSON-RPC request body - * @returns JSON-RPC response - */ - public abstract fetch( - method: T, - params?: RPC.Methods[T]['params'], - id?: string | number - ): Promise< - JRPC.ResponseBody & { - result?: RPC.Methods[T]['result']; - error?: JRPC.Error; - } - >; -} diff --git a/src/channel/rpc_0_6.ts b/src/channel/rpc_0_6.ts index 6c7aecc2e..432caed9d 100644 --- a/src/channel/rpc_0_6.ts +++ b/src/channel/rpc_0_6.ts @@ -1,4 +1,3 @@ -import { BatchClient } from '../batch'; import { NetworkName, StarknetChainId } from '../constants'; import { LibraryError } from '../provider/errors'; import { @@ -19,6 +18,7 @@ import { waitForTransactionOptions, } from '../types'; import { JRPC, RPCSPEC06 as RPC } from '../types/api'; +import { BatchClient } from '../utils/batch'; import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; import { validateAndParseEthAddress } from '../utils/eth'; diff --git a/src/channel/rpc_0_7.ts b/src/channel/rpc_0_7.ts index 3072fdd8c..0ee413ff4 100644 --- a/src/channel/rpc_0_7.ts +++ b/src/channel/rpc_0_7.ts @@ -1,4 +1,3 @@ -import { BatchClient } from '../batch'; import { NetworkName, StarknetChainId } from '../constants'; import { LibraryError } from '../provider/errors'; import { @@ -19,6 +18,7 @@ import { waitForTransactionOptions, } from '../types'; import { JRPC, RPCSPEC07 as RPC } from '../types/api'; +import { BatchClient } from '../utils/batch'; import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; import { validateAndParseEthAddress } from '../utils/eth'; diff --git a/src/batch/default.ts b/src/utils/batch/index.ts similarity index 92% rename from src/batch/default.ts rename to src/utils/batch/index.ts index f0c44d8b6..3d4b1c9b8 100644 --- a/src/batch/default.ts +++ b/src/utils/batch/index.ts @@ -1,9 +1,14 @@ -import { BatchClientInterface, BatchClientOptions } from './interface'; -import { stringify } from '../utils/json'; -import { RPC } from '../types'; -import { JRPC } from '../types/api'; +import { stringify } from '../json'; +import { RPC } from '../../types'; +import { JRPC } from '../../types/api'; -export class BatchClient implements BatchClientInterface { +export type BatchClientOptions = { + nodeUrl: string; + headers: object; + interval: number; +}; + +export class BatchClient { public nodeUrl: string; public headers: object; From 257c68e1f6ba16f7cd5fad2d33a3c0d0a56b9660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Sat, 6 Jul 2024 13:32:48 +0300 Subject: [PATCH 05/10] test: add BatchClient tests --- __tests__/utils/batch.test.ts | 32 ++++++++++++++++++++++++++++++++ src/utils/batch/index.ts | 7 +++++++ 2 files changed, 39 insertions(+) create mode 100644 __tests__/utils/batch.test.ts diff --git a/__tests__/utils/batch.test.ts b/__tests__/utils/batch.test.ts new file mode 100644 index 000000000..cde8a8add --- /dev/null +++ b/__tests__/utils/batch.test.ts @@ -0,0 +1,32 @@ +import { BatchClient } from '../../src/utils/batch'; +import { createBlockForDevnet, getTestProvider } from '../config/fixtures'; +import { initializeMatcher } from '../config/schema'; + +describe('Batch Client', () => { + const provider = getTestProvider(false); + + const batchClient = new BatchClient({ + nodeUrl: provider.channel.nodeUrl, + headers: provider.channel.headers, + interval: 0, + }); + + initializeMatcher(expect); + + test('should batch two requests', async () => { + await createBlockForDevnet(); + + const fetchSpy = jest.spyOn(batchClient as any, 'sendBatch'); + + const [blockNumber, blockWithReceipts] = await Promise.all([ + batchClient.fetch('starknet_blockNumber'), + batchClient.fetch('starknet_getBlockWithReceipts', { block_id: 'latest' }), + ]); + + expect(typeof blockNumber.result).toBe('number'); + expect(blockWithReceipts.result).toMatchSchemaRef('BlockWithTxReceipts'); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + fetchSpy.mockRestore(); + }); +}); diff --git a/src/utils/batch/index.ts b/src/utils/batch/index.ts index 3d4b1c9b8..f8130c83a 100644 --- a/src/utils/batch/index.ts +++ b/src/utils/batch/index.ts @@ -86,6 +86,13 @@ export class BatchClient { return raw.json(); } + /** + * Automatically batches and fetches JSON-RPC calls in a single request. + * @param method Method to call + * @param params Method parameters + * @param id JSON-RPC Request ID + * @returns JSON-RPC Response + */ public async fetch< T extends keyof RPC.Methods, TResponse extends JRPC.ResponseBody & { From b1d10a13fd2308c927a072cd0c87115b294c26ae Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Fri, 19 Jul 2024 17:16:55 +0200 Subject: [PATCH 06/10] fix: jest debut fix api requests match using uid --- __tests__/config/jest.setup.ts | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/__tests__/config/jest.setup.ts b/__tests__/config/jest.setup.ts index 90bbc1b5b..21078680d 100644 --- a/__tests__/config/jest.setup.ts +++ b/__tests__/config/jest.setup.ts @@ -18,31 +18,46 @@ const combiner: object[] = []; if (process.env.DEBUG === 'true') { register({ request(url, config) { - const body = JSON.parse(config.body); - combiner.push({ - request: { - url, - method: config.method, - body, - }, - }); + const randId = crypto.randomUUID(); + if (config.body) { + const body = JSON.parse(config.body); + combiner.push({ + request: { + matchId: randId, + url, + method: config.method, + body, + }, + }); + + // match request and response when DEBUG, lib override headers instead of add + const headers = { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-match-id': randId, + }; + // eslint-disable-next-line no-param-reassign + config.headers = headers; + } return [url, config]; }, requestError(error) { - const match: any = combiner.find((it: any) => typeof it.result === 'undefined'); - match.result = error; - console.log('[fetch.requestError]', match); + // unknown original request + console.log('[fetch.requestError]', error); return Promise.reject(error); }, response(response) { + const requestId = response.request.headers.get('x-match-id'); const cloned = response.clone(); cloned.json().then((res) => { const { result } = res; - const match: any = combiner.find((it: any) => it.request.body.id === res.id); + const match: any = combiner.find((it: any) => it.request.matchId === requestId); if (match && 'request' in match) { - match.result = result; + if (result) match.result = result; + else match.response = res; + console.log(util.inspect(match, false, null, true /* enable colors */)); } else { console.log(result); @@ -52,9 +67,8 @@ if (process.env.DEBUG === 'true') { }, responseError(error) { - const match: any = combiner.find((it: any) => typeof it.result === 'undefined'); - match.result = error; - console.log('[fetch.responseError]', match); + // unknown original request + console.log('[fetch.responseError]', error); return Promise.reject(error); }, }); From f1ca02280eaa81460ba0b95c114132b7916f6af4 Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Fri, 19 Jul 2024 17:37:57 +0200 Subject: [PATCH 07/10] test: provider --- __tests__/config/fixtures.ts | 25 +++- __tests__/utils/batch.test.ts | 17 +++ package-lock.json | 232 +++++++++++++++++++++++++++++++++- 3 files changed, 265 insertions(+), 9 deletions(-) diff --git a/__tests__/config/fixtures.ts b/__tests__/config/fixtures.ts index 0443acd5d..0d49004a9 100644 --- a/__tests__/config/fixtures.ts +++ b/__tests__/config/fixtures.ts @@ -2,7 +2,12 @@ import fs from 'node:fs'; import path from 'node:path'; import { Account, Provider, ProviderInterface, RpcProvider, json } from '../../src'; -import { CompiledSierra, CompiledSierraCasm, LegacyCompiledContract } from '../../src/types'; +import { + CompiledSierra, + CompiledSierraCasm, + LegacyCompiledContract, + RpcProviderOptions, +} from '../../src/types'; import { ETransactionVersion } from '../../src/types/api'; import { toHex } from '../../src/utils/num'; import { wait } from '../../src/utils/provider'; @@ -72,12 +77,22 @@ export const compiledSidMulticallCasm = readContractSierraCasm('starknetId/multi export const compiledNonZero = readContractSierra('cairo/cairo263/zeroable.sierra'); export const compiledNonZeroCasm = readContractSierraCasm('cairo/cairo263/zeroable'); -export function getTestProvider(isProvider?: true): ProviderInterface; -export function getTestProvider(isProvider?: false): RpcProvider; -export function getTestProvider(isProvider: boolean = true): ProviderInterface | RpcProvider { +export function getTestProvider( + isProvider?: true, + setProviderOptions?: RpcProviderOptions +): ProviderInterface; +export function getTestProvider( + isProvider?: false, + setProviderOptions?: RpcProviderOptions +): RpcProvider; +export function getTestProvider( + isProvider: boolean = true, + setProviderOptions?: RpcProviderOptions +): ProviderInterface | RpcProvider { const isDevnet = process.env.IS_DEVNET === 'true'; - const providerOptions = { + const providerOptions: RpcProviderOptions = { + ...setProviderOptions, nodeUrl: process.env.TEST_RPC_URL, // accelerate the tests when running locally ...(isDevnet && { transactionRetryIntervalFallback: 1000 }), diff --git a/__tests__/utils/batch.test.ts b/__tests__/utils/batch.test.ts index cde8a8add..8c878290e 100644 --- a/__tests__/utils/batch.test.ts +++ b/__tests__/utils/batch.test.ts @@ -29,4 +29,21 @@ describe('Batch Client', () => { expect(fetchSpy).toHaveBeenCalledTimes(1); fetchSpy.mockRestore(); }); + + test('batch request using Provider', async () => { + const myBatchProvider = getTestProvider(false, { + batch: 0, + }); + + // eslint-disable-next-line @typescript-eslint/dot-notation + const sendBatchSpy = jest.spyOn(myBatchProvider.channel['batchClient'] as any, 'sendBatch'); + + await Promise.all([ + myBatchProvider.getBlock(), + myBatchProvider.getBlockLatestAccepted(), + myBatchProvider.getBlockTransactionCount('latest'), + ]); + + expect(sendBatchSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/package-lock.json b/package-lock.json index d55aabfe7..306d09550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@penovicp/starknet", - "version": "7.0.0", + "name": "starknet", + "version": "6.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@penovicp/starknet", - "version": "7.0.0", + "name": "starknet", + "version": "6.11.0", "license": "MIT", "dependencies": { "@noble/curves": "~1.4.0", @@ -13938,6 +13938,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "inBundle": true, "license": "ISC", @@ -13955,6 +13957,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -13967,6 +13971,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" @@ -13990,6 +13996,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14005,6 +14013,8 @@ }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14173,6 +14183,8 @@ }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", "dev": true, "inBundle": true, "license": "ISC", @@ -14182,6 +14194,8 @@ }, "node_modules/npm/node_modules/@npmcli/node-gyp": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", "dev": true, "inBundle": true, "license": "ISC", @@ -14221,6 +14235,8 @@ }, "node_modules/npm/node_modules/@npmcli/query": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", + "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -14259,6 +14275,8 @@ }, "node_modules/npm/node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14343,6 +14361,8 @@ }, "node_modules/npm/node_modules/@tufjs/canonical-json": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14365,6 +14385,8 @@ }, "node_modules/npm/node_modules/abbrev": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -14374,6 +14396,8 @@ }, "node_modules/npm/node_modules/agent-base": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14386,6 +14410,8 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14399,6 +14425,8 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14408,6 +14436,8 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "inBundle": true, "license": "MIT", @@ -14420,18 +14450,24 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/archy": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "inBundle": true, "license": "MIT" @@ -14465,6 +14501,8 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14497,6 +14535,8 @@ }, "node_modules/npm/node_modules/chalk": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "inBundle": true, "license": "MIT", @@ -14509,6 +14549,8 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -14518,6 +14560,8 @@ }, "node_modules/npm/node_modules/ci-info": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", "dev": true, "funding": [ { @@ -14545,6 +14589,8 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14554,6 +14600,8 @@ }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-4.0.0.tgz", + "integrity": "sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14576,6 +14624,8 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14588,18 +14638,24 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "inBundle": true, "license": "MIT", @@ -14629,6 +14685,8 @@ }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14641,6 +14699,8 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14664,6 +14724,8 @@ }, "node_modules/npm/node_modules/diff": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -14673,18 +14735,24 @@ }, "node_modules/npm/node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14695,6 +14763,8 @@ }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14704,18 +14774,24 @@ }, "node_modules/npm/node_modules/err-code": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true, "inBundle": true, "license": "Apache-2.0" }, "node_modules/npm/node_modules/fastest-levenshtein": { "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14725,6 +14801,8 @@ }, "node_modules/npm/node_modules/foreground-child": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", "dev": true, "inBundle": true, "license": "ISC", @@ -14741,6 +14819,8 @@ }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, "inBundle": true, "license": "ISC", @@ -14753,6 +14833,8 @@ }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14784,12 +14866,16 @@ }, "node_modules/npm/node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/hasown": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14814,12 +14900,16 @@ }, "node_modules/npm/node_modules/http-cache-semantics": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "inBundle": true, "license": "MIT", @@ -14833,6 +14923,8 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14846,6 +14938,8 @@ }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "inBundle": true, "license": "MIT", @@ -14871,6 +14965,8 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14880,6 +14976,8 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14916,6 +15014,8 @@ }, "node_modules/npm/node_modules/ip-address": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, "inBundle": true, "license": "MIT", @@ -14929,6 +15029,8 @@ }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", "dev": true, "inBundle": true, "license": "MIT", @@ -14953,6 +15055,8 @@ }, "node_modules/npm/node_modules/is-core-module": { "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "inBundle": true, "license": "MIT", @@ -14965,6 +15069,8 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14974,12 +15080,16 @@ }, "node_modules/npm/node_modules/is-lambda": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "inBundle": true, "license": "ISC" @@ -15004,6 +15114,8 @@ }, "node_modules/npm/node_modules/jsbn": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true, "inBundle": true, "license": "MIT" @@ -15019,6 +15131,8 @@ }, "node_modules/npm/node_modules/json-stringify-nice": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15028,6 +15142,8 @@ }, "node_modules/npm/node_modules/jsonparse": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true, "engines": [ "node >= 0.2.0" @@ -15037,12 +15153,16 @@ }, "node_modules/npm/node_modules/just-diff": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/just-diff-apply": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", "dev": true, "inBundle": true, "license": "MIT" @@ -15271,6 +15391,8 @@ }, "node_modules/npm/node_modules/minipass-collect": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15300,6 +15422,8 @@ }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15324,6 +15448,8 @@ }, "node_modules/npm/node_modules/minipass-json-stream": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15346,6 +15472,8 @@ }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "inBundle": true, "license": "ISC", @@ -15370,6 +15498,8 @@ }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "inBundle": true, "license": "ISC", @@ -15394,6 +15524,8 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15419,6 +15551,8 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "inBundle": true, "license": "MIT", @@ -15431,6 +15565,8 @@ }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "inBundle": true, "license": "MIT" @@ -15446,6 +15582,8 @@ }, "node_modules/npm/node_modules/negotiator": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15518,6 +15656,8 @@ }, "node_modules/npm/node_modules/npm-audit-report": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-5.0.0.tgz", + "integrity": "sha512-EkXrzat7zERmUhHaoren1YhTxFwsOu5jypE84k6632SXTHcQE1z8V51GC6GVZt8LxkC+tbBcKMUBZAgk8SUSbw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15539,6 +15679,8 @@ }, "node_modules/npm/node_modules/npm-install-checks": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -15551,6 +15693,8 @@ }, "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -15575,6 +15719,8 @@ }, "node_modules/npm/node_modules/npm-packlist": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "inBundle": true, "license": "ISC", @@ -15643,6 +15789,8 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -15689,6 +15837,8 @@ }, "node_modules/npm/node_modules/parse-conflict-json": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15703,6 +15853,8 @@ }, "node_modules/npm/node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -15759,6 +15911,8 @@ }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15768,6 +15922,8 @@ }, "node_modules/npm/node_modules/promise-call-limit": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.1.tgz", + "integrity": "sha512-utl+0x8gIDasV5X+PI5qWEPqH6fJS0pFtQ/4gZ95xfEFb/89dmh+/b895TbFDBLiafBvxD/PGTKfvxl4kH/pQg==", "dev": true, "inBundle": true, "license": "ISC", @@ -15777,12 +15933,16 @@ }, "node_modules/npm/node_modules/promise-inflight": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "inBundle": true, "license": "MIT", @@ -15808,6 +15968,8 @@ }, "node_modules/npm/node_modules/qrcode-terminal": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true, "inBundle": true, "bin": { @@ -15828,6 +15990,8 @@ }, "node_modules/npm/node_modules/read-cmd-shim": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -15837,6 +16001,8 @@ }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15850,6 +16016,8 @@ }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "inBundle": true, "license": "MIT", @@ -15859,6 +16027,8 @@ }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15878,6 +16048,8 @@ }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -15890,6 +16062,8 @@ }, "node_modules/npm/node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -15899,6 +16073,8 @@ }, "node_modules/npm/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15928,6 +16104,8 @@ }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15966,6 +16144,8 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -15986,6 +16166,8 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, "inBundle": true, "license": "CC-BY-3.0" @@ -16026,6 +16208,8 @@ }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16041,6 +16225,8 @@ "node_modules/npm/node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16055,6 +16241,8 @@ }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16068,6 +16256,8 @@ "node_modules/npm/node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16080,6 +16270,8 @@ }, "node_modules/npm/node_modules/supports-color": { "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, "inBundle": true, "license": "MIT", @@ -16142,18 +16334,24 @@ }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", + "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -16177,6 +16375,8 @@ }, "node_modules/npm/node_modules/unique-filename": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, "inBundle": true, "license": "ISC", @@ -16189,6 +16389,8 @@ }, "node_modules/npm/node_modules/unique-slug": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -16201,12 +16403,16 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/validate-npm-package-license": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -16236,12 +16442,16 @@ }, "node_modules/npm/node_modules/walk-up-path": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/which": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "inBundle": true, "license": "ISC", @@ -16266,6 +16476,8 @@ }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -16284,6 +16496,8 @@ "node_modules/npm/node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -16316,6 +16530,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16328,6 +16544,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" @@ -16351,6 +16569,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -16366,6 +16586,8 @@ }, "node_modules/npm/node_modules/write-file-atomic": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "inBundle": true, "license": "ISC", @@ -16379,6 +16601,8 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "inBundle": true, "license": "ISC" From 078c37df5687c2ee84ba9a8dfbc5d01153aeb066 Mon Sep 17 00:00:00 2001 From: PhilippeR26 Date: Wed, 24 Jul 2024 19:44:01 +0200 Subject: [PATCH 08/10] feat: ledger Signer --- __tests__/utils/encode.test.ts | 10 + __tests__/utils/ethSigner.test.ts | 14 + __tests__/utils/num.test.ts | 8 + package-lock.json | 69 ++++- package.json | 1 + src/constants.ts | 1 + src/signer/index.ts | 1 + src/signer/ledgerSigner.ts | 267 ++++++++++++++++++ src/utils/encode.ts | 24 ++ src/utils/num.ts | 25 +- .../guides/pictures/LedgerConnectivity.png | Bin 0 -> 91559 bytes www/docs/guides/pictures/LedgerTitle.png | Bin 0 -> 63053 bytes www/docs/guides/signature.md | 57 +++- 13 files changed, 469 insertions(+), 8 deletions(-) create mode 100644 src/signer/ledgerSigner.ts create mode 100644 www/docs/guides/pictures/LedgerConnectivity.png create mode 100644 www/docs/guides/pictures/LedgerTitle.png diff --git a/__tests__/utils/encode.test.ts b/__tests__/utils/encode.test.ts index c0d3df623..a3efb032a 100644 --- a/__tests__/utils/encode.test.ts +++ b/__tests__/utils/encode.test.ts @@ -1,3 +1,4 @@ +import { encode } from '../../src'; import { atobUniversal, btoaUniversal } from '../../src/utils/encode'; describe('atobUniversal and btoaUniversal functions', () => { @@ -32,3 +33,12 @@ describe('atobUniversal and btoaUniversal functions', () => { expect(decoded).toEqual(new Uint8Array([])); }); }); + +describe('concatenateArrayBuffer', () => { + test('should concatenate uint8Arrays', () => { + const path0buff = new Uint8Array([128, 0, 10, 85]); + const path1buff = new Uint8Array([71, 65, 233, 201]); + const result = encode.concatenateArrayBuffer([path0buff, path1buff]); + expect(result).toEqual(new Uint8Array([128, 0, 10, 85, 71, 65, 233, 201])); + }); +}); diff --git a/__tests__/utils/ethSigner.test.ts b/__tests__/utils/ethSigner.test.ts index 37d4a684d..53c0370ce 100644 --- a/__tests__/utils/ethSigner.test.ts +++ b/__tests__/utils/ethSigner.test.ts @@ -10,6 +10,7 @@ import { encode, eth, extractContractHashes, + getLedgerPathBuffer, hash, num, stark, @@ -353,3 +354,16 @@ describe('Ethereum signer', () => { }); }); }); + +describe('Ledger Signer', () => { + // signature of Ledger can't be tested automatically. + // So, just the test of the path encoding. + test('getLedgerPathBuffer', () => { + const path = getLedgerPathBuffer(3, 'AstroAPP'); + expect(path).toEqual( + new Uint8Array([ + 128, 0, 10, 85, 71, 65, 233, 201, 95, 192, 123, 107, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + ]) + ); + }); +}); diff --git a/__tests__/utils/num.test.ts b/__tests__/utils/num.test.ts index 04e7ab33b..78b08d194 100644 --- a/__tests__/utils/num.test.ts +++ b/__tests__/utils/num.test.ts @@ -18,6 +18,7 @@ import { isNumber, isBoolean, } from '../../src/utils/num'; +import { num } from '../../src'; describe('isHex', () => { test('should return true for valid hex strings', () => { @@ -208,3 +209,10 @@ describe('isBoolean', () => { expect(isBoolean({})).toBe(false); }); }); + +describe('stringToSha256ToArrayBuff4', () => { + test('should correctly hash&encode an utf8 string', () => { + const buff = num.stringToSha256ToArrayBuff4('LedgerW'); + expect(buff).toEqual(new Uint8Array([43, 206, 231, 219])); + }); +}); diff --git a/package-lock.json b/package-lock.json index d55aabfe7..8ce4252b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { - "name": "@penovicp/starknet", - "version": "7.0.0", + "name": "starknet", + "version": "6.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@penovicp/starknet", - "version": "7.0.0", + "name": "starknet", + "version": "6.11.0", "license": "MIT", "dependencies": { + "@ledgerhq/hw-transport": "^6.31.1", "@noble/curves": "~1.4.0", "@noble/hashes": "^1.4.0", "@scure/base": "~1.1.3", @@ -3858,6 +3859,49 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@ledgerhq/devices": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.1.tgz", + "integrity": "sha512-Mbjzqlcj4Q2StxEmaYEb5wv6sK5Sk26L4xs0BC9io/AyvpXNTDAp67tryB/klNcvd+WwZPcPdYYvlNzfQ0WTUA==", + "dependencies": { + "@ledgerhq/errors": "^6.18.0", + "@ledgerhq/logs": "^6.12.0", + "rxjs": "^7.8.1", + "semver": "^7.3.5" + } + }, + "node_modules/@ledgerhq/devices/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@ledgerhq/errors": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.18.0.tgz", + "integrity": "sha512-L3jQWAGyooxRDk/MRlW2v4Ji9+kloBtdmz9wBkHaj2j0n+05rweJSV3GHw9oye1BYMbVFqFffmT4H3hlXlCasw==" + }, + "node_modules/@ledgerhq/hw-transport": { + "version": "6.31.1", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.1.tgz", + "integrity": "sha512-0hVcrqUOM7AYV/JEq8yoeBiXLjpWrentgYt8MC3n+iNFfpORU/SUprcbu0s884IHzj+a8mx0JCZp9y7uPSLlzg==", + "dependencies": { + "@ledgerhq/devices": "^8.4.1", + "@ledgerhq/errors": "^6.18.0", + "@ledgerhq/logs": "^6.12.0", + "events": "^3.3.0" + } + }, + "node_modules/@ledgerhq/logs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.12.0.tgz", + "integrity": "sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==" + }, "node_modules/@noble/curves": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", @@ -8731,6 +8775,14 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -17799,6 +17851,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -19211,7 +19271,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true, "license": "0BSD" }, "node_modules/tsup": { diff --git a/package.json b/package.json index be8d24529..9b92739cd 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "typescript-coverage-report": "npm:@penovicp/typescript-coverage-report@^1.0.0-beta.2" }, "dependencies": { + "@ledgerhq/hw-transport": "^6.31.1", "@noble/curves": "~1.4.0", "@noble/hashes": "^1.4.0", "@scure/base": "~1.1.3", diff --git a/src/constants.ts b/src/constants.ts index 228bef719..ba87c28ff 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,7 @@ export { ETransactionVersion as TRANSACTION_VERSION }; export const ZERO = 0n; export const MASK_250 = 2n ** 250n - 1n; // 2 ** 250 - 1 +export const MASK_31 = 2n ** 31n - 1n; // 2 ** 31 - 1 export const API_VERSION = ZERO; export const PRIME = 2n ** 251n + 17n * 2n ** 192n + 1n; diff --git a/src/signer/index.ts b/src/signer/index.ts index 1ec304d6c..ff56b157a 100644 --- a/src/signer/index.ts +++ b/src/signer/index.ts @@ -1,3 +1,4 @@ export * from './interface'; export * from './default'; export * from './ethSigner'; +export * from './ledgerSigner'; diff --git a/src/signer/ledgerSigner.ts b/src/signer/ledgerSigner.ts new file mode 100644 index 000000000..c6471e5a1 --- /dev/null +++ b/src/signer/ledgerSigner.ts @@ -0,0 +1,267 @@ +import Transport from '@ledgerhq/hw-transport'; +import type { + InvocationsSignerDetails, + V2InvocationsSignerDetails, + V3InvocationsSignerDetails, + DeployAccountSignerDetails, + V2DeployAccountSignerDetails, + V3DeployAccountSignerDetails, + DeclareSignerDetails, + V2DeclareSignerDetails, + V3DeclareSignerDetails, + TypedData, + Call, + Signature, +} from '../types'; +import assert from '../utils/assert'; +import { CallData } from '../utils/calldata'; +import type { SignerInterface } from './interface'; +import { MASK_31 } from '../constants'; +import { ETransactionVersion2 } from '../types/api/rpcspec_0_6'; +import { getMessageHash } from '../utils/typedData'; +import { getExecuteCalldata } from '../utils/transaction'; +import { + calculateDeclareTransactionHash, + calculateDeployAccountTransactionHash, + calculateInvokeTransactionHash, +} from '../utils/hash'; +import { intDAM } from '../utils/stark'; +import { addHexPrefix, buf2hex, concatenateArrayBuffer, removeHexPrefix } from '../utils/encode'; +import { hexToBytes, stringToSha256ToArrayBuff4, toHex } from '../utils/num'; +import { starkCurve } from '../utils/ec'; +import { ETransactionVersion3 } from '../types/api'; + +/** + * Signer for accounts using a Ledger Nano S+/X signature + */ +export class LedgerSigner implements SignerInterface { + readonly transporter: Transport; + + readonly accountID: number; + + readonly eip2645applicationName: string; + + readonly pathBuffer: Uint8Array; + + private appVersion: string; + + protected pubKey: string; + + protected fullPubKey: string; + + /** + * constructor of the LedgerSigner class. + * @param {Transport} transport 5 transports are available to handle USB, bluetooth, Node, Web, Mobile. + * See Guides for more details. + * @param {number} accountID ID of Ledger Nano (can handle 2**31 accounts). + * @param {string} [eip2645application='LedgerW'] A wallet is defined by an ERC2645 derivation path (6 items). + * One item is the `application`. Default value is `LedgerW`. + * @example + * ```typescript + * import TransportNodeHid from "@ledgerhq/hw-transport-node-hid"; + * const myNodeTransport = await TransportNodeHid.create(); + * const myLedgerSigner = new LedgerSigner(myNodeTransport, 0); + * ``` + */ + constructor(transport: Transport, accountID: number, eip2645application: string = 'LedgerW') { + assert(accountID >= 0, 'Ledger account ID shall not be a negative number.'); + assert(accountID <= MASK_31, 'Ledger account ID shall be < 2**31.'); + assert(!!eip2645application, 'Ledger application name shall not be empty.'); + this.transporter = transport; + this.accountID = accountID; + this.pubKey = ''; + this.fullPubKey = ''; + this.eip2645applicationName = eip2645application; + this.appVersion = ''; + this.pathBuffer = getLedgerPathBuffer(this.accountID, this.eip2645applicationName); + } + + /** + * provides the Starknet public key + * @returns an hex string : 64 characters are Point X coordinate. + */ + public async getPubKey(): Promise { + if (!this.pubKey) await this.getPublicKeys(); + return this.pubKey; + } + + /** + * provides the full public key (with parity prefix) + * @returns an hex string : 2 first characters are the parity, the 64 following characters are Point X coordinate. 64 last characters are Point Y coordinate. + */ + public async getFullPubKey(): Promise { + if (!this.fullPubKey) await this.getPublicKeys(); + return this.fullPubKey; + } + + /** + * Returns the version of the Starknet APP implemented in the Ledger. + * @returns {string} version. + * @example + * ```typescript + * const result = await myLedgerSigner.getAppVersion(); + * // result= "1.1.1" + * ``` + */ + public async getAppVersion(): Promise { + if (!this.appVersion) { + const resp = await this.transporter.send(Number('0x5a'), 0, 0, 0); + this.appVersion = `${resp[0]}.${resp[1]}.${resp[2]}`; + } + return this.appVersion; + } + + public async signMessage(typedDataToHash: TypedData, accountAddress: string): Promise { + const msgHash = getMessageHash(typedDataToHash, accountAddress); + return this.signRaw(msgHash); + } + + public async signTransaction( + transactions: Call[], + transactionsDetail: InvocationsSignerDetails + ): Promise { + const compiledCalldata = getExecuteCalldata(transactions, transactionsDetail.cairoVersion); + let msgHash; + + // TODO: How to do generic union discriminator for all like this + if (Object.values(ETransactionVersion2).includes(transactionsDetail.version as any)) { + const det = transactionsDetail as V2InvocationsSignerDetails; + msgHash = calculateInvokeTransactionHash({ + ...det, + senderAddress: det.walletAddress, + compiledCalldata, + version: det.version, + }); + } else if (Object.values(ETransactionVersion3).includes(transactionsDetail.version as any)) { + const det = transactionsDetail as V3InvocationsSignerDetails; + msgHash = calculateInvokeTransactionHash({ + ...det, + senderAddress: det.walletAddress, + compiledCalldata, + version: det.version, + nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode), + feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode), + }); + } else { + throw Error('unsupported signTransaction version'); + } + + return this.signRaw(msgHash as string); + } + + public async signDeployAccountTransaction( + details: DeployAccountSignerDetails + ): Promise { + const compiledConstructorCalldata = CallData.compile(details.constructorCalldata); + /* const version = BigInt(details.version).toString(); */ + let msgHash; + + if (Object.values(ETransactionVersion2).includes(details.version as any)) { + const det = details as V2DeployAccountSignerDetails; + msgHash = calculateDeployAccountTransactionHash({ + ...det, + salt: det.addressSalt, + constructorCalldata: compiledConstructorCalldata, + version: det.version, + }); + } else if (Object.values(ETransactionVersion3).includes(details.version as any)) { + const det = details as V3DeployAccountSignerDetails; + msgHash = calculateDeployAccountTransactionHash({ + ...det, + salt: det.addressSalt, + compiledConstructorCalldata, + version: det.version, + nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode), + feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode), + }); + } else { + throw Error('unsupported signDeployAccountTransaction version'); + } + + return this.signRaw(msgHash as string); + } + + public async signDeclareTransaction( + // contractClass: ContractClass, // Should be used once class hash is present in ContractClass + details: DeclareSignerDetails + ): Promise { + let msgHash; + + if (Object.values(ETransactionVersion2).includes(details.version as any)) { + const det = details as V2DeclareSignerDetails; + msgHash = calculateDeclareTransactionHash({ + ...det, + version: det.version, + }); + } else if (Object.values(ETransactionVersion3).includes(details.version as any)) { + const det = details as V3DeclareSignerDetails; + msgHash = calculateDeclareTransactionHash({ + ...det, + version: det.version, + nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode), + feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode), + }); + } else { + throw Error('unsupported signDeclareTransaction version'); + } + + return this.signRaw(msgHash as string); + } + + private async signRaw(msgHash: string): Promise { + addHexPrefix( + buf2hex(await this.transporter.send(Number('0x5a'), 2, 0, 0, Buffer.from(this.pathBuffer))) + ); + // eslint-disable-next-line no-bitwise + const shiftedHash = toHex(BigInt(msgHash) << 4n); + const buff2 = hexToBytes(shiftedHash); + const respSign2 = Uint8Array.from( + await this.transporter.send(Number('0x5a'), 2, 1, 0, Buffer.from(buff2)) + ); + const r = BigInt(addHexPrefix(buf2hex(respSign2.subarray(1, 33)))); + const s = BigInt(addHexPrefix(buf2hex(respSign2.subarray(33, 65)))); + const v = respSign2[65]; + const sign0 = new starkCurve.Signature(r, s); + const sign1 = sign0.addRecoveryBit(v); + return sign1; + } + + private async getPublicKeys() { + const pathBuff = this.pathBuffer; + const respGetPublic = Uint8Array.from( + await this.transporter.send(Number('0x5a'), 1, 0, 0, Buffer.from(pathBuff)) + ); + this.pubKey = addHexPrefix(buf2hex(respGetPublic.subarray(1, 33))); + this.fullPubKey = addHexPrefix(buf2hex(respGetPublic.subarray(0, 65))); + } +} + +/** + * format the Ledger wallet path to an Uint8Array. + * EIP2645 path = 2645'/starknet'/application'/0'/accountId'/0 + * @param {number} accountId Id of account. < 2**31. + * @param {string} [applicationName='LedgerW'] utf8 string of application name. + * @returns an Uint8array of 24 bytes. + */ +export function getLedgerPathBuffer(accountId: number, applicationName: string): Uint8Array { + const path0buff = new Uint8Array([128, 0, 10, 85]); // "0x80000A55" EIP2645; + const path1buff = new Uint8Array([71, 65, 233, 201]); // "starknet" + const path2buff = + applicationName === 'LedgerW' + ? new Uint8Array([43, 206, 231, 219]) + : stringToSha256ToArrayBuff4(applicationName); + const path3buff = new Uint8Array([0, 0, 0, 0]); + const hex = toHex(accountId); + const padded = addHexPrefix(removeHexPrefix(hex).padStart(8, '0')); + const path4buff = hexToBytes(padded); + const path5buff = new Uint8Array([0, 0, 0, 0]); + const pathBuff = concatenateArrayBuffer([ + path0buff, + path1buff, + path2buff, + path3buff, + path4buff, + path5buff, + ]); + return pathBuff; +} diff --git a/src/utils/encode.ts b/src/utils/encode.ts index 4095e6ba6..6b9878a40 100644 --- a/src/utils/encode.ts +++ b/src/utils/encode.ts @@ -292,3 +292,27 @@ export const pascalToSnake = (text: string) => .join('_') .toUpperCase() : text; + +/** + * Combine multiple Uint8Arrays into one. + * Useful for wallet path creation. + * @param {Uint8Array[]} uint8arrays An array of Uint8Array. + * @returns {Uint8Array} all the Uint8Arrays joined. + * @example + * ```typescript + * const path0buff = new Uint8Array([128, 0, 10, 85]); + * const path1buff = new Uint8Array([71, 65, 233, 201]); + * const result = encode.concatenateArrayBuffer([path0buff, path1buff]); + * // result = Uint8Array(8) [128, 0, 10, 85, 71, 65, 233, 201] + * ``` + */ +export function concatenateArrayBuffer(uint8arrays: Uint8Array[]): Uint8Array { + const totalLength = uint8arrays.reduce((total, uint8array) => total + uint8array.byteLength, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + uint8arrays.forEach((uint8array) => { + result.set(uint8array, offset); + offset += uint8array.byteLength; + }); + return result; +} diff --git a/src/utils/num.ts b/src/utils/num.ts index c3211f057..5c5e622d2 100644 --- a/src/utils/num.ts +++ b/src/utils/num.ts @@ -1,8 +1,9 @@ import { hexToBytes as hexToBytesNoble } from '@noble/curves/abstract/utils'; - +import { sha256 } from '@noble/hashes/sha256'; import { BigNumberish } from '../types'; import assert from './assert'; -import { addHexPrefix, removeHexPrefix } from './encode'; +import { addHexPrefix, buf2hex, removeHexPrefix } from './encode'; +import { MASK_31 } from '../constants'; /** @deprecated prefer importing from 'types' over 'num' */ export type { BigNumberish }; @@ -376,3 +377,23 @@ export function isNumber(value: unknown): value is number { export function isBoolean(value: unknown): value is boolean { return typeof value === 'boolean'; } + +/** + * Calculate the sha256 hash of an utf8 string, then encode the + * result in an uint8Array of 4 elements. + * Useful in wallet path calculation. + * @param {string} str utf8 string (hex string not handled). + * @returns a uint8Array of 4 bytes. + * @example + * ```typescript + * const ledgerPathApplicationName = 'LedgerW'; + * const path2Buffer = num.stringToSha256ToArrayBuff4(ledgerPathApplicationName); + * // path2Buffer = Uint8Array(4) [43, 206, 231, 219] + * ``` + */ +export function stringToSha256ToArrayBuff4(str: string): Uint8Array { + // eslint-disable-next-line no-bitwise + const int31 = (n: bigint) => Number(n & MASK_31); + const result: number = int31(BigInt(addHexPrefix(buf2hex(sha256(str))))); + return hexToBytes(toHex(result)); +} diff --git a/www/docs/guides/pictures/LedgerConnectivity.png b/www/docs/guides/pictures/LedgerConnectivity.png new file mode 100644 index 0000000000000000000000000000000000000000..248064f226bd9cb9cf7517768a763226a133c4fb GIT binary patch literal 91559 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 [!NOTE] +> The last version of the Ledger Starknet APP (v1.1.1) only supports blind signing of the hash of your action. Sign only hashes from a code that you trust. + +For example, for a Node script : + +```typescript +import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; +const myLedgerTransport = await TransportNodeHid.create(); +const myLedgerSigner = new LedgerSigner(myLedgerTransport, 0); +const pubK = await myLedgerSigner.getPubKey(); +const fullPubK = await myLedgerSigner.getFullPubKey(); +// ... +// deploy here an account related to this public key +// ... +const ledgerAccount = new Account(myProvider, ledger0addr, myLedgerSigner); +``` + +> [!IMPORTANT] +> The Ledger shall be connected, unlocked, with the Starknet internal APP activated, before launch of the script. + +Some complete examples : +A Node script : [here](https://github.com/PhilippeR26/starknet.js-workshop-typescript/blob/main/src/scripts/ledgerNano/5.testLedgerAccount.ts). +A test Web DAPP, to use in devnet-rs network : [here](https://github.com/PhilippeR26/Starknet-Ledger-Wallet). + +If you want to read the version of the Ledger Starknet APP : + +```typescript +const resp = await myLedgerTransport.send(Number('0x5a'), 0, 0, 0); +const appVersion = resp[0] + '.' + resp[1] + '.' + resp[2]; +console.log('version=', appVersion); +``` From f8c1dacc164d487eeac014f932a395ca064e9545 Mon Sep 17 00:00:00 2001 From: PhilippeR26 Date: Thu, 25 Jul 2024 19:25:52 +0200 Subject: [PATCH 09/10] feat: add L1->L2 hashes --- __tests__/utils/hash.test.ts | 59 ++++++++++++++++ src/provider/rpc.ts | 19 ++---- src/utils/hash/selector.ts | 89 +++++++++++++++++++++---- src/utils/hash/transactionHash/index.ts | 1 + src/utils/hash/transactionHash/v2.ts | 48 +++++++++++++ www/docs/guides/L1message.md | 60 ++++++++++++++++- 6 files changed, 248 insertions(+), 28 deletions(-) diff --git a/__tests__/utils/hash.test.ts b/__tests__/utils/hash.test.ts index 2d1399794..2b24789f3 100644 --- a/__tests__/utils/hash.test.ts +++ b/__tests__/utils/hash.test.ts @@ -1,4 +1,5 @@ import { keccakBn, starknetKeccak, getSelectorFromName, getSelector } from '../../src/utils/hash'; +import { constants, hash } from '../../src'; describe('keccakBn', () => { test('should properly calculate the Keccak hash', () => { @@ -38,4 +39,62 @@ describe('getSelector', () => { test('should return the proper selector when provided a decimal string', () => { expect(getSelector('123456')).toBe('0x1e240'); }); + + test('should return the proper selector when provided a number', () => { + expect(getSelector(123456)).toBe('0x1e240'); + }); + + test('should return the proper selector when provided a bigint', () => { + expect(getSelector(123456n)).toBe('0x1e240'); + }); +}); + +describe('L1->L2 messaging', () => { + // L1 tx for a message L1->L2 + // data extracted from : + // https://sepolia.etherscan.io/tx/0xd82ce7dd9f3964d89d2eb9d555e1460fb7792be274950abe578d610f95cc40f5 + // data extracted from etherscan : + const l1FromAddress = '0x0000000000000000000000008453fc6cd1bcfe8d4dfc069c400b433054d47bdc'; + const l2ToAddress = 2158142789748719025684046545159279785659305214176670733242887773692203401023n; + const l2Selector = 774397379524139446221206168840917193112228400237242521560346153613428128537n; + const payload = [ + 4543560n, + 829565602143178078434185452406102222830667255948n, + 3461886633118033953192540141609307739580461579986333346825796013261542798665n, + 9000000000000000n, + 0n, + ]; + const l1Nonce = 8288n; + + test('solidityUint256PackedKeccak256', () => { + const kec256Hash = hash.solidityUint256PackedKeccak256(['0x100', '200', 300, 400n]); + expect(kec256Hash).toBe('0xd1e6cb422b65269603c491b0c85463295edabebfb2a6844e4fdc389ff1dcdd97'); + }); + + test('getL2MessageHash', () => { + // https://sepolia.starkscan.co/message/0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090#messagelogs + const l1ToL2MessageHash = hash.getL2MessageHash( + l1FromAddress, + l2ToAddress, + l2Selector, + payload, + l1Nonce + ); + expect(l1ToL2MessageHash).toBe( + '0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090' + ); + }); + + test('calculateL2MessageTxHash', () => { + // https://sepolia.starkscan.co/tx/0x067d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07 + const l2TxHash = hash.calculateL2MessageTxHash( + l1FromAddress, + l2ToAddress, + l2Selector, + payload, + constants.StarknetChainId.SN_SEPOLIA, + l1Nonce + ); + expect(l2TxHash).toBe('0x67d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07'); + }); }); diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 65d8c0ac4..548e1215f 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -1,7 +1,4 @@ -import { bytesToHex } from '@noble/curves/abstract/utils'; -import { keccak_256 } from '@noble/hashes/sha3'; import type { SPEC } from 'starknet-types-07'; - import { RPC06, RPC07, RpcChannel } from '../channel'; import { AccountInvocations, @@ -34,13 +31,13 @@ import type { TransactionWithHash } from '../types/provider/spec'; import assert from '../utils/assert'; import { getAbiContractVersion } from '../utils/calldata/cairo'; import { isSierra } from '../utils/contract'; -import { addHexPrefix, removeHexPrefix } from '../utils/encode'; -import { hexToBytes, toHex } from '../utils/num'; +import { toHex } from '../utils/num'; import { wait } from '../utils/provider'; import { RPCResponseParser } from '../utils/responseParser/rpc'; import { GetTransactionReceiptResponse, ReceiptTx } from '../utils/transactionReceipt'; import { LibraryError } from './errors'; import { ProviderInterface } from './interface'; +import { solidityUint256PackedKeccak256 } from '../utils/hash'; export class RpcProvider implements ProviderInterface { public responseParser: RPCResponseParser; @@ -115,7 +112,7 @@ export class RpcProvider implements ProviderInterface { /** * Pause the execution of the script until a specified block is created. - * @param {BlockIdentifier} blockIdentifier bloc number (BigNumberisk) or 'pending' or 'latest'. + * @param {BlockIdentifier} blockIdentifier bloc number (BigNumberish) or 'pending' or 'latest'. * Use of 'latest" or of a block already created will generate no pause. * @param {number} [retryInterval] number of milliseconds between 2 requests to the node * @example @@ -160,7 +157,7 @@ export class RpcProvider implements ProviderInterface { .then(this.responseParser.parseL1GasPriceResponse); } - public async getL1MessageHash(l2TxHash: BigNumberish) { + public async getL1MessageHash(l2TxHash: BigNumberish): Promise { const transaction = (await this.channel.getTransactionByHash(l2TxHash)) as TransactionWithHash; assert(transaction.type === 'L1_HANDLER', 'This L2 transaction is not a L1 message.'); const { calldata, contract_address, entry_point_selector, nonce } = @@ -173,13 +170,7 @@ export class RpcProvider implements ProviderInterface { calldata.length - 1, ...calldata.slice(1), ]; - const myEncode = addHexPrefix( - params.reduce( - (res: string, par: BigNumberish) => res + removeHexPrefix(toHex(par)).padStart(64, '0'), - '' - ) - ); - return addHexPrefix(bytesToHex(keccak_256(hexToBytes(myEncode)))); + return solidityUint256PackedKeccak256(params); } public async getBlockWithReceipts(blockIdentifier?: BlockIdentifier) { diff --git a/src/utils/hash/selector.ts b/src/utils/hash/selector.ts index 063b662a8..87d4dd918 100644 --- a/src/utils/hash/selector.ts +++ b/src/utils/hash/selector.ts @@ -1,12 +1,13 @@ import { keccak } from '@scure/starknet'; - +import { keccak_256 } from '@noble/hashes/sha3'; +import { bytesToHex } from '@noble/curves/abstract/utils'; import { MASK_250 } from '../../constants'; import { BigNumberish } from '../../types'; import { addHexPrefix, removeHexPrefix, utf8ToArray } from '../encode'; -import { hexToBytes, isHex, isStringWholeNumber, toHex, toHexString } from '../num'; +import { hexToBytes, isBigInt, isHex, isNumber, isStringWholeNumber, toHex } from '../num'; /** - * Calculate the hex-string Keccak hash for a given BigNumberish + * Calculate the hex-string Starknet Keccak hash for a given BigNumberish * * @param value value to hash * @returns hex-string Keccak hash @@ -24,7 +25,7 @@ export function keccakBn(value: BigNumberish): string { /** * [internal] - * Calculate hex-string Keccak hash for a given string + * Calculate hex-string Starknet Keccak hash for a given string * * String -> hex-string Keccak hash * @returns format: hex-string @@ -69,9 +70,9 @@ export function getSelectorFromName(funcName: string) { } /** - * Calculate the hex-string selector from a given abi function name, decimal string or hex string + * Calculate the hex-string selector from a given abi function name or of any representation of number. * - * @param value hex-string | dec-string | ascii-string + * @param value ascii-string | hex-string | dec-string | number | BigInt * @returns hex-string selector * @example * ```typescript @@ -83,14 +84,76 @@ export function getSelectorFromName(funcName: string) { * * const selector3: string = getSelector("123456"); * // selector3 = "0x1e240" + * + * const selector4: string = getSelector(123456n); + * // selector4 = "0x1e240" * ``` */ -export function getSelector(value: string) { - if (isHex(value)) { - return value; - } - if (isStringWholeNumber(value)) { - return toHexString(value); - } +export function getSelector(value: string | BigNumberish) { + if (isNumber(value) || isBigInt(value)) return toHex(value); + if (isHex(value)) return value; + if (isStringWholeNumber(value)) return toHex(value); return getSelectorFromName(value); } + +/** + * Solidity hash of an array of uint256 + * @param {BigNumberish[]} params an array of uint256 numbers + * @returns the hash of the array of Solidity uint256 + * @example + * ```typescript + * const result = hash.solidityUint256PackedKeccak256(['0x100', '200', 300, 400n]); + * // result = '0xd1e6cb422b65269603c491b0c85463295edabebfb2a6844e4fdc389ff1dcdd97' + * ``` + */ +export function solidityUint256PackedKeccak256(params: BigNumberish[]): string { + const myEncode = addHexPrefix( + params.reduce( + (res: string, par: BigNumberish) => res + removeHexPrefix(toHex(par)).padStart(64, '0'), + '' + ) + ); + return addHexPrefix(bytesToHex(keccak_256(hexToBytes(myEncode)))); +} + +/** + * Calculate the L2 message hash related by a message L1->L2 + * @param {BigNumberish} l1FromAddress L1 account address that paid the message. + * @param {BigNumberish} l2ToAddress L2 contract address to execute. + * @param {string | BigNumberish} l2Selector can be a function name ("bridge_withdraw") or a number (BigNumberish). + * @param {RawCalldata} l2Calldata an array of BigNumberish of the raw parameters passed to the above function. + * @param {BigNumberish} l1Nonce The nonce of the L1 account. + * @returns {string} hex-string of the L2 transaction hash + * @example + * ```typescript + * const l1FromAddress = "0x0000000000000000000000008453fc6cd1bcfe8d4dfc069c400b433054d47bdc"; + * const l2ToAddress = 2158142789748719025684046545159279785659305214176670733242887773692203401023n; + * const l2Selector = 774397379524139446221206168840917193112228400237242521560346153613428128537n; + * const payload = [ + * 4543560n, + * 829565602143178078434185452406102222830667255948n, + * 3461886633118033953192540141609307739580461579986333346825796013261542798665n, + * 9000000000000000n, + * 0n, + * ]; + * const l1Nonce = 8288n; + * const result = hash.getL2MessageHash(l1FromAddress, l2ToAddress, l2Selector, payload, l1Nonce); + * // result = "0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090" + * ``` + */ +export function getL2MessageHash( + l1FromAddress: BigNumberish, + l2ToAddress: BigNumberish, + l2Selector: string | BigNumberish, + l2Calldata: BigNumberish[], + l1Nonce: BigNumberish +): string { + return solidityUint256PackedKeccak256([ + l1FromAddress, + l2ToAddress, + l1Nonce, + l2Selector, + l2Calldata.length, + ...l2Calldata, + ]); +} diff --git a/src/utils/hash/transactionHash/index.ts b/src/utils/hash/transactionHash/index.ts index cc0a2e3cf..f0a28d6f4 100644 --- a/src/utils/hash/transactionHash/index.ts +++ b/src/utils/hash/transactionHash/index.ts @@ -22,6 +22,7 @@ import { calculateInvokeTransactionHash as v3calculateInvokeTransactionHash, } from './v3'; +export { calculateL2MessageTxHash } from './v2'; /* * INVOKE TX HASH */ diff --git a/src/utils/hash/transactionHash/v2.ts b/src/utils/hash/transactionHash/v2.ts index 253824a6b..1ba81088c 100644 --- a/src/utils/hash/transactionHash/v2.ts +++ b/src/utils/hash/transactionHash/v2.ts @@ -8,6 +8,7 @@ import { StarknetChainId, TransactionHashPrefix } from '../../../constants'; import { BigNumberish, RawCalldata } from '../../../types'; import { starkCurve } from '../../ec'; import { toBigInt } from '../../num'; +import { getSelector } from '../selector'; /** * Compute pedersen hash from data @@ -127,3 +128,50 @@ export function calculateTransactionHash( [nonce] ); } + +/** + * Calculate the L2 transaction hash generated by a message L1->L2 + * @param {BigNumberish} l1FromAddress L1 account address that paid the message. + * @param {BigNumberish} l2ToAddress L2 contract address to execute. + * @param {string | BigNumberish} l2Selector can be a function name ("bridge_withdraw") or a number (BigNumberish). + * @param {RawCalldata} l2Calldata an array of BigNumberish of the raw parameters passed to the above function. + * @param {BigNumberish} l2ChainId L2 chain ID : from constants.StarknetChainId.xxx + * @param {BigNumberish} l1Nonce The nonce of the L1 account. + * @returns {string} hex-string of the L2 transaction hash + * @example + * ```typescript + * const l1FromAddress = "0x0000000000000000000000008453fc6cd1bcfe8d4dfc069c400b433054d47bdc"; + * const l2ToAddress = 2158142789748719025684046545159279785659305214176670733242887773692203401023n; + * const l2Selector = 774397379524139446221206168840917193112228400237242521560346153613428128537n; + * const payload = [ + * 4543560n, + * 829565602143178078434185452406102222830667255948n, + * 3461886633118033953192540141609307739580461579986333346825796013261542798665n, + * 9000000000000000n, + * 0n, + * ]; + * const l1Nonce = 8288n; + * const result = hash.calculateL2MessageTxHash(l1FromAddress, l2ToAddress, l2Selector, payload, constants.StarknetChainId.SN_SEPOLIA, l1Nonce); + * // result = "0x67d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07" + * ``` + */ +export function calculateL2MessageTxHash( + l1FromAddress: BigNumberish, + l2ToAddress: BigNumberish, + l2Selector: string | BigNumberish, + l2Calldata: RawCalldata, + l2ChainId: StarknetChainId, + l1Nonce: BigNumberish +): string { + const payload = [l1FromAddress, ...l2Calldata]; + return calculateTransactionHashCommon( + TransactionHashPrefix.L1_HANDLER, + 0, + l2ToAddress, + getSelector(l2Selector), + payload, + 0, + l2ChainId, + [l1Nonce] + ); +} diff --git a/www/docs/guides/L1message.md b/www/docs/guides/L1message.md index d4f8ecc30..22708d55b 100644 --- a/www/docs/guides/L1message.md +++ b/www/docs/guides/L1message.md @@ -8,7 +8,7 @@ You can exchange messages between L1 & L2 networks: - L2 Starknet mainnet ↔️ L1 Ethereum. - L2 Starknet testnet ↔️ L1 Sepolia ETH testnet. -- L2 local Starknet devnet ↔️ L1 local ETH testnet (Ganache, ...). +- L2 local Starknet devnet ↔️ L1 local ETH testnet (anvil, ...). You can find an explanation of the global mechanism [here](https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/). @@ -48,6 +48,52 @@ const responseEstimateMessageFee = await provider.estimateMessageFee({ If the fee is paid in L1, the Cairo contract at `to_Address` is automatically executed, function `entry_point_selector` (the function shall have a decorator `#[l1_handler]` in the Cairo code, with a first parameter called `from_address: felt252`). The payload shall not include the `from_address` parameter. +### L1 ➡️ L2 hashes + +Starknet.js proposes 2 functions to calculate hashes related to a L1 ➡️ L2 message : + +- The L2 message hash: + For a L1 tx requesting a message L1->L2, some data extracted from etherscan : https://sepolia.etherscan.io/tx/0xd82ce7dd9f3964d89d2eb9d555e1460fb7792be274950abe578d610f95cc40f5 + + ```typescript + const l1FromAddress = '0x0000000000000000000000008453fc6cd1bcfe8d4dfc069c400b433054d47bdc'; + const l2ToAddress = 2158142789748719025684046545159279785659305214176670733242887773692203401023n; + const l2Selector = 774397379524139446221206168840917193112228400237242521560346153613428128537n; + const payload = [ + 4543560n, + 829565602143178078434185452406102222830667255948n, + 3461886633118033953192540141609307739580461579986333346825796013261542798665n, + 9000000000000000n, + 0n, + ]; + const l1Nonce = 8288n; + const l1ToL2MessageHash = hash.getL2MessageHash( + l1FromAddress, + l2ToAddress, + l2Selector, + payload, + l1Nonce + ); + // l1ToL2MessageHash = '0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090' + ``` + + Can be verified here : https://sepolia.starkscan.co/message/0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090#messagelogs + +- The L2 transaction hash: + For the same message: + ```typescript + const l1ToL2TransactionHash = hash.calculateL2MessageTxHash( + l1FromAddress, + l2ToAddress, + l2Selector, + payload, + constants.StarknetChainId.SN_SEPOLIA, + l1Nonce + ); + // l1ToL2TransactionHash = '0x67d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07' + ``` + Can be verified here : https://sepolia.starkscan.co/tx/0x067d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07 + ## L2 ➡️ L1 messages To send a message to L1, you will just invoke a Cairo contract function, paying a fee that will pay all the processes (in L1 & L2). @@ -63,3 +109,15 @@ const { suggestedMaxFee: estimatedFee1 } = await account0.estimateInvokeFee({ ``` The result is in `estimatedFee1`, of type BN. + +### L2 ➡️ L1 hash + +Starknet.js proposes a function to calculate the L1 ➡️ L2 message hash : + +```typescript +const l2TransactionHash = '0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819'; +const l1MessageHash = await provider.getL1MessageHash(l2TransactionHash); +// l1MessageHash = '0x55b3f8b6e607fffd9b4d843dfe8f9b5c05822cd94fcad8797deb01d77805532a' +``` + +Can be verified here : https://sepolia.voyager.online/tx/0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819#messages From 13ef078c2bda5e54512c7663f44a9afc6ae66f7c Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Fri, 26 Jul 2024 13:06:43 +0200 Subject: [PATCH 10/10] docs: batchClient --- www/docs/guides/connect_network.md | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/www/docs/guides/connect_network.md b/www/docs/guides/connect_network.md index 9abc87917..5eea392c5 100644 --- a/www/docs/guides/connect_network.md +++ b/www/docs/guides/connect_network.md @@ -160,3 +160,43 @@ const provider = new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050/rpc' }); ``` > If you have customized host and port during starknet-devnet initialization, adapt in accordance your script. + +## Batch JSON-RPC + +The BatchClient class allows requests to be batched together in a single HTTP request, either by the interval amount or at the end of the callback queue if the batch is set to 0. By batching requests, we can reduce the overhead associated with handling individual requests. + +#### Example of usage with RpcProvider + +```typescript +const myProvider = new RpcProvider({ + batch: 0, +}); + +const [getBlockResponse, blockHashAndNumber, txCount] = await Promise.all([ + myBatchProvider.getBlock(), + myBatchProvider.getBlockLatestAccepted(), + myBatchProvider.getBlockTransactionCount('latest'), +]); + +// ... usage of getBlockResponse, blockHashAndNumber, txCount +``` + +#### Example of direct usage of underlying BatchClient class + +```typescript +const provider = new RpcProvider(); + +const batchClient = new BatchClient({ + nodeUrl: provider.channel.nodeUrl, + headers: provider.channel.headers, + interval: 0, +}); + +const [getBlockResponse, blockHashAndNumber, txCount] = await Promise.all([ + myBatchProvider.getBlock(), + myBatchProvider.getBlockLatestAccepted(), + myBatchProvider.getBlockTransactionCount('latest'), +]); + +// ... usage of getBlockResponse, blockHashAndNumber, txCount +```