diff --git a/package.json b/package.json index e24c8a03..a71d5932 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,19 @@ "types": "./dist/esm/crypto/index.d.ts", "import": "./dist/esm/crypto/index.js", "require": "./dist/cjs/crypto/index.js" + }, + "./core": { + "types": "./dist/esm/core/index.d.ts", + "import": "./dist/esm/core/index.js", + "require": "./dist/cjs/core/index.js" } }, "files": [ "dist" ], "scripts": { - "build": "tsc --skipLibCheck -p tsconfig.esm.json && tsc --skipLibCheck -p tsconfig.cjs.json && node -e \"fs.mkdirSync('dist/esm', {recursive: true}); fs.writeFileSync('dist/esm/package.json', '{\\\"type\\\": \\\"module\\\"}')\" && node -e \"fs.mkdirSync('dist/cjs', {recursive: true}); fs.writeFileSync('dist/cjs/package.json', '{\\\"type\\\": \\\"commonjs\\\"}')\"", - "typecheck": "tsc --skipLibCheck -p tsconfig.json --noEmit", + "build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node -e \"fs.mkdirSync('dist/esm', {recursive: true}); fs.writeFileSync('dist/esm/package.json', '{\\\"type\\\": \\\"module\\\"}')\" && node -e \"fs.mkdirSync('dist/cjs', {recursive: true}); fs.writeFileSync('dist/cjs/package.json', '{\\\"type\\\": \\\"commonjs\\\"}')\"", + "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "bench": "node bench/signer.bench.mjs", "docs": "typedoc" diff --git a/src/client.ts b/src/client.ts index 527d1dcd..0ea0d10f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -152,55 +152,60 @@ export function createWhiteChainClient(config: WhiteChainConfig & { provider?: a const wc = requireWallet() const abi = requireGrantAbi() return (wc as any).writeContract({ + chain: config.chain as any, address: addresses.grant, abi, functionName: 'submitApplication', args: [grantId, applicant, metadataUri], - }) + } as any) }, async approveApplication({ applicationId }) { const wc = requireWallet() const abi = requireGrantAbi() return (wc as any).writeContract({ + chain: config.chain as any, address: addresses.grant, abi, functionName: 'approveApplication', args: [applicationId], - }) + } as any) }, async submitMilestoneEvidence({ milestoneId, evidenceUri }) { const wc = requireWallet() const abi = requireGrantAbi() return (wc as any).writeContract({ + chain: config.chain as any, address: addresses.grant, abi, functionName: 'submitMilestoneEvidence', args: [milestoneId, evidenceUri], - }) + } as any) }, async approveMilestone({ milestoneId }) { const wc = requireWallet() const abi = requireGrantAbi() return (wc as any).writeContract({ + chain: config.chain as any, address: addresses.grant, abi, functionName: 'approveMilestone', args: [milestoneId], - }) + } as any) }, async releasePayout({ milestoneId }) { const wc = requireWallet() const abi = requireGrantAbi() return (wc as any).writeContract({ + chain: config.chain as any, address: addresses.grant, abi, functionName: 'releasePayout', args: [milestoneId], - }) + } as any) }, submitApplication: withGasEstimation( async ({ grantId, applicant, metadataUri }) => { diff --git a/src/core/Multicall.ts b/src/core/Multicall.ts new file mode 100644 index 00000000..c0324684 --- /dev/null +++ b/src/core/Multicall.ts @@ -0,0 +1,137 @@ +import type { Address, Hex, PublicClient } from 'viem' +import type { MulticallCall, MulticallResult, MulticallOptions } from '../types/multicall.js' +import { WhiteChainError } from '../types.js' + +/** + * Standard Multicall3 contract address deployed on Whitechain and EVM networks. + */ +export const MULTICALL3_DEFAULT_ADDRESS: Address = '0xca11bde05977b3631167028862be2a173976ca11' + +/** + * Minimal Multicall3 ABI for aggregate3 execution. + */ +export const MULTICALL3_ABI = [ + { + inputs: [ + { + components: [ + { name: 'target', type: 'address' }, + { name: 'allowFailure', type: 'bool' }, + { name: 'callData', type: 'bytes' }, + ], + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'aggregate3', + outputs: [ + { + components: [ + { name: 'success', type: 'bool' }, + { name: 'returnData', type: 'bytes' }, + ], + name: 'returnData', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, +] as const + +export class Multicall { + public readonly multicallAddress: Address + private publicClient?: PublicClient + + constructor(options?: { publicClient?: PublicClient; multicallAddress?: Address }) { + this.publicClient = options?.publicClient + this.multicallAddress = options?.multicallAddress ?? MULTICALL3_DEFAULT_ADDRESS + } + + /** + * Batches multiple view calls into a single RPC eth_call request to Multicall3. + * + * @param calls Array of MulticallCall targets and callData payloads. + * @param options Execution overrides (publicClient, multicallAddress, default allowFailure). + * @returns Clean, typed array of MulticallResult matching the input calls. + */ + public async aggregate( + calls: TCalls, + options?: MulticallOptions + ): Promise<{ [K in keyof TCalls]: MulticallResult ? R : any> }> { + if (!calls || calls.length === 0) { + return [] as any + } + + const client = options?.publicClient ?? this.publicClient + if (!client) { + throw new WhiteChainError('No publicClient provided for Multicall aggregate execution') + } + + const multicallAddress = options?.multicallAddress ?? this.multicallAddress + + // Format calls into Multicall3 aggregate3 call tuples: [target, allowFailure, callData] + const formattedCalls = calls.map((c) => ({ + target: c.target, + allowFailure: c.allowFailure ?? options?.allowFailure ?? true, + callData: c.callData, + })) + + // Execute single eth_call to Multicall3 aggregate3 + const rawResults = (await (client as any).readContract({ + address: multicallAddress, + abi: MULTICALL3_ABI, + functionName: 'aggregate3', + args: [formattedCalls], + })) as Array<{ success: boolean; returnData: Hex }> + + // Process and decode each return tuple + const results = rawResults.map((res, i) => { + const call = calls[i] + const success = res.success + const returnData = res.returnData + + if (!success) { + return { + success: false, + data: null, + returnData, + error: new WhiteChainError(`Multicall view function reverted at index ${i} (target: ${call.target})`), + } + } + + if (call.decoder) { + try { + const decodedData = call.decoder(returnData) + return { + success: true, + data: decodedData, + returnData, + } + } catch (err) { + return { + success: false, + data: null, + returnData, + error: err instanceof Error ? err : new WhiteChainError(String(err)), + } + } + } + + return { + success: true, + data: returnData, + returnData, + } + }) + + return results as any + } +} + +/** + * Factory helper to construct a Multicall instance. + */ +export function createMulticall(options?: { publicClient?: PublicClient; multicallAddress?: Address }): Multicall { + return new Multicall(options) +} diff --git a/src/core/index.ts b/src/core/index.ts index 30301ffb..d11838ea 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,3 +1,15 @@ -export { Contract } from './Contract.js' export { Contract, type ContractClient } from './Contract.js' export { WhitechainSDK, type WhitechainSDKConfig, type WhitechainSDKPlugins } from './WhitechainSDK.js' + +export { + Multicall, + createMulticall, + MULTICALL3_DEFAULT_ADDRESS, + MULTICALL3_ABI, +} from './Multicall.js' + +export type { + MulticallCall, + MulticallResult, + MulticallOptions, +} from '../types/multicall.js' diff --git a/src/index.ts b/src/index.ts index 056e1eaf..02ced4a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,6 +43,13 @@ export { } from './providers/BrowserProvider.js' export { + NonceManager, + createNonceManager, + type NonceManagerOptions, + type GetOnChainNonceFn, +} from './wallet/index.js' + + IpcProvider, type IpcProviderOptions, } from './providers/IpcProvider.js' diff --git a/src/types/multicall.ts b/src/types/multicall.ts new file mode 100644 index 00000000..32c4d1af --- /dev/null +++ b/src/types/multicall.ts @@ -0,0 +1,47 @@ +import type { Address, Hex, PublicClient } from 'viem' +import type { WhiteChainError } from '../types.js' + +/** + * A single call payload to be batched via Multicall3. + */ +export type MulticallCall = { + /** The target contract address for the view call. */ + target: Address + /** The encoded ABI function call payload. */ + callData: Hex + /** + * If true (default: true), a revert in this specific call will not cause the + * overall batch to revert. + */ + allowFailure?: boolean + /** + * Optional decoder function to convert returned `Hex` bytes into a typed object. + */ + decoder?: (returnData: Hex) => T +} + +/** + * The result of a single call batched via Multicall3. + */ +export type MulticallResult = { + /** Whether the specific call succeeded on-chain. */ + success: boolean + /** The decoded return data if successful and decoder returned, otherwise raw Hex or null. */ + data: T | null + /** The raw returned bytes from the call execution. */ + returnData: Hex + /** Error object if the call reverted or decoding failed. */ + error?: WhiteChainError | Error +} + +/** + * Options for configuring Multicall3 execution. + */ +export type MulticallOptions = { + /** Override the Multicall3 contract address. */ + multicallAddress?: Address + /** PublicClient instance to execute the eth_call request. */ + publicClient?: PublicClient + /** Default allowFailure setting for calls in this batch if call.allowFailure is omitted. */ + allowFailure?: boolean +} diff --git a/src/wallet/NonceManager.ts b/src/wallet/NonceManager.ts index 09e7044b..70ce6c87 100644 --- a/src/wallet/NonceManager.ts +++ b/src/wallet/NonceManager.ts @@ -1,3 +1,123 @@ +import type { Address, PublicClient } from 'viem' +import { WhiteChainError } from '../types.js' + +export type GetOnChainNonceFn = (address: Address) => Promise + +export type NonceManagerOptions = { + address: Address + publicClient?: PublicClient | { getTransactionCount(args: { address: Address; blockTag?: string }): Promise } + getOnChainNonce?: GetOnChainNonceFn + initialNonce?: number +} + +export class NonceManager { + public readonly address: Address + private _nextNonce: number | null = null + private _getOnChainNonce?: GetOnChainNonceFn + private _initialFetchPromise: Promise | null = null + private _nonceQueue: Array<(nonce: number) => void> = [] + + constructor(options: NonceManagerOptions) { + this.address = options.address + + if (options.initialNonce !== undefined) { + if (options.initialNonce < 0 || !Number.isInteger(options.initialNonce)) { + throw new WhiteChainError('initialNonce must be a non-negative integer') + } + this._nextNonce = options.initialNonce + } + + if (options.getOnChainNonce) { + this._getOnChainNonce = options.getOnChainNonce + } else if (options.publicClient) { + this._getOnChainNonce = async (addr: Address) => { + const count = await (options.publicClient as any).getTransactionCount({ address: addr, blockTag: 'pending' }) + return typeof count === 'bigint' ? Number(count) : count + } + } + } + + public isInitialized(): boolean { + return this._nextNonce !== null + } + + public getCachedNonce(): number | null { + return this._nextNonce + } + + public async getNextNonce(): Promise { + if (this._nextNonce !== null) { + const nonce = this._nextNonce + this._nextNonce++ + return nonce + } + + if (this._initialFetchPromise) { + return new Promise((resolve) => { + this._nonceQueue.push(resolve) + }) + } + + if (!this._getOnChainNonce) { + throw new WhiteChainError('No publicClient or getOnChainNonce provider configured for NonceManager') + } + + this._initialFetchPromise = this._getOnChainNonce(this.address) + + try { + const onChainNonce = await this._initialFetchPromise + + const queuedResolvers = this._nonceQueue + this._nonceQueue = [] + + let curr = onChainNonce + 1 + for (const resolve of queuedResolvers) { + resolve(curr) + curr++ + } + this._nextNonce = curr + this._initialFetchPromise = null + + return onChainNonce + } catch (err) { + this._initialFetchPromise = null + this._nonceQueue = [] + throw err + } + } + + public async getNextNonceBigInt(): Promise { + const nonce = await this.getNextNonce() + return BigInt(nonce) + } + + public setNonce(nonce: number): void { + if (nonce < 0 || !Number.isInteger(nonce)) { + throw new WhiteChainError('nonce must be a non-negative integer') + } + this._nextNonce = nonce + } + + public reset(): void { + this._nextNonce = null + this._initialFetchPromise = null + this._nonceQueue = [] + } + + public async sendTransaction(sendFn: (nonce: number) => Promise): Promise { + const nonce = await this.getNextNonce() + try { + return await sendFn(nonce) + } catch (error) { + // If the transaction fails, caller can choose to reset() or handle + throw error + } + } +} + +export function createNonceManager(options: NonceManagerOptions): NonceManager { + return new NonceManager(options) +} export class NonceManager { private nonces = new Map() private pendingFetches = new Map>() diff --git a/src/wallet/index.ts b/src/wallet/index.ts index d1d64964..9d551f9b 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -1,2 +1,8 @@ +export { + NonceManager, + createNonceManager, + type NonceManagerOptions, + type GetOnChainNonceFn, +} from './NonceManager.js' export { NonceManager } from './NonceManager.js' export { HDWallet, createHDWallet, type HDWalletOptions } from './HDWallet.js' diff --git a/tests/core/Multicall.test.ts b/tests/core/Multicall.test.ts new file mode 100644 index 00000000..ee1716bb --- /dev/null +++ b/tests/core/Multicall.test.ts @@ -0,0 +1,155 @@ +import { describe, it, expect, vi } from 'vitest' +import { + Multicall, + createMulticall, + MULTICALL3_DEFAULT_ADDRESS, + MULTICALL3_ABI, +} from '../../src/core/Multicall.js' +import { WhiteChainError } from '../../src/types.js' +import type { Address, Hex } from 'viem' + +const dummyTarget = '0x1111111111111111111111111111111111111111' as Address +const customMulticallAddress = '0x2222222222222222222222222222222222222222' as Address + +describe('Multicall3 Batching', () => { + it('batches 50 queries into exactly 1 HTTP RPC call', async () => { + let readContractCallCount = 0 + let lastContractCallArgs: any = null + + // Mock publicClient that tracks call count + const mockPublicClient = { + readContract: vi.fn().mockImplementation(async (args: any) => { + readContractCallCount++ + lastContractCallArgs = args + // Return 50 successful mock responses + return Array.from({ length: 50 }, (_, i) => ({ + success: true, + returnData: `0x${(i + 1).toString(16).padStart(64, '0')}` as Hex, + })) + }), + } as any + + const multicall = new Multicall({ publicClient: mockPublicClient }) + + // Build 50 call objects + const calls = Array.from({ length: 50 }, (_, i) => ({ + target: dummyTarget, + callData: `0xabc${i}` as Hex, + decoder: (bytes: Hex) => parseInt(bytes, 16), + })) + + const results = await multicall.aggregate(calls) + + // Verify EXACTLY 1 RPC request was issued for 50 queries + expect(readContractCallCount).toBe(1) + expect(mockPublicClient.readContract).toHaveBeenCalledTimes(1) + expect(lastContractCallArgs.address).toBe(MULTICALL3_DEFAULT_ADDRESS) + expect(lastContractCallArgs.abi).toEqual(MULTICALL3_ABI) + expect(lastContractCallArgs.functionName).toBe('aggregate3') + expect(lastContractCallArgs.args[0].length).toBe(50) + + // Verify 50 results parsed correctly + expect(results.length).toBe(50) + expect(results[0]).toEqual({ + success: true, + data: 1, + returnData: '0x0000000000000000000000000000000000000000000000000000000000000001', + }) + expect(results[49].data).toBe(50) + }) + + it('gracefully handles partial failures (reverts) with aggregate3 allowFailure', async () => { + const mockPublicClient = { + readContract: vi.fn().mockResolvedValue([ + { success: true, returnData: '0x000000000000000000000000000000000000000000000000000000000000000a' }, + { success: false, returnData: '0x' }, // Reverted call + { success: true, returnData: '0x0000000000000000000000000000000000000000000000000000000000000014' }, + ]), + } as any + + const multicall = createMulticall({ publicClient: mockPublicClient }) + + const calls = [ + { target: dummyTarget, callData: '0x1111' as Hex, decoder: (hex: Hex) => parseInt(hex, 16) }, + { target: dummyTarget, callData: '0x2222' as Hex, allowFailure: true }, + { target: dummyTarget, callData: '0x3333' as Hex, decoder: (hex: Hex) => parseInt(hex, 16) }, + ] + + const results = await multicall.aggregate(calls) + + expect(results.length).toBe(3) + // Successful call #1 + expect(results[0].success).toBe(true) + expect(results[0].data).toBe(10) + + // Failed call #2 (reverted on-chain, but batch succeeded) + expect(results[1].success).toBe(false) + expect(results[1].data).toBe(null) + expect(results[1].error).toBeInstanceOf(WhiteChainError) + + // Successful call #3 + expect(results[2].success).toBe(true) + expect(results[2].data).toBe(20) + }) + + it('supports custom multicallAddress override', async () => { + const mockPublicClient = { + readContract: vi.fn().mockResolvedValue([ + { success: true, returnData: '0x1234' }, + ]), + } as any + + const multicall = new Multicall({ + publicClient: mockPublicClient, + multicallAddress: customMulticallAddress, + }) + + await multicall.aggregate([ + { target: dummyTarget, callData: '0x9999' as Hex }, + ]) + + expect(mockPublicClient.readContract).toHaveBeenCalledWith( + expect.objectContaining({ + address: customMulticallAddress, + }) + ) + }) + + it('catches decoder errors and marks individual result as failed', async () => { + const mockPublicClient = { + readContract: vi.fn().mockResolvedValue([ + { success: true, returnData: '0xinvalid' }, + ]), + } as any + + const multicall = new Multicall({ publicClient: mockPublicClient }) + + const results = await multicall.aggregate([ + { + target: dummyTarget, + callData: '0x1234' as Hex, + decoder: () => { + throw new Error('Decoder failed to parse bytes') + }, + }, + ]) + + expect(results[0].success).toBe(false) + expect(results[0].data).toBe(null) + expect(results[0].error?.message).toBe('Decoder failed to parse bytes') + }) + + it('throws WhiteChainError if no publicClient is provided', async () => { + const multicall = new Multicall() + + await expect( + multicall.aggregate([{ target: dummyTarget, callData: '0x1234' as Hex }]) + ).rejects.toThrow(WhiteChainError) + }) + + it('returns empty array immediately if calls array is empty', async () => { + const multicall = new Multicall() + const results = await multicall.aggregate([]) + expect(results).toEqual([]) + }) +}) diff --git a/tests/wallet/NonceManager.test.ts b/tests/wallet/NonceManager.test.ts index b0a49192..337c6421 100644 --- a/tests/wallet/NonceManager.test.ts +++ b/tests/wallet/NonceManager.test.ts @@ -1,4 +1,163 @@ import { describe, it, expect, vi } from 'vitest' +import { + NonceManager, + createNonceManager, +} from '../../src/wallet/NonceManager.js' +import { WhiteChainError } from '../../src/types.js' +import type { Address } from 'viem' + +const dummyAddress = '0x1111111111111111111111111111111111111111' as Address + +describe('NonceManager', () => { + it('correctly assigns nonces N, N+1, N+2... for 10 concurrent async calls with initialNonce', async () => { + const nonceManager = new NonceManager({ + address: dummyAddress, + initialNonce: 10, + }) + + expect(nonceManager.isInitialized()).toBe(true) + expect(nonceManager.getCachedNonce()).toBe(10) + + // Call getNextNonce() 10 times concurrently + const noncePromises = Array.from({ length: 10 }, () => nonceManager.getNextNonce()) + const nonces = await Promise.all(noncePromises) + + // Should return nonces 10 to 19 strictly sequential and without collisions + expect(nonces).toEqual([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + expect(nonceManager.getCachedNonce()).toBe(20) + }) + + it('handles 10 concurrent sendTransaction calls with predicted nonces without collision', async () => { + const nonceManager = createNonceManager({ + address: dummyAddress, + initialNonce: 100, + }) + + const assignedNonces: number[] = [] + + const sendFn = (nonce: number) => { + assignedNonces.push(nonce) + return Promise.resolve(`0xhash_${nonce}`) + } + + // 10 asynchronous sendTransaction calls launched in parallel + const txPromises = Array.from({ length: 10 }, () => nonceManager.sendTransaction(sendFn)) + const hashes = await Promise.all(txPromises) + + expect(hashes.length).toBe(10) + expect(assignedNonces).toEqual([100, 101, 102, 103, 104, 105, 106, 107, 108, 109]) + }) + + it('queues concurrent calls during initial RPC fetch and assigns sequential nonces', async () => { + let rpcCallCount = 0 + + // Simulate async RPC call delay + const getOnChainNonce = vi.fn().mockImplementation(async () => { + rpcCallCount++ + await new Promise((resolve) => setTimeout(resolve, 50)) + return 5 + }) + + const nonceManager = new NonceManager({ + address: dummyAddress, + getOnChainNonce, + }) + + expect(nonceManager.isInitialized()).toBe(false) + expect(nonceManager.getCachedNonce()).toBe(null) + + // Launch 10 concurrent calls while uninitialized + const noncePromises = Array.from({ length: 10 }, () => nonceManager.getNextNonce()) + const nonces = await Promise.all(noncePromises) + + // Only 1 RPC request should have been triggered + expect(rpcCallCount).toBe(1) + + // All 10 callers should receive nonces 5 to 14 + expect(nonces).toEqual([5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) + expect(nonceManager.getCachedNonce()).toBe(15) + }) + + it('fetches on-chain nonce via publicClient if provided', async () => { + const getTransactionCount = vi.fn().mockResolvedValue(42) + const publicClient = { getTransactionCount } as any + + const nonceManager = new NonceManager({ + address: dummyAddress, + publicClient, + }) + + const nonce1 = await nonceManager.getNextNonce() + const nonce2 = await nonceManager.getNextNonce() + + expect(nonce1).toBe(42) + expect(nonce2).toBe(43) + expect(getTransactionCount).toHaveBeenCalledTimes(1) + expect(getTransactionCount).toHaveBeenCalledWith({ address: dummyAddress, blockTag: 'pending' }) + }) + + it('supports reset() to fall back to RPC on dropped transactions', async () => { + let rpcNonce = 20 + const getOnChainNonce = vi.fn().mockImplementation(async () => rpcNonce) + + const nonceManager = new NonceManager({ + address: dummyAddress, + getOnChainNonce, + }) + + const n1 = await nonceManager.getNextNonce() + const n2 = await nonceManager.getNextNonce() + expect(n1).toBe(20) + expect(n2).toBe(21) + expect(getOnChainNonce).toHaveBeenCalledTimes(1) + + // Simulate dropped tx: reset local state and update RPC on-chain nonce + nonceManager.reset() + expect(nonceManager.isInitialized()).toBe(false) + expect(nonceManager.getCachedNonce()).toBe(null) + + rpcNonce = 20 // on-chain nonce remained 20 because tx dropped + const n3 = await nonceManager.getNextNonce() + expect(n3).toBe(20) + expect(getOnChainNonce).toHaveBeenCalledTimes(2) + }) + + it('supports setNonce to manually override next nonce', () => { + const nonceManager = new NonceManager({ + address: dummyAddress, + initialNonce: 0, + }) + + nonceManager.setNonce(50) + expect(nonceManager.getCachedNonce()).toBe(50) + }) + + it('supports getNextNonceBigInt', async () => { + const nonceManager = new NonceManager({ + address: dummyAddress, + initialNonce: 7, + }) + + const bigintNonce = await nonceManager.getNextNonceBigInt() + expect(bigintNonce).toBe(7n) + expect(typeof bigintNonce).toBe('bigint') + }) + + it('throws WhiteChainError if configured without publicClient or getOnChainNonce when fetching', async () => { + const nonceManager = new NonceManager({ + address: dummyAddress, + }) + + await expect(nonceManager.getNextNonce()).rejects.toThrow(WhiteChainError) + }) + + it('throws WhiteChainError for invalid initial or manually set nonces', () => { + expect(() => new NonceManager({ address: dummyAddress, initialNonce: -1 })).toThrow(WhiteChainError) + expect(() => new NonceManager({ address: dummyAddress, initialNonce: 1.5 })).toThrow(WhiteChainError) + + const nm = new NonceManager({ address: dummyAddress, initialNonce: 0 }) + expect(() => nm.setNonce(-5)).toThrow(WhiteChainError) + expect(() => nm.setNonce(3.14)).toThrow(WhiteChainError) import { NonceManager } from '../../src/wallet/NonceManager.js' describe('NonceManager', () => { diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index f7a2e292..4a1b08dd 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -1,4 +1,15 @@ { + "extends": "./tsconfig.json", + "compilerOptions": { + "target": "ES2021", + "module": "CommonJS", + "moduleResolution": "node", + "lib": ["ES2022", "DOM"], + "outDir": "dist/cjs", + "skipLibCheck": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] "compilerOptions": { "target": "ES2020", "module": "NodeNext", diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 7d2b0a60..76a9672e 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -4,6 +4,9 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist/esm", + "lib": ["ES2022", "DOM"], + "skipLibCheck": true, + "types": ["node"] "skipLibCheck": true }, "include": ["src"], diff --git a/tsconfig.json b/tsconfig.json index 99eb1614..12601ffd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", - "lib": ["ES2021", "DOM"], "lib": ["ES2022", "DOM"], "declaration": true, "outDir": "dist", @@ -11,7 +10,8 @@ "esModuleInterop": true, "skipLibCheck": true, "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "types": ["node"] }, "include": ["src", "tests", "examples"], "exclude": ["node_modules", "dist"]