diff --git a/package.json b/package.json index d256d69a..04eea2a7 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "_dist-stats": "gzip -k9f -S '.gz' ./dist/quais.min.js && gzip -k9f -S '.gz' ./dist/quais.umd.min.js && gzip -k9f -S '.gz' ./dist/wordlists-extra.min.js && du -hs ./dist/*.gz && echo '' && du -hs ./dist/*.js", "auto-build": "npm run build -- -w", "lint": "eslint src/**/*.ts --fix", - "build": "tsc --project tsconfig.esm.json", + "build": "tsc --project tsconfig.esm.json && cp ./misc/basedirs/lib.esm/package.json ./lib/esm", "build-all": "npm run build && npm run build-commonjs", "build-clean": "npm run clean && npm run build && npm run build-all && npm run _build-dist && npm run _dist-stats", "build-commonjs": "tsc --project tsconfig.commonjs.json", diff --git a/src/_tests/test-providers-data.ts b/src/_tests/test-providers-data.ts index fee4f365..467eff56 100644 --- a/src/_tests/test-providers-data.ts +++ b/src/_tests/test-providers-data.ts @@ -8,8 +8,9 @@ import assert from 'assert'; //import type { Provider } from "../index.js"; import { getTxType, quais } from '../index.js'; import axios from 'axios'; -import { stall } from "./utils.js"; -import dotenv from "dotenv"; +import { stall } from './utils.js'; +import dotenv from 'dotenv'; +import { Shard } from '../constants/index.js'; dotenv.config(); // import { // networkFeatureAtBlock, networkNames, @@ -20,13 +21,12 @@ dotenv.config(); //setupProviders(); - const providerC1 = new quais.JsonRpcProvider(process.env.CYPRUS1URL); const privateKey = process.env.CYPRUS1PK; -console.log(privateKey) +console.log(privateKey); const wallet = new quais.Wallet(process.env.CYPRUS1PK || '', providerC1); -const destinationC1 = '0x0047f9CEa7662C567188D58640ffC48901cde02a' -const destinationC2 = '0x011ae0a1Bd5B71b4F16F8FdD3AEF278C3D042449' +const destinationC1 = '0x0047f9CEa7662C567188D58640ffC48901cde02a'; +const destinationC2 = '0x011ae0a1Bd5B71b4F16F8FdD3AEF278C3D042449'; function equals(name: string, actual: any, expected: any): void { if (expected && expected.eq) { @@ -100,43 +100,43 @@ async function getRPCGasPrice(url: string | undefined) { async function sendTransaction(to: string) { let txResponse; let typeValue; - try{ - console.log("Nonce: ", await providerC1.getTransactionCount(wallet.address, 'latest'),) - do{ - typeValue = getTxType(wallet.address, to); - const gas = await getRPCGasPrice(process.env.CYPRUS1URL); - let tx: { - from: string; - to: string; - value: any; - gasPrice: any; - maxFeePerGas: any; - maxPriorityFeePerGas:any; - nonce: number; - data: string; - type: number; - gasLimit: number; - chainId: number; - etxGasLimit?: any; - etxGasTip?: any; - etxGasPrice?: any; - } = { - from: wallet.address, - to, - value: quais.parseEther("0.1"), // Sending 0.1 ether - gasPrice: gas*2, - maxFeePerGas: quais.parseUnits('20', 'gwei'), - maxPriorityFeePerGas: quais.parseUnits('20', 'gwei'), - nonce: await providerC1.getTransactionCount(wallet.address, 'latest'), - data: '', - type: typeValue, - gasLimit: typeValue == 0 ? 21000 : 42000, - chainId: Number(process.env.CHAIN_ID || 1337), - }; - txResponse = await wallet.sendTransaction(tx); - console.log(txResponse) - await stall(15000); - } while (txResponse.hash == null); + try { + console.log('Nonce: ', await providerC1.getTransactionCount(wallet.address, 'latest')); + do { + typeValue = getTxType(wallet.address, to); + const gas = await getRPCGasPrice(process.env.CYPRUS1URL); + const tx: { + from: string; + to: string; + value: any; + gasPrice: any; + maxFeePerGas: any; + maxPriorityFeePerGas: any; + nonce: number; + data: string; + type: number; + gasLimit: number; + chainId: number; + etxGasLimit?: any; + etxGasTip?: any; + etxGasPrice?: any; + } = { + from: wallet.address, + to, + value: quais.parseEther('0.1'), // Sending 0.1 ether + gasPrice: gas * 2, + maxFeePerGas: quais.parseUnits('20', 'gwei'), + maxPriorityFeePerGas: quais.parseUnits('20', 'gwei'), + nonce: await providerC1.getTransactionCount(wallet.address, 'latest'), + data: '', + type: typeValue, + gasLimit: typeValue == 0 ? 21000 : 42000, + chainId: Number(process.env.CHAIN_ID || 1337), + }; + txResponse = await wallet.sendTransaction(tx); + console.log(txResponse); + await stall(15000); + } while (txResponse.hash == null); console.log(`Transaction hash for type ${typeValue}: `, txResponse.hash); return txResponse; @@ -152,16 +152,13 @@ async function fetchRPCBlock(blockNumber: string | null) { try { let response; do { - response = await axios.post(process.env.CYPRUS1URL || "http://localhost:8610", { - jsonrpc: "2.0", - method: "quai_getBlockByNumber", - params: [ - blockNumber || '0xA', - false - ], - id: 1 - }); - }while (response?.data?.result?.woHeader?.headerHash == null) + response = await axios.post(process.env.CYPRUS1URL || 'http://localhost:8610', { + jsonrpc: '2.0', + method: 'quai_getBlockByNumber', + params: [blockNumber || '0xA', false], + id: 1, + }); + } while (response?.data?.result?.woHeader?.headerHash == null); return response.data.result; } catch (error: any) { throw error; @@ -242,7 +239,7 @@ describe('Test Provider Block operations', function () { }); it('should fetch block by number', async function () { - const responseBlock = (await providerC1.getBlock('0,0', '0xA')) as quais.Block; + const responseBlock = (await providerC1.getBlock(Shard.Cyprus1, '0xA')) as quais.Block; assert.ok(responseBlock != null, 'block != null'); // TODO: `provider` is not used, remove? // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -256,7 +253,7 @@ describe('Test Provider Block operations', function () { it('should fetch block by hash', async function () { assert.ok(block.hash != null, 'block.hash != null'); - const responseBlock = (await providerC1.getBlock('0,0', block.hash)) as quais.Block; + const responseBlock = (await providerC1.getBlock(Shard.Paxos2, block.hash)) as quais.Block; assert.ok(responseBlock != null, 'block != null'); // TODO: `provider` is not used, remove? // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -274,8 +271,8 @@ describe('Test Transaction operations', function () { let internalToExternalTx: any; it('should fetch balance after internal tx', async function () { - this.timeout(60000) - const oldBal = await fetchRPCBalance(destinationC1, process.env.CYPRUS1URL || "http://localhost:8610"); + this.timeout(60000); + const oldBal = await fetchRPCBalance(destinationC1, process.env.CYPRUS1URL || 'http://localhost:8610'); internalTx = await sendTransaction(destinationC1); await stall(30000); const expectedBal = BigInt(internalTx.value); @@ -283,11 +280,13 @@ describe('Test Transaction operations', function () { const actualBal = Number(balance) - Number(oldBal); const tolerance = 1e-6; // Define a small tolerance level - const withinTolerance = Math.abs((actualBal - Number(expectedBal)) * 100 / Number(expectedBal)) <= tolerance; - assert(withinTolerance, `Actual balance ${actualBal} is not within the acceptable range of expected balance ${Number(expectedBal)}`); + const withinTolerance = Math.abs(((actualBal - Number(expectedBal)) * 100) / Number(expectedBal)) <= tolerance; + assert( + withinTolerance, + `Actual balance ${actualBal} is not within the acceptable range of expected balance ${Number(expectedBal)}`, + ); - - const receipt = await fetchRPCTxReceipt(internalTx.hash, process.env.CYPRUS1URL || "http://localhost:8610"); + const receipt = await fetchRPCTxReceipt(internalTx.hash, process.env.CYPRUS1URL || 'http://localhost:8610'); const expectedReceipt = { blockHash: receipt.blockHash, contractAddress: receipt.contractAddress || null, @@ -312,16 +311,18 @@ describe('Test Transaction operations', function () { ...receiptResponse, logs: receiptResponse?.logs, }; - console.log(receiptResult.blockHash) - equals("Internal Tx Receipt", receiptResult, expectedReceipt); - + console.log(receiptResult.blockHash); + equals('Internal Tx Receipt', receiptResult, expectedReceipt); }); it('should fetch transaction receipt for internal to external tx', async function () { this.timeout(120000); internalToExternalTx = await sendTransaction(destinationC2); await stall(60000); - const receipt = await fetchRPCTxReceipt(internalToExternalTx.hash, process.env.CYPRUS1URL || "http://localhost:8610"); + const receipt = await fetchRPCTxReceipt( + internalToExternalTx.hash, + process.env.CYPRUS1URL || 'http://localhost:8610', + ); await stall(30000); const etx = receipt.etxs[0]; const expectedReceipt = { diff --git a/src/constants/index.ts b/src/constants/index.ts index dc26531b..438634af 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -4,15 +4,10 @@ * @_section: api/constants: Constants [about-constants] */ -export { ZeroAddress } from "./addresses.js"; -export { ZeroHash } from "./hashes.js"; -export { - N, - WeiPerEther, - MaxUint256, - MinInt256, - MaxInt256 -} from "./numbers.js"; -export { quaisymbol, MessagePrefix } from "./strings.js"; -export { ShardData } from "./shards.js"; -export { QI_COIN_TYPE, QUAI_COIN_TYPE } from "./coins.js"; +export { ZeroAddress } from './addresses.js'; +export { ZeroHash } from './hashes.js'; +export { N, WeiPerEther, MaxUint256, MinInt256, MaxInt256 } from './numbers.js'; +export { quaisymbol, MessagePrefix } from './strings.js'; +export { toShard, Shard, ShardData } from './shards.js'; +export { toZone, Zone, ZoneData } from './zones.js'; +export { QI_COIN_TYPE, QUAI_COIN_TYPE } from './coins.js'; diff --git a/src/constants/shards.ts b/src/constants/shards.ts index 63c84ec3..bc7da889 100644 --- a/src/constants/shards.ts +++ b/src/constants/shards.ts @@ -1,72 +1,72 @@ +import { ZoneData } from './zones.js'; + +export enum Shard { + Cyprus = '0x0', + Cyprus1 = '0x00', + Cyprus2 = '0x01', + Cyprus3 = '0x02', + Paxos = '0x1', + Paxos1 = '0x10', + Paxos2 = '0x11', + Paxos3 = '0x12', + Hydra = '0x2', + Hydra1 = '0x20', + Hydra2 = '0x21', + Hydra3 = '0x22', + Prime = '0x', +} + +function shardFromBytes(shard: string): Shard { + switch (shard) { + case '0x00': + return Shard.Cyprus1; + case '0x01': + return Shard.Cyprus2; + case '0x02': + return Shard.Cyprus3; + case '0x10': + return Shard.Paxos1; + case '0x11': + return Shard.Paxos2; + case '0x12': + return Shard.Paxos3; + case '0x20': + return Shard.Hydra1; + case '0x21': + return Shard.Hydra2; + case '0x22': + return Shard.Hydra3; + default: + throw new Error('Invalid shard'); + } +} /** * Constant data that defines each shard within the network. * * @category Constants */ - export const ShardData = [ + ...ZoneData, { - name: 'Cyprus One', - nickname: 'cyprus1', - shard: 'zone-0-0', - context: 2, - byte: '0x00', //0000 0000 region-0 zone-0 - }, - { - name: 'Cyprus Two', - nickname: 'cyprus2', - shard: 'zone-0-1', - context: 2, - byte: '0x01', // 0000 0001 region-0 zone-1 - }, - { - name: 'Cyprus Three', - nickname: 'cyprus3', - shard: 'zone-0-2', - context: 2, - byte: '0x02', // 0000 0010 region-0 zone-2 - }, - { - name: 'Paxos One', - nickname: 'paxos1', - shard: 'zone-1-0', - context: 2, - byte: '0x10', // 0001 0000 region-1 zone-0 - }, - { - name: 'Paxos Two', - nickname: 'paxos2', - shard: 'zone-1-1', + name: 'Cyprus', + nickname: 'cyprus', + shard: 'region-0', context: 2, - byte: '0x11', // 0001 0001 region-1 zone-1 + byte: '0x0', }, { - name: 'Paxos Three', - nickname: 'paxos3', - shard: 'zone-1-2', + name: 'Paxos', + nickname: 'paxos', + shard: 'region-1', context: 2, - byte: '0x12', // 0001 0010 region-1 zone-2 + byte: '0x1', }, { - name: 'Hydra One', - nickname: 'hydra1', - shard: 'zone-2-0', + name: 'Hydra', + nickname: 'hydra', + shard: 'region-2', context: 2, - byte: '0x20', // 0010 0000 region-2 zone-0 - }, - { - name: 'Hydra Two', - nickname: 'hydra2', - shard: 'zone-2-1', - context: 2, - byte: '0x21', // 0010 0001 region-2 zone-1 - }, - { - name: 'Hydra Three', - nickname: 'hydra3', - shard: 'zone-2-2', - context: 2, - byte: '0x22', // 0010 0010 region-2 zone-2 + byte: '0x2', }, { name: 'Prime', @@ -76,3 +76,10 @@ export const ShardData = [ byte: '0x', }, ]; + +export function toShard(shard: string): Shard { + return shardFromBytes( + ShardData.find((it) => it.name == shard || it.byte == shard || it.nickname == shard || it.shard == shard) + ?.byte || '', + ); +} diff --git a/src/constants/zones.ts b/src/constants/zones.ts new file mode 100644 index 00000000..0599c62e --- /dev/null +++ b/src/constants/zones.ts @@ -0,0 +1,109 @@ +export enum Zone { + Cyprus1 = '0x00', + Cyprus2 = '0x01', + Cyprus3 = '0x02', + Paxos1 = '0x10', + Paxos2 = '0x11', + Paxos3 = '0x12', + Hydra1 = '0x20', + Hydra2 = '0x21', + Hydra3 = '0x22', +} + +function zoneFromBytes(zone: string): Zone { + switch (zone) { + case '0x00': + return Zone.Cyprus1; + case '0x01': + return Zone.Cyprus2; + case '0x02': + return Zone.Cyprus3; + case '0x10': + return Zone.Paxos1; + case '0x11': + return Zone.Paxos2; + case '0x12': + return Zone.Paxos3; + case '0x20': + return Zone.Hydra1; + case '0x21': + return Zone.Hydra2; + case '0x22': + return Zone.Hydra3; + default: + throw new Error('Invalid zone'); + } +} + +export const ZoneData = [ + { + name: 'Cyprus One', + nickname: 'cyprus1', + shard: 'zone-0-0', + context: 2, + byte: '0x00', //0000 0000 region-0 zone-0 + }, + { + name: 'Cyprus Two', + nickname: 'cyprus2', + shard: 'zone-0-1', + context: 2, + byte: '0x01', // 0000 0001 region-0 zone-1 + }, + { + name: 'Cyprus Three', + nickname: 'cyprus3', + shard: 'zone-0-2', + context: 2, + byte: '0x02', // 0000 0010 region-0 zone-2 + }, + { + name: 'Paxos One', + nickname: 'paxos1', + shard: 'zone-1-0', + context: 2, + byte: '0x10', // 0001 0000 region-1 zone-0 + }, + { + name: 'Paxos Two', + nickname: 'paxos2', + shard: 'zone-1-1', + context: 2, + byte: '0x11', // 0001 0001 region-1 zone-1 + }, + { + name: 'Paxos Three', + nickname: 'paxos3', + shard: 'zone-1-2', + context: 2, + byte: '0x12', // 0001 0010 region-1 zone-2 + }, + { + name: 'Hydra One', + nickname: 'hydra1', + shard: 'zone-2-0', + context: 2, + byte: '0x20', // 0010 0000 region-2 zone-0 + }, + { + name: 'Hydra Two', + nickname: 'hydra2', + shard: 'zone-2-1', + context: 2, + byte: '0x21', // 0010 0001 region-2 zone-1 + }, + { + name: 'Hydra Three', + nickname: 'hydra3', + shard: 'zone-2-2', + context: 2, + byte: '0x22', // 0010 0010 region-2 zone-2 + }, +]; + +export function toZone(shard: string): Zone { + return zoneFromBytes( + ZoneData.find((it) => it.name == shard || it.byte == shard || it.nickname == shard || it.shard == shard) + ?.byte || '', + ); +} diff --git a/src/contract/contract.ts b/src/contract/contract.ts index fea12455..2c1c557a 100644 --- a/src/contract/contract.ts +++ b/src/contract/contract.ts @@ -46,6 +46,7 @@ import type { DeferredTopicFilter, WrappedFallback, } from './types.js'; +import { toShard, Zone } from '../constants/index.js'; const BN_0 = BigInt(0); @@ -1032,7 +1033,7 @@ export class BaseContract implements Addressable, EventEmitterable = Array, I = BaseContract } const sender = String(tx.from); - const toShard = getShardForAddress(sender); + const toShard = getZoneForAddress(sender); let i = 0; const startingData = tx.data; while (i < 10000) { - var contractAddress = getContractAddress(sender, BigInt(tx.nonce || 0), tx.data || ''); - var contractShard = getShardForAddress(contractAddress); - console.log("contractAddress ", contractAddress); - var utxo = isQiAddress(contractAddress); + const contractAddress = getContractAddress(sender, BigInt(tx.nonce || 0), tx.data || ''); + const contractShard = getZoneForAddress(contractAddress); + console.log('contractAddress ', contractAddress); + const utxo = isQiAddress(contractAddress); if (contractShard === toShard && !utxo) { return tx; } diff --git a/src/contract/wrappers.ts b/src/contract/wrappers.ts index e28d41ba..bc5b793f 100644 --- a/src/contract/wrappers.ts +++ b/src/contract/wrappers.ts @@ -9,6 +9,7 @@ import type { Provider } from '../providers/index.js'; import type { BaseContract } from './contract.js'; import type { ContractEventName } from './types.js'; +import { Shard } from '../constants/index.js'; /** * An **EventLog** contains additional properties parsed from the {@link Log | **Log**}. @@ -170,11 +171,11 @@ export class ContractUnknownEventPayload extends EventPayload /** * Resolves to the block the event occured in. * - * @param {string} shard - The shard to get the block from. + * @param {Shard} shard - The shard to get the block from. * * @returns {Promise} A promise resolving to the block the event occured in. */ - async getBlock(shard: string): Promise { + async getBlock(shard: Shard): Promise { return await this.log.getBlock(shard); } diff --git a/src/providers/abstract-provider.ts b/src/providers/abstract-provider.ts index 831d92b9..cf0becc8 100644 --- a/src/providers/abstract-provider.ts +++ b/src/providers/abstract-provider.ts @@ -8,14 +8,14 @@ // @TODO // Event coalescence -// When we register an event with an async value (e.g. address is a Signer), +// When we register an event with an async value (e.g. address is a Signer), // we need to add it immeidately for the Event API, but also // need time to resolve the address. Upon resolving the address, we need to // migrate the listener to the static event. We also need to maintain a map // of Signer to address so we can sync respond to listenerCount. import { getAddress, resolveAddress } from '../address/index.js'; -import { ShardData } from '../constants/index.js'; +import { Shard, toShard, toZone, Zone } from '../constants/index.js'; import { TxInput, TxOutput } from '../transaction/index.js'; import { Outpoint } from '../transaction/utxo.js'; import { @@ -74,11 +74,11 @@ import type { Provider, ProviderEvent, TransactionRequest, -} from "./provider.js"; -import { WorkObjectLike } from "../transaction/work-object.js"; -import {QiTransaction, QuaiTransaction} from "../transaction/index.js"; -import {QuaiTransactionResponseParams} from "./formatting.js"; -import {keccak256, SigningKey} from "../crypto/index.js"; +} from './provider.js'; +import { WorkObjectLike } from '../transaction/work-object.js'; +import { QiTransaction, QuaiTransaction } from '../transaction/index.js'; +import { QuaiTransactionResponseParams } from './formatting.js'; +import { keccak256, SigningKey } from '../crypto/index.js'; type Timer = ReturnType; @@ -363,13 +363,13 @@ export type PerformActionFilter = topics?: Array>; fromBlock?: BlockTag; toBlock?: BlockTag; - shard: string; + shard: Shard; } | { address?: string | Array; topics?: Array>; blockHash?: string; - shard: string; + shard: Shard; }; /** @@ -425,117 +425,117 @@ export type PerformActionRequest = | { method: 'broadcastTransaction'; signedTransaction: string; - shard: string; + zone: Zone; } | { method: 'call'; transaction: PerformActionTransaction; blockTag: BlockTag; - shard?: string; + zone?: Zone; } | { method: 'chainId'; - shard?: string; + zone?: Zone; } | { method: 'estimateGas'; transaction: PerformActionTransaction; - shard?: string; + zone?: Zone; } | { method: 'getBalance'; address: string; blockTag: BlockTag; - shard: string; + zone: Zone; } | { method: 'getOutpointsByAddress'; address: string; - shard: string; + zone: Zone; } | { method: 'getBlock'; blockTag: BlockTag; includeTransactions: boolean; - shard: string; + shard: Shard; } | { method: 'getBlock'; blockHash: string; includeTransactions: boolean; - shard: string; + shard: Shard; } | { method: 'getBlockNumber'; - shard?: string; + shard?: Shard; } | { method: 'getCode'; address: string; blockTag: BlockTag; - shard: string; + zone: Zone; } | { method: 'getGasPrice'; txType: boolean; - shard?: string; + zone?: Zone; } | { method: 'getLogs'; filter: PerformActionFilter; - shard: string; + zone: Zone; } | { method: 'getMaxPriorityFeePerGas'; - shard?: string; + zone?: Zone; } | { method: 'getStorage'; address: string; position: bigint; blockTag: BlockTag; - shard: string; + zone: Zone; } | { method: 'getTransaction'; hash: string; - shard: string; + zone: Zone; } | { method: 'getTransactionCount'; address: string; blockTag: BlockTag; - shard: string; + zone: Zone; } | { method: 'getTransactionReceipt'; hash: string; - shard: string; + zone: Zone; } | { method: 'getTransactionResult'; hash: string; - shard: string; + zone: Zone; } | { method: 'getRunningLocations'; - shard?: string; + shard?: Shard; } | { method: 'getProtocolTrieExpansionCount'; - shard: string; + shard: Shard; } | { method: 'getQiRateAtBlock'; blockTag: BlockTag; amt: number; - shard: string; + zone: Zone; } | { method: 'getQuaiRateAtBlock'; blockTag: BlockTag; amt: number; - shard: string; + zone: Zone; } | { method: 'getProtocolExpansionNumber'; @@ -581,7 +581,7 @@ const defaultOptions = { * @category Providers */ export class AbstractProvider implements Provider { - _urlMap: Map; + _urlMap: Map; #connect: FetchRequest[]; #subs: Map; #plugins: Map; @@ -649,13 +649,13 @@ export class AbstractProvider implements Provider { async initUrlMap(urls: U): Promise { if (urls instanceof FetchRequest) { urls.url = urls.url.split(':')[0] + ':' + urls.url.split(':')[1] + ':9001'; - this._urlMap.set('0x', urls as C); + this._urlMap.set(Shard.Prime, urls as C); this.#connect.push(urls); const shards = await this.getRunningLocations(); shards.forEach((shard) => { const port = 9200 + 20 * shard[0] + shard[1]; this._urlMap.set( - `0x${shard[0].toString(16)}${shard[1].toString(16)}`, + toShard(`0x${shard[0].toString(16)}${shard[1].toString(16)}`), new FetchRequest(urls.url.split(':')[0] + ':' + urls.url.split(':')[1] + ':' + port) as C, ); }); @@ -665,13 +665,13 @@ export class AbstractProvider implements Provider { for (const url of urls) { const primeUrl = url.split(':')[0] + ':' + url.split(':')[1] + ':9001'; const primeConnect = new FetchRequest(primeUrl); - this._urlMap.set('0x', primeConnect as C); + this._urlMap.set(Shard.Prime, primeConnect as C); this.#connect.push(primeConnect); const shards = await this.getRunningLocations(); shards.forEach((shard) => { const port = 9200 + 20 * shard[0] + shard[1]; this._urlMap.set( - `0x${shard[0].toString(16)}${shard[1].toString(16)}`, + toShard(`0x${shard[0].toString(16)}${shard[1].toString(16)}`), new FetchRequest(url.split(':')[0] + ':' + url.split(':')[1] + ':' + port) as C, ); }); @@ -679,33 +679,26 @@ export class AbstractProvider implements Provider { } } - shardBytes(shard: string): string { - return ( - ShardData.find((it) => it.name == shard || it.byte == shard || it.nickname == shard || it.shard == shard) - ?.byte || '' - ); - } - get connect(): FetchRequest[] { return this.#connect; } - async shardFromAddress(_address: AddressLike): Promise { + async zoneFromAddress(_address: AddressLike): Promise { const address: string | Promise = this._getAddress(_address); - return (await address).slice(0, 4); + return toZone((await address).slice(0, 4)); } - shardFromHash(hash: string): string { - return hash.slice(0, 4); + shardFromHash(hash: string): Shard { + return toShard(hash.slice(0, 4)); } - async getLatestQuaiRate(shard: string, amt: number = 1): Promise { - const blockNumber = await this.getBlockNumber(shard); - return this.getQuaiRateAtBlock(shard, blockNumber, amt); + async getLatestQuaiRate(zone: Zone, amt: number = 1): Promise { + const blockNumber = await this.getBlockNumber(toShard(zone)); + return this.getQuaiRateAtBlock(zone, blockNumber, amt); } - async getQuaiRateAtBlock(shard: string, blockTag: BlockTag, amt: number = 1): Promise { - let resolvedBlockTag = this._getBlockTag(shard, blockTag); + async getQuaiRateAtBlock(zone: Zone, blockTag: BlockTag, amt: number = 1): Promise { + let resolvedBlockTag = this._getBlockTag(toShard(zone), blockTag); if (typeof resolvedBlockTag !== 'string') { resolvedBlockTag = await resolvedBlockTag; } @@ -714,7 +707,7 @@ export class AbstractProvider implements Provider { method: 'getQuaiRateAtBlock', blockTag: resolvedBlockTag, amt, - shard: shard, + zone: zone, }); } @@ -724,13 +717,13 @@ export class AbstractProvider implements Provider { }); } - async getLatestQiRate(shard: string, amt: number = 1): Promise { - const blockNumber = await this.getBlockNumber(shard); - return this.getQiRateAtBlock(shard, blockNumber, amt); + async getLatestQiRate(zone: Zone, amt: number = 1): Promise { + const blockNumber = await this.getBlockNumber(toShard(zone)); + return this.getQiRateAtBlock(zone, blockNumber, amt); } - async getQiRateAtBlock(shard: string, blockTag: BlockTag, amt: number = 1): Promise { - let resolvedBlockTag = this._getBlockTag(shard, blockTag); + async getQiRateAtBlock(zone: Zone, blockTag: BlockTag, amt: number = 1): Promise { + let resolvedBlockTag = this._getBlockTag(toShard(zone), blockTag); if (typeof resolvedBlockTag !== 'string') { resolvedBlockTag = await resolvedBlockTag; } @@ -739,7 +732,7 @@ export class AbstractProvider implements Provider { method: 'getQiRateAtBlock', blockTag: resolvedBlockTag, amt, - shard: shard, + zone: zone, }); } @@ -886,12 +879,12 @@ export class AbstractProvider implements Provider { * * Sub-classes **must** override this. * - * @param {string} [shard] - The shard to use for the network detection. + * @param {Shard} [shard] - The shard to use for the network detection. * * @returns {Promise} A promise resolving to the network. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - _detectNetwork(shard?: string): Promise { + _detectNetwork(shard?: Shard): Promise { assert(false, 'sub-classes must implement this', 'UNSUPPORTED_OPERATION', { operation: '_detectNetwork', }); @@ -916,7 +909,7 @@ export class AbstractProvider implements Provider { // State - async getBlockNumber(shard?: string): Promise { + async getBlockNumber(shard?: Shard): Promise { const blockNumber = getNumber(await this.#perform({ method: 'getBlockNumber', shard: shard }), '%response'); if (this.#lastBlockNumber >= 0) { this.#lastBlockNumber = blockNumber; @@ -925,8 +918,8 @@ export class AbstractProvider implements Provider { } /** - * Returns or resolves to the address for `address`, resolving {@link Addressable | **Addressable**} - * objects and returning if already an address. + * Returns or resolves to the address for `address`, resolving {@link Addressable | **Addressable**} objects and + * returning if already an address. * * @param {AddressLike} address - The address to normalize. * @@ -940,12 +933,12 @@ export class AbstractProvider implements Provider { * Returns or resolves to a valid block tag for `blockTag`, resolving negative values and returning if already a * valid block tag. * - * @param {string} [shard] - The shard to use for the block tag. + * @param {Shard} [shard] - The shard to use for the block tag. * @param {BlockTag} [blockTag] - The block tag to normalize. * * @returns {string | Promise} A promise that resolves to a valid block tag. */ - _getBlockTag(shard?: string, blockTag?: BlockTag): string | Promise { + _getBlockTag(shard?: Shard, blockTag?: BlockTag): string | Promise { if (blockTag == null) { return 'latest'; } @@ -985,8 +978,8 @@ export class AbstractProvider implements Provider { } /** - * Returns or resolves to a filter for `filter`, resolving any {@link Addressable | **Addressable**} - * object and returning if already a valid filter. + * Returns or resolves to a filter for `filter`, resolving any {@link Addressable | **Addressable**} object and + * returning if already a valid filter. * * @param {Filter | FilterByBlockHash} filter - The filter to normalize. * @@ -1006,7 +999,7 @@ export class AbstractProvider implements Provider { const blockHash = 'blockHash' in filter ? filter.blockHash : undefined; - const resolve = (_address: Array, fromBlock?: string, toBlock?: string, shard?: string) => { + const resolve = (_address: Array, fromBlock?: string, toBlock?: string, shard?: Shard) => { let address: undefined | string | Array = undefined; switch (_address.length) { case 0: @@ -1086,8 +1079,8 @@ export class AbstractProvider implements Provider { } /** - * Returns or resovles to a transaction for `request`, resolving any - * {@link Addressable | **Addressable**} and returning if already a valid transaction. + * Returns or resovles to a transaction for `request`, resolving any {@link Addressable | **Addressable**} and + * returning if already a valid transaction. * * @param {PerformActionTransaction} _request - The transaction to normalize. * @@ -1136,7 +1129,10 @@ export class AbstractProvider implements Provider { }); if (request.blockTag != null) { - const blockTag = this._getBlockTag(request.chainId?.toString(), request.blockTag); + const blockTag = this._getBlockTag( + request.chainId ? toShard(request.chainId.toString()) : undefined, + request.blockTag, + ); if (isPromise(blockTag)) { promises.push( (async function () { @@ -1158,7 +1154,7 @@ export class AbstractProvider implements Provider { return request; } - async getNetwork(shard: string = 'prime'): Promise { + async getNetwork(shard: Shard = Shard.Prime): Promise { // No explicit network was set and this is our first time if (this.#networkPromise == null) { // Detect the current network (shared with all calls) @@ -1206,22 +1202,22 @@ export class AbstractProvider implements Provider { return expected.clone(); } - async getRunningLocations(shard?: string): Promise { + async getRunningLocations(shard?: Shard): Promise { return await this.#perform( shard ? { method: 'getRunningLocations', shard: shard } : { method: 'getRunningLocations' }, ); } - async getProtocolTrieExpansionCount(shard: string): Promise { + async getProtocolTrieExpansionCount(shard: Shard): Promise { return await this.#perform({ method: 'getProtocolTrieExpansionCount', shard: shard }); } - async getFeeData(shard?: string, txType: boolean = true): Promise { + async getFeeData(zone?: Zone, txType: boolean = true): Promise { const getFeeDataFunc = async () => { const { gasPrice, priorityFee } = await resolveProperties({ gasPrice: (async () => { try { - const value = await this.#perform({ method: 'getGasPrice', txType, shard: shard }); + const value = await this.#perform({ method: 'getGasPrice', txType, zone: zone }); return getBigInt(value, '%response'); } catch (error) { console.log(error); @@ -1231,7 +1227,7 @@ export class AbstractProvider implements Provider { priorityFee: (async () => { try { const value = txType - ? await this.#perform({ method: 'getMaxPriorityFeePerGas', shard: shard }) + ? await this.#perform({ method: 'getMaxPriorityFeePerGas', zone: zone }) : 0; return getBigInt(value, '%response'); // eslint-disable-next-line no-empty @@ -1263,12 +1259,12 @@ export class AbstractProvider implements Provider { if (isPromise(tx)) { tx = await tx; } - const shard = await this.shardFromAddress(addressFromTransactionRequest(tx)); + const zone = await this.zoneFromAddress(addressFromTransactionRequest(tx)); return getBigInt( await this.#perform({ method: 'estimateGas', transaction: tx, - shard: shard, + zone: zone, }), '%response', ); @@ -1276,15 +1272,15 @@ export class AbstractProvider implements Provider { // TODO: `attempt` is not used, remove or re-write // eslint-disable-next-line @typescript-eslint/no-unused-vars - async #call(tx: PerformActionTransaction, blockTag: string, attempt: number, shard?: string): Promise { + async #call(tx: PerformActionTransaction, blockTag: string, attempt: number, zone?: Zone): Promise { // This came in as a PerformActionTransaction, so to/from are safe; we can cast const transaction = copyRequest(tx); - return hexlify(await this._perform({ method: "call", transaction, blockTag, shard })); + return hexlify(await this._perform({ method: 'call', transaction, blockTag, zone })); } // TODO: `shard` is not used, remove or re-write // eslint-disable-next-line @typescript-eslint/no-unused-vars - async #checkNetwork(promise: Promise, shard?: string): Promise { + async #checkNetwork(promise: Promise, shard?: Shard): Promise { const { value } = await resolveProperties({ network: this.getNetwork(), value: promise, @@ -1293,19 +1289,21 @@ export class AbstractProvider implements Provider { } async call(_tx: QuaiTransactionRequest): Promise { - const shard = await this.shardFromAddress(addressFromTransactionRequest(_tx)); + const zone = await this.zoneFromAddress(addressFromTransactionRequest(_tx)); + const shard = toShard(zone); const { tx, blockTag } = await resolveProperties({ tx: this._getTransactionRequest(_tx), blockTag: this._getBlockTag(shard, _tx.blockTag), }); - return await this.#checkNetwork(this.#call(tx, blockTag, -1, shard), shard); + return await this.#checkNetwork(this.#call(tx, blockTag, -1, zone), shard); } // Account async #getAccountValue(request: _PerformAccountRequest, _address: AddressLike, _blockTag?: BlockTag): Promise { let address: string | Promise = this._getAddress(_address); - const shard = await this.shardFromAddress(_address); + const zone = await this.zoneFromAddress(_address); + const shard = toShard(zone); let blockTag: string | Promise = this._getBlockTag(shard, _blockTag); @@ -1314,7 +1312,7 @@ export class AbstractProvider implements Provider { } return await this.#checkNetwork( - this.#perform(Object.assign(request, { address, blockTag, shard: shard }) as PerformActionRequest), + this.#perform(Object.assign(request, { address, blockTag, zone: zone }) as PerformActionRequest), shard, ); } @@ -1348,14 +1346,14 @@ export class AbstractProvider implements Provider { } // Write - async broadcastTransaction(shard: string, signedTx: string): Promise { + async broadcastTransaction(zone: Zone, signedTx: string): Promise { const type = decodeProtoTransaction(getBytes(signedTx)).type; const { blockNumber, hash, network } = await resolveProperties({ - blockNumber: this.getBlockNumber(shard), + blockNumber: this.getBlockNumber(toShard(zone)), hash: this._perform({ method: 'broadcastTransaction', signedTransaction: signedTx, - shard: shard, + zone: zone, }), network: this.getNetwork(), }); @@ -1372,7 +1370,7 @@ export class AbstractProvider implements Provider { } } - async #getBlock(shard: string, block: BlockTag | string, includeTransactions: boolean): Promise { + async #getBlock(shard: Shard, block: BlockTag | string, includeTransactions: boolean): Promise { // @TODO: Add CustomBlockPlugin check if (isHexString(block, 32)) { return await this.#perform({ @@ -1397,7 +1395,7 @@ export class AbstractProvider implements Provider { } // Queries - async getBlock(shard: string, block: BlockTag | string, prefetchTxs?: boolean): Promise { + async getBlock(shard: Shard, block: BlockTag | string, prefetchTxs?: boolean): Promise { const { network, params } = await resolveProperties({ network: this.getNetwork(), params: this.#getBlock(shard, block, !!prefetchTxs), @@ -1409,10 +1407,10 @@ export class AbstractProvider implements Provider { } async getTransaction(hash: string): Promise { - const shard = this.shardFromHash(hash); + const zone = toZone(this.shardFromHash(hash)); const { network, params } = await resolveProperties({ network: this.getNetwork(), - params: this.#perform({ method: 'getTransaction', hash, shard: shard }), + params: this.#perform({ method: 'getTransaction', hash, zone: zone }), }); if (params == null) { return null; @@ -1422,10 +1420,10 @@ export class AbstractProvider implements Provider { } async getTransactionReceipt(hash: string): Promise { - const shard = this.shardFromHash(hash); + const zone = toZone(this.shardFromHash(hash)); const { network, params } = await resolveProperties({ network: this.getNetwork(), - params: this.#perform({ method: 'getTransactionReceipt', hash, shard: shard }), + params: this.#perform({ method: 'getTransactionReceipt', hash, zone: zone }), }); if (params == null) { return null; @@ -1433,7 +1431,7 @@ export class AbstractProvider implements Provider { // Some backends did not backfill the effectiveGasPrice in to old transactions // in the receipt, so we look it up manually and inject it. if (params.gasPrice == null && params.effectiveGasPrice == null) { - const tx = await this.#perform({ method: 'getTransaction', hash, shard: shard }); + const tx = await this.#perform({ method: 'getTransaction', hash, zone: zone }); if (tx == null) { throw new Error('report this; could not find tx or effectiveGasPrice'); } @@ -1444,10 +1442,10 @@ export class AbstractProvider implements Provider { } async getTransactionResult(hash: string): Promise { - const shard = this.shardFromHash(hash); + const zone = toZone(this.shardFromHash(hash)); const { result } = await resolveProperties({ network: this.getNetwork(), - result: this.#perform({ method: 'getTransactionResult', hash, shard: shard }), + result: this.#perform({ method: 'getTransactionResult', hash, zone: zone }), }); if (result == null) { return null; @@ -1461,11 +1459,11 @@ export class AbstractProvider implements Provider { if (isPromise(filter)) { filter = await filter; } - const shard = filter.shard; + const zone = toZone(filter.shard); const { network, params } = await resolveProperties({ network: this.getNetwork(), - params: this.#perform>({ method: 'getLogs', filter, shard: shard }), + params: this.#perform>({ method: 'getLogs', filter, zone: zone }), }); return params.map((p) => this._wrapLog(p, network)); @@ -1531,7 +1529,7 @@ export class AbstractProvider implements Provider { // TODO: not implemented yet // eslint-disable-next-line @typescript-eslint/no-unused-vars - async waitForBlock(shard: string, blockTag?: BlockTag): Promise { + async waitForBlock(shard: Shard, blockTag?: BlockTag): Promise { assert(false, 'not implemented yet', 'NOT_IMPLEMENTED', { operation: 'waitForBlock', }); diff --git a/src/providers/provider-jsonrpc.ts b/src/providers/provider-jsonrpc.ts index 9d99e2c2..60bbc318 100644 --- a/src/providers/provider-jsonrpc.ts +++ b/src/providers/provider-jsonrpc.ts @@ -14,26 +14,30 @@ // https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false -import { AbiCoder } from "../abi/index.js"; -import { accessListify } from "../transaction/index.js"; +import { AbiCoder } from '../abi/index.js'; +import { accessListify } from '../transaction/index.js'; import { - getBigInt, hexlify, isHexString, toQuantity, - makeError, assert, assertArgument, - FetchRequest -} from "../utils/index.js"; - -import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js"; -import { Network } from "./network.js"; -import { FilterIdEventSubscriber, FilterIdPendingSubscriber } from "./subscriber-filterid.js"; - -import type { TransactionLike } from "../transaction/index.js"; - -import type { PerformActionRequest, Subscriber, Subscription } from "./abstract-provider.js"; -import type { Networkish } from "./network.js"; -import type {TransactionRequest} from "./provider.js"; -import type { Signer } from "../signers/signer.js"; -import { UTXOEntry, UTXOTransactionOutput } from "../transaction/utxo.js"; - + getBigInt, + hexlify, + isHexString, + toQuantity, + makeError, + assert, + assertArgument, + FetchRequest, +} from '../utils/index.js'; + +import { AbstractProvider, UnmanagedSubscriber } from './abstract-provider.js'; +import { Network } from './network.js'; +import { FilterIdEventSubscriber, FilterIdPendingSubscriber } from './subscriber-filterid.js'; + +import type { TransactionLike } from '../transaction/index.js'; + +import type { PerformActionRequest, Subscriber, Subscription } from './abstract-provider.js'; +import type { Networkish } from './network.js'; +import type { TransactionRequest } from './provider.js'; +import { UTXOEntry, UTXOTransactionOutput } from '../transaction/utxo.js'; +import { Shard, toShard } from '../constants/index.js'; type Timer = ReturnType; @@ -257,7 +261,7 @@ export interface QuaiJsonRpcTransactionRequest extends AbstractJsonRpcTransactio type ResolveFunc = (result: JsonRpcResult) => void; type RejectFunc = (error: Error) => void; -type Payload = { payload: JsonRpcPayload; resolve: ResolveFunc; reject: RejectFunc; shard?: string }; +type Payload = { payload: JsonRpcPayload; resolve: ResolveFunc; reject: RejectFunc; shard?: Shard }; /** * The JsonRpcApiProvider is an abstract class and **MUST** be sub-classed. @@ -337,7 +341,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvi await Promise.all( Array.from(payloadMap).map(async ([key, value]) => { const payload = value.length === 1 ? value[0] : value; - const shard = key; + const shard = key ? toShard(key) : undefined; this.emit('debug', { action: 'sendRpcPayload', payload }); @@ -464,7 +468,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvi */ abstract _send( payload: JsonRpcPayload | Array, - shard?: string, + shard?: Shard, ): Promise>; /** @@ -484,7 +488,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvi if (tx && tx.type != null && getBigInt(tx.type)) { // If there are no EIP-1559 properties, it might be non-EIP-a559 if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) { - const feeData = await this.getFeeData(req.shard); + const feeData = await this.getFeeData(req.zone); if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) { // Network doesn't know about EIP-1559 (and hence type) req = Object.assign({}, req, { @@ -498,7 +502,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvi const request = this.getRpcRequest(req); if (request != null) { - const shard = 'shard' in req ? req.shard : undefined; + const shard = 'shard' in req ? req.shard : 'zone' in req ? toShard(req.zone!) : undefined; return await this.send(request.method, request.args, shard); } @@ -979,11 +983,11 @@ export abstract class JsonRpcApiProvider extends AbstractProvi * * @param {string} method - The method to call. * @param {any[] | Record} params - The parameters to pass to the method. - * @param {string} shard - The shard to send the request to. + * @param {Shard} shard - The shard to send the request to. * * @returns {Promise} A promise that resolves to the result of the method call. */ - send(method: string, params: Array | Record, shard?: string): Promise { + send(method: string, params: Array | Record, shard?: Shard): Promise { // @TODO: cache chainId?? purge on switch_networks // We have been destroyed; no operations are supported anymore @@ -1076,18 +1080,17 @@ export class JsonRpcProvider extends JsonRpcApiProvider { return subscriber; } - _getConnection(shard?: string): FetchRequest { + _getConnection(shard?: Shard): FetchRequest { let connection; - if (typeof shard === 'string') { - const shardBytes = this.shardBytes(shard); - connection = this._urlMap.get(shardBytes) ?? this.connect[this.connect.length - 1]!.clone(); + if (shard !== undefined) { + connection = this._urlMap.get(shard) ?? this.connect[this.connect.length - 1]!.clone(); } else { connection = this.connect[this.connect.length - 1]!.clone(); } return new FetchRequest(connection.url); } - async send(method: string, params: Array | Record, shard?: string): Promise { + async send(method: string, params: Array | Record, shard?: Shard): Promise { // All requests are over HTTP, so we can just start handling requests // We do this here rather than the constructor so that we don't send any // requests to the network (i.e. quai_chainId) until we absolutely have to. @@ -1096,7 +1099,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider { return await super.send(method, params, shard); } - async _send(payload: JsonRpcPayload | Array, shard?: string): Promise> { + async _send(payload: JsonRpcPayload | Array, shard?: Shard): Promise> { // Configure a POST connection for the requested method const request = this._getConnection(shard); request.body = JSON.stringify(payload); diff --git a/src/providers/provider-socket.ts b/src/providers/provider-socket.ts index 3588e29b..5c5b2702 100644 --- a/src/providers/provider-socket.ts +++ b/src/providers/provider-socket.ts @@ -19,6 +19,7 @@ import type { EventFilter } from './provider.js'; import type { JsonRpcApiProviderOptions, JsonRpcError, JsonRpcPayload, JsonRpcResult } from './provider-jsonrpc.js'; import type { Networkish } from './network.js'; import type { WebSocketLike } from './provider-websocket.js'; +import { Shard } from '../constants/index.js'; type JsonRpcSubscription = { method: string; @@ -275,7 +276,7 @@ export class SocketProvider extends JsonRpcApiProvider { async _send( payload: JsonRpcPayload | Array, - shard?: string, + shard?: Shard, ): Promise> { // WebSocket provider doesn't accept batches assertArgument(!Array.isArray(payload), 'WebSocket does not support batch send', 'payload', payload); @@ -346,7 +347,7 @@ export class SocketProvider extends JsonRpcApiProvider { * Sub-classes **must** override this to send `message` over their transport. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async _write(message: string, shard?: string): Promise { + async _write(message: string, shard?: Shard): Promise { throw new Error('sub-classes must override this'); } } diff --git a/src/providers/provider-websocket.ts b/src/providers/provider-websocket.ts index 05c19fcc..a9773e9f 100644 --- a/src/providers/provider-websocket.ts +++ b/src/providers/provider-websocket.ts @@ -4,6 +4,7 @@ import { SocketProvider } from './provider-socket.js'; import type { JsonRpcApiProviderOptions } from './provider-jsonrpc.js'; import type { Networkish } from './network.js'; +import { Shard, toShard } from '../constants/index.js'; /** * A generic interface to a Websocket-like object. @@ -44,7 +45,7 @@ export type WebSocketCreator = () => WebSocketLike; export class WebSocketProvider extends SocketProvider { #websockets: WebSocketLike[]; - readyMap: Map = new Map(); + readyMap: Map = new Map(); get websocket(): WebSocketLike[] { if (this.#websockets == null) { @@ -63,7 +64,7 @@ export class WebSocketProvider extends SocketProvider { this.initPromise = this.initUrlMap(typeof url === 'string' ? [url] : url); } - initWebSocket(websocket: WebSocketLike, shard: string): void { + initWebSocket(websocket: WebSocketLike, shard: Shard): void { websocket.onopen = async () => { try { await this._start(); @@ -80,7 +81,7 @@ export class WebSocketProvider extends SocketProvider { }; } - async waitShardReady(shard: string): Promise { + async waitShardReady(shard: Shard): Promise { let count = 0; while (!this.readyMap.get(shard)) { await new Promise((resolve) => setTimeout(resolve, Math.pow(2, count))); @@ -103,10 +104,10 @@ export class WebSocketProvider extends SocketProvider { const port = 8200 + 20 * shard[0] + shard[1]; const shardUrl = baseUrl.split(':').slice(0, 2).join(':'); const websocket = createWebSocket(shardUrl, port); - this.initWebSocket(websocket, `0x${shard[0].toString(16)}${shard[1].toString(16)}`); + this.initWebSocket(websocket, toShard(`0x${shard[0].toString(16)}${shard[1].toString(16)}`)); this.#websockets.push(websocket); - this._urlMap.set(`0x${shard[0].toString(16)}${shard[1].toString(16)}`, websocket); - await this.waitShardReady(`0x${shard[0].toString(16)}${shard[1].toString(16)}`); + this._urlMap.set(toShard(`0x${shard[0].toString(16)}${shard[1].toString(16)}`), websocket); + await this.waitShardReady(toShard(`0x${shard[0].toString(16)}${shard[1].toString(16)}`)); }), ); }; @@ -115,45 +116,44 @@ export class WebSocketProvider extends SocketProvider { for (const url of urls) { const baseUrl = `${url.split(':')[0]}:${url.split(':')[1]}`; const primeWebsocket = createWebSocket(baseUrl, 8001); - this.initWebSocket(primeWebsocket, '0x'); + this.initWebSocket(primeWebsocket, Shard.Prime); this.#websockets.push(primeWebsocket); - this._urlMap.set('0x', primeWebsocket); - await this.waitShardReady('0x'); + this._urlMap.set(Shard.Prime, primeWebsocket); + await this.waitShardReady(Shard.Prime); await initShardWebSockets(baseUrl); } } else if (typeof urls === 'function') { const primeWebsocket = urls(); - this.initWebSocket(primeWebsocket, '0x'); + this.initWebSocket(primeWebsocket, Shard.Prime); this.#websockets.push(primeWebsocket); - this._urlMap.set('0x', primeWebsocket); - await this.waitShardReady('0x'); + this._urlMap.set(Shard.Prime, primeWebsocket); + await this.waitShardReady(Shard.Prime); const baseUrl = this.#websockets[0].url.split(':').slice(0, 2).join(':'); await initShardWebSockets(baseUrl); } else { const primeWebsocket = urls as WebSocketLike; - this.initWebSocket(primeWebsocket, '0x'); + this.initWebSocket(primeWebsocket, Shard.Prime); this.#websockets.push(primeWebsocket); - this._urlMap.set('0x', primeWebsocket); - await this.waitShardReady('0x'); + this._urlMap.set(Shard.Prime, primeWebsocket); + await this.waitShardReady(Shard.Prime); const baseUrl = primeWebsocket.url.split(':').slice(0, 2).join(':'); await initShardWebSockets(baseUrl); } } - async _write(message: string, shard?: string): Promise { - const shardKey = shard ? this.shardBytes(shard) : undefined; + async _write(message: string, shard?: Shard): Promise { if (this.websocket.length < 1) { throw new Error('Websocket closed'); } - if (shardKey && !this._urlMap.has(shardKey)) { + if (shard && !this._urlMap.has(shard)) { throw new Error('Shard not found'); } - const websocket = shardKey ? this._urlMap.get(shardKey) : this.websocket[this.websocket.length - 1]; + const websocket = shard ? this._urlMap.get(shard) : this.websocket[this.websocket.length - 1]; if (!websocket) { throw new Error('Websocket is undefined'); } - if (shardKey) { - await this.waitShardReady(shardKey); + if (shard) { + await this.waitShardReady(shard); } websocket.send(message); } diff --git a/src/providers/provider.ts b/src/providers/provider.ts index fcd2e774..da53f679 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -22,6 +22,7 @@ import type { ContractRunner } from '../contract/index.js'; import type { Network } from './network.js'; import type { Outpoint } from '../transaction/utxo.js'; import type { TxInput, TxOutput } from '../transaction/utxo.js'; +import type { Zone, Shard } from '../constants/index.js'; const BN_0 = BigInt(0); @@ -49,6 +50,7 @@ import { import { WorkObjectLike } from '../transaction/work-object.js'; import { QiTransactionLike } from '../transaction/qi-transaction.js'; import { QuaiTransactionLike } from '../transaction/quai-transaction.js'; +import { toShard, toZone } from '../constants/index.js'; // ----------------------- @@ -1044,11 +1046,11 @@ export class Log implements LogParams { /** * Returns the block that this log occurred in. * - * @param {string} shard - The shard to fetch the block from. + * @param {Shard} shard - The shard to fetch the block from. * * @returns {Promise} A promise resolving to the block. */ - async getBlock(shard: string): Promise { + async getBlock(shard: Shard): Promise { const block = await this.provider.getBlock(shard, this.blockHash); assert(!!block, 'failed to find transaction', 'UNKNOWN_ERROR', {}); return block; @@ -1087,8 +1089,8 @@ export class Log implements LogParams { ////////////////////// // Transaction Receipt -export function shardFromHash(hash: string): string { - return hash.slice(0, 4); +export function zoneFromHash(hash: string): Zone { + return toZone(hash.slice(0, 4)); } /** * A **TransactionReceipt** includes additional information about a transaction that is only available after it has been @@ -1305,12 +1307,12 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable} A promise resolving to the block. * @throws {Error} If the block is not found. */ - async getBlock(shard: string): Promise { + async getBlock(shard: Shard): Promise { const block = await this.provider.getBlock(shard, this.blockHash); if (block == null) { throw new Error('TODO'); @@ -1351,8 +1353,8 @@ export class TransactionReceipt implements TransactionReceiptParams, Iterable { - const shard = shardFromHash(this.hash); - return (await this.provider.getBlockNumber(shard)) - this.blockNumber + 1; + const zone = zoneFromHash(this.hash); + return (await this.provider.getBlockNumber(toShard(zone))) - this.blockNumber + 1; } /** @@ -1612,11 +1614,11 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac * * This will return null if the transaction has not been included yet. * - * @param {string} shard - The shard to fetch the block from. + * @param {Shard} shard - The shard to fetch the block from. * * @returns {null | Promise} A promise resolving to the block. */ - async getBlock(shard: string): Promise { + async getBlock(shard: Shard): Promise { let blockNumber = this.blockNumber; if (blockNumber == null) { const tx = await this.getTransaction(); @@ -1656,11 +1658,11 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac * @throws {Error} If the block is not found. */ async confirmations(): Promise { - const shard = shardFromHash(this.hash); + const zone = zoneFromHash(this.hash); if (this.blockNumber == null) { const { tx, blockNumber } = await resolveProperties({ tx: this.getTransaction(), - blockNumber: this.provider.getBlockNumber(shard), + blockNumber: this.provider.getBlockNumber(toShard(zone)), }); // Not mined yet... @@ -1671,7 +1673,7 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac return blockNumber - tx.blockNumber + 1; } - const blockNumber = await this.provider.getBlockNumber(shard); + const blockNumber = await this.provider.getBlockNumber(toShard(zone)); return blockNumber - this.blockNumber + 1; } @@ -1695,14 +1697,14 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac let startBlock = this.startBlock; let nextScan = -1; let stopScanning = startBlock === -1 ? true : false; - const shard = shardFromHash(this.hash); + const zone = zoneFromHash(this.hash); const checkReplacement = async () => { // Get the current transaction count for this sender if (stopScanning) { return null; } const { blockNumber, nonce } = await resolveProperties({ - blockNumber: this.provider.getBlockNumber(shard), + blockNumber: this.provider.getBlockNumber(toShard(zone)), nonce: this.provider.getTransactionCount(this.from), }); @@ -1737,7 +1739,7 @@ export class QuaiTransactionResponse implements QuaiTransactionLike, QuaiTransac if (stopScanning) { return null; } - const block = await this.provider.getBlock(shard, nextScan, true); + const block = await this.provider.getBlock(toShard(zone), nextScan, true); // This should not happen; but we'll try again shortly if (block == null) { @@ -2073,11 +2075,11 @@ export class QiTransactionResponse implements QiTransactionLike, QiTransactionRe * * This will return null if the transaction has not been included yet. * - * @param {string} shard - The shard to fetch the block from. + * @param {Shard} shard - The shard to fetch the block from. * * @returns {null | Promise} A promise resolving to the block or null if not found. */ - async getBlock(shard: string): Promise { + async getBlock(shard: Shard): Promise { let blockNumber = this.blockNumber; if (blockNumber == null) { const tx = await this.getTransaction(); @@ -2117,11 +2119,11 @@ export class QiTransactionResponse implements QiTransactionLike, QiTransactionRe * @returns {Promise} A promise resolving to the number of confirmations. */ async confirmations(): Promise { - const shard = shardFromHash(this.hash); + const zone = zoneFromHash(this.hash); if (this.blockNumber == null) { const { tx, blockNumber } = await resolveProperties({ tx: this.getTransaction(), - blockNumber: this.provider.getBlockNumber(shard), + blockNumber: this.provider.getBlockNumber(toShard(zone)), }); // Not mined yet... @@ -2132,7 +2134,7 @@ export class QiTransactionResponse implements QiTransactionLike, QiTransactionRe return blockNumber - tx.blockNumber + 1; } - const blockNumber = await this.provider.getBlockNumber(shard); + const blockNumber = await this.provider.getBlockNumber(toShard(zone)); return blockNumber - this.blockNumber + 1; } @@ -2322,7 +2324,7 @@ export interface Filter extends EventFilter { */ toBlock?: BlockTag; - shard: string; + shard: Shard; } /** @@ -2335,7 +2337,7 @@ export interface FilterByBlockHash extends EventFilter { * The blockhash of the specific block for the filter. */ blockHash?: string; - shard: string; + shard: Shard; } ////////////////////// @@ -2407,35 +2409,33 @@ export interface Provider extends ContractRunner, EventEmitterable} A promise resolving to the block number. */ - getBlockNumber(shard: string): Promise; + getBlockNumber(shard: Shard): Promise; /** * Get the connected {@link Network | **Network**}. * - * @param {string} shard - The shard to fetch the network from. + * @param {Shard} shard - The shard to fetch the network from. * * @returns {Promise} A promise resolving to the network. */ - getNetwork(shard?: string): Promise; + getNetwork(shard?: Shard): Promise; /** * Get the best guess at the recommended {@link FeeData | **FeeData**}. * - * @param {string} shard - The shard to fetch the fee data from. + * @param {Zone} zone - The shard to fetch the fee data from. * * @returns {Promise} A promise resolving to the fee data. */ - getFeeData(shard: string): Promise; + getFeeData(zone: Zone): Promise; /** * Get a work object to package a transaction in. * - * @param {string} shard - The shard to fetch the work object from. - * * @returns {Promise} A promise resolving to the work object. */ getPendingHeader(): Promise; @@ -2534,14 +2534,14 @@ export interface Provider extends ContractRunner, EventEmitterable} A promise resolving to the transaction response. * @throws {Error} If the transaction is invalid or the transaction is replaced. */ - broadcastTransaction(shard: string, signedTx: string, from?: AddressLike): Promise; + broadcastTransaction(zone: Zone, signedTx: string, from?: AddressLike): Promise; //////////////////// // Queries @@ -2552,14 +2552,14 @@ export interface Provider extends ContractRunner, EventEmitterable} A promise resolving to the block or null if not found. * @throws {Error} If the block is not found. */ - getBlock(shard: string, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise; + getBlock(shard: Shard, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise; /** * Resolves to the transaction for `hash`. @@ -2625,12 +2625,12 @@ export interface Provider extends ContractRunner, EventEmitterable} A promise resolving to the block. */ - waitForBlock(shard: string, blockTag?: BlockTag): Promise; + waitForBlock(shard: Shard, blockTag?: BlockTag): Promise; /** * Resolves to the number indicating the size of the network diff --git a/src/quais.ts b/src/quais.ts index 89de19fe..004f6c67 100644 --- a/src/quais.ts +++ b/src/quais.ts @@ -1,16 +1,29 @@ // VERSION -export { version } from "./_version.js"; +export { version } from './_version.js'; // APPLICATION BINARY INTERFACE export { - decodeBytes32String, encodeBytes32String, - + decodeBytes32String, + encodeBytes32String, AbiCoder, - ConstructorFragment, ErrorFragment, EventFragment, Fragment, FallbackFragment, FunctionFragment, NamedFragment, ParamType, StructFragment, - - checkResultErrors, ErrorDescription, Indexed, Interface, LogDescription, Result, TransactionDescription, + ConstructorFragment, + ErrorFragment, + EventFragment, + Fragment, + FallbackFragment, + FunctionFragment, + NamedFragment, + ParamType, + StructFragment, + checkResultErrors, + ErrorDescription, + Indexed, + Interface, + LogDescription, + Result, + TransactionDescription, Typed, -} from "./abi/index.js"; +} from './abi/index.js'; // ADDRESS export { @@ -23,17 +36,28 @@ export { //CONSTANTS export { ZeroAddress, - WeiPerEther, MaxUint256, MinInt256, MaxInt256, N, + WeiPerEther, + MaxUint256, + MinInt256, + MaxInt256, + N, ZeroHash, - quaisymbol, MessagePrefix -} from "./constants/index.js"; + quaisymbol, + MessagePrefix, +} from './constants/index.js'; // CONTRACT export { - BaseContract, Contract, + BaseContract, + Contract, ContractFactory, - ContractEventPayload, ContractTransactionReceipt, ContractTransactionResponse, ContractUnknownEventPayload, EventLog, UndecodedEventLog -} from "./contract/index.js"; + ContractEventPayload, + ContractTransactionReceipt, + ContractTransactionResponse, + ContractUnknownEventPayload, + EventLog, + UndecodedEventLog, +} from './contract/index.js'; // CRYPTO export { @@ -41,12 +65,15 @@ export { randomBytes, keccak256, ripemd160, - sha256, sha512, + sha256, + sha512, pbkdf2, - scrypt, scryptSync, + scrypt, + scryptSync, lock, - Signature, SigningKey -} from "./crypto/index.js"; + Signature, + SigningKey, +} from './crypto/index.js'; // HASH export { @@ -54,8 +81,8 @@ export { hashMessage, verifyMessage, solidityPacked, solidityPackedKeccak256, solidityPackedSha256, TypedDataEncoder, - verifyTypedData -} from "./hash/index.js"; + verifyTypedData, +} from './hash/index.js'; // PROVIDERS export { @@ -102,7 +129,7 @@ export { getBigInt, getNumber, getUint, toBeArray, toBigInt, toBeHex, toNumber, toQuantity, fromTwos, toTwos, mask, formatQuai, parseQuai, formatEther, parseEther, formatUnits, parseUnits, - uuidV4, getTxType, getShardForAddress, getAddressDetails, isQiAddress, + uuidV4, getTxType, getZoneForAddress, getAddressDetails, isQiAddress, } from "./utils/index.js"; export { @@ -124,27 +151,24 @@ export { } from "./wallet/index.js"; // WORDLIST -export { - Wordlist, LangEn, LangEs, WordlistOwl, WordlistOwlA, wordlists -} from "./wordlists/index.js"; - - +export { Wordlist, LangEn, LangEs, WordlistOwl, WordlistOwlA, wordlists } from './wordlists/index.js'; ///////////////////////////// // Types // APPLICATION BINARY INTERFACE export type { - JsonFragment, JsonFragmentType, - FormatType, FragmentType, + JsonFragment, + JsonFragmentType, + FormatType, + FragmentType, InterfaceAbi, - ParamTypeWalkFunc, ParamTypeWalkAsyncFunc -} from "./abi/index.js"; + ParamTypeWalkFunc, + ParamTypeWalkAsyncFunc, +} from './abi/index.js'; // ADDRESS -export type { - Addressable, AddressLike -} from "./address/index.js"; +export type { Addressable, AddressLike } from './address/index.js'; // CONTRACT export type { @@ -156,10 +180,10 @@ export type { } from "./contract/index.js"; // CRYPTO -export type { ProgressCallback, SignatureLike } from "./crypto/index.js"; +export type { ProgressCallback, SignatureLike } from './crypto/index.js'; // HASH -export type { TypedDataDomain, TypedDataField } from "./hash/index.js"; +export type { TypedDataDomain, TypedDataField } from './hash/index.js'; // PROVIDERS export type { @@ -183,10 +207,7 @@ export type { } from "./signers/index.js"; // TRANSACTION -export type { - AccessList, AccessListish, AccessListEntry, - TransactionLike -} from "./transaction/index.js"; +export type { AccessList, AccessListish, AccessListEntry, TransactionLike } from './transaction/index.js'; // UTILS export type { diff --git a/src/signers/abstract-signer.ts b/src/signers/abstract-signer.ts index 0f0371b1..273d552b 100644 --- a/src/signers/abstract-signer.ts +++ b/src/signers/abstract-signer.ts @@ -20,6 +20,7 @@ import type { import type { Signer } from "./signer.js"; import { getTxType } from "../utils/index.js"; import {QiTransaction, QiTransactionLike, QuaiTransaction, QuaiTransactionLike} from "../transaction/index.js"; +import { toZone, Zone } from '../constants/index.js'; function checkProvider(signer: AbstractSigner, operation: string): Provider { if (signer.provider) { @@ -76,9 +77,9 @@ export abstract class AbstractSigner

{ + async zoneFromAddress(_address: AddressLike): Promise { const address: string | Promise = this._getAddress(_address); - return (await address).slice(0, 4); + return toZone((await address).slice(0, 4)); } /** * Returns the signer connected to `provider`. @@ -103,7 +104,7 @@ export abstract class AbstractSigner

{ const provider = checkProvider(this, 'populateTransaction'); - const shard = await this.shardFromAddress(tx.from); + const zone = await this.zoneFromAddress(tx.from); const pop = (await populate(this, tx)) as QuaiTransactionLike; @@ -135,12 +136,12 @@ export abstract class AbstractSigner

{ const provider = checkProvider(this, 'sendTransaction'); const sender = await this.getAddress(); - const shard = await this.shardFromAddress(addressFromTransactionRequest(tx)); + const zone = await this.zoneFromAddress(addressFromTransactionRequest(tx)); let pop; let txObj; @@ -192,7 +193,7 @@ export abstract class AbstractSigner

; diff --git a/src/transaction/abstract-transaction.ts b/src/transaction/abstract-transaction.ts index 0bad6377..e3f24444 100644 --- a/src/transaction/abstract-transaction.ts +++ b/src/transaction/abstract-transaction.ts @@ -5,6 +5,7 @@ import type { BigNumberish } from '../utils/index.js'; import type { SignatureLike } from '../crypto/index.js'; import { encodeProtoTransaction } from '../encoding/proto-encode.js'; import type { TxInput, TxOutput } from './utxo.js'; +import { Zone } from '../constants/index.js'; /** * A **TransactionLike** is a JSON representation of a transaction. @@ -32,10 +33,10 @@ export interface TransactionLike { } /** - * @TODO write documentation for this interface. - * * @category Transaction * @todo Write documentation for this interface. + * + * @todo Write documentation for this interface. */ export interface ProtoTransaction { /** @@ -46,7 +47,7 @@ export interface ProtoTransaction { /** * @todo Write documentation for this property. */ - to?: Uint8Array | null + to?: Uint8Array | null; /** * @todo Write documentation for this property. @@ -160,18 +161,16 @@ export interface ProtoTransaction { } /** - * @TODO write documentation for this interface. - * * @category Transaction * @todo Write documentation for this interface. + * + * @todo Write documentation for this interface. */ export interface ProtoAccessList { access_tuples: Array; } /** - * @TODO write documentation for this interface. - * * @category Transaction * @todo Write documentation for this interface. */ @@ -194,11 +193,9 @@ type allowedSignatureTypes = Signature | string; * tx = new Transaction(); * //_result: * - * tx.data = "0x1234"; - * //_result: - * ``` - * - * @category Transaction + * tx.data = '0x1234'; + * //_result: + * ``` */ export abstract class AbstractTransaction implements TransactionLike { protected _type: number | null; @@ -291,10 +288,9 @@ export abstract class AbstractTransaction imple /** * Returns true if signed. * - * This provides a Type Guard that properties requiring a signed - * transaction are non-null. + * This provides a Type Guard that properties requiring a signed transaction are non-null. * - * @returns {boolean} Indicates if the transaction is signed. + * @returns {boolean} Indicates if the transaction is signed. */ isSigned(): this is AbstractTransaction & { type: number; @@ -332,50 +328,48 @@ export abstract class AbstractTransaction imple } /** - * Return the most "likely" type; currently the highest - * supported transaction type. + * Return the most "likely" type; currently the highest supported transaction type. * - * @returns {number} The inferred transaction type. + * @returns {number} The inferred transaction type. */ inferType(): number { return this.inferTypes().pop(); } /** - * Validates the explicit properties and returns a list of compatible - * transaction types. + * Validates the explicit properties and returns a list of compatible transaction types. * - * @returns {Array} The compatible transaction types. + * @returns {number[]} The compatible transaction types. */ abstract inferTypes(): Array; /** - * Create a copy of this transaciton. + * Create a copy of this transaciton. * - * @returns {AbstractTransaction} The cloned transaction. + * @returns {AbstractTransaction} The cloned transaction. */ abstract clone(): AbstractTransaction; /** - * Return a JSON-friendly object. + * Return a JSON-friendly object. * - * @returns {TransactionLike} The JSON-friendly object. + * @returns {TransactionLike} The JSON-friendly object. */ abstract toJSON(): TransactionLike; /** - * Return a protobuf-friendly JSON object. + * Return a protobuf-friendly JSON object. * - * @returns {ProtoTransaction} The protobuf-friendly JSON object. + * @returns {ProtoTransaction} The protobuf-friendly JSON object. */ abstract toProtobuf(): ProtoTransaction; - abstract get originShard(): string | undefined; + abstract get originZone(): Zone | undefined; - abstract get destShard(): string | undefined; + abstract get destZone(): Zone | undefined; get isExternal(): boolean { - return this.destShard !== undefined && this.originShard !== this.destShard + return this.destZone !== undefined && this.originZone !== this.destZone; } /** diff --git a/src/transaction/qi-transaction.ts b/src/transaction/qi-transaction.ts index 706e8faf..e11fe752 100644 --- a/src/transaction/qi-transaction.ts +++ b/src/transaction/qi-transaction.ts @@ -2,7 +2,7 @@ import {keccak256} from "../crypto/index.js"; import {AbstractTransaction, TransactionLike, TxInput, TxOutput} from "./index.js"; import { assertArgument, - getBytes, getShardForAddress, + getBytes, getZoneForAddress, hexlify, isQiAddress, toBigInt } from "../utils/index.js"; @@ -10,6 +10,7 @@ import { decodeProtoTransaction } from '../encoding/index.js'; import {formatNumber} from "../providers/format.js"; import { computeAddress } from "../address/index.js"; import { ProtoTransaction} from "./abstract-transaction.js"; +import { Zone } from '../constants/index.js'; /** * @category Transaction @@ -67,18 +68,18 @@ export class QiTransaction extends AbstractTransaction implements QiTran throw new Error('Transaction must have at least one input and one output'); } - const destUtxo = isQiAddress(hexlify(this.txOutputs[0].address) || ""); + const destUtxo = isQiAddress(hexlify(this.txOutputs[0].address) || ''); const pubKey = hexlify(this.txInputs[0].pub_key); - const senderAddr = computeAddress(pubKey || ""); + const senderAddr = computeAddress(pubKey || ''); const originUtxo = isQiAddress(senderAddr); - if (!this.destShard || !this.originShard) { + if (!this.destZone || !this.originZone) { throw new Error( - `Invalid shards: origin ${this.originShard} -> destination ${this.destShard} (address: ${senderAddr})`, + `Invalid zones: origin ${this.originZone} -> destination ${this.destZone} (address: ${senderAddr})`, ); } if (this.isExternal && destUtxo !== originUtxo) { - throw new Error('Cross-shard & cross-ledger transactions are not supported'); + throw new Error('Cross-zone & cross-ledger transactions are not supported'); } const hexString = this.serialized.startsWith('0x') ? this.serialized.substring(2) : this.serialized; @@ -87,7 +88,7 @@ export class QiTransaction extends AbstractTransaction implements QiTran const hashHex = keccak256(dataBuffer); const hashBuffer = Buffer.from(hashHex.substring(2), 'hex'); - const origin = this.originShard ? parseInt(this.originShard, 16) : 0; + const origin = this.originZone ? parseInt(this.originZone.slice(2), 16) : 0; hashBuffer[0] = origin; hashBuffer[1] |= 0x80; hashBuffer[2] = origin; @@ -96,15 +97,17 @@ export class QiTransaction extends AbstractTransaction implements QiTran return '0x' + hashBuffer.toString('hex'); } - get originShard(): string | undefined { + get originZone(): Zone | undefined { const pubKey = hexlify(this.txInputs[0].pub_key); const senderAddr = computeAddress(pubKey || ''); - return getShardForAddress(senderAddr)?.byte.slice(2); + const zone = getZoneForAddress(senderAddr); + return zone ?? undefined; } - get destShard(): string | undefined { - return getShardForAddress(hexlify(this.txOutputs[0].address) || '')?.byte.slice(2); + get destZone(): Zone | undefined { + const zone = getZoneForAddress(hexlify(this.txOutputs[0].address) || ''); + return zone ?? undefined; } /** diff --git a/src/transaction/quai-transaction.ts b/src/transaction/quai-transaction.ts index 167ec6af..2382d43d 100644 --- a/src/transaction/quai-transaction.ts +++ b/src/transaction/quai-transaction.ts @@ -8,7 +8,7 @@ import { getBigInt, getBytes, getNumber, - getShardForAddress, + getZoneForAddress, hexlify, isQiAddress, toBeArray, toBigInt, zeroPadValue } from "../utils/index.js"; @@ -16,6 +16,7 @@ import { decodeProtoTransaction, encodeProtoTransaction } from '../encoding/inde import { getAddress, recoverAddress } from "../address/index.js"; import { formatNumber, handleNumber } from "../providers/format.js"; import { ProtoTransaction} from "./abstract-transaction.js"; +import { Zone } from '../constants'; /** * @category Transaction @@ -122,14 +123,14 @@ export class QuaiTransaction extends AbstractTransaction implements Q return this.unsignedHash; } get unsignedHash(): string { - const destUtxo = isQiAddress(this.to || ""); + const destUtxo = isQiAddress(this.to || ''); const originUtxo = isQiAddress(this.from); - if (!this.originShard) { - throw new Error("Invalid Shard for from or to address"); + if (!this.originZone) { + throw new Error('Invalid Shard for from or to address'); } if (this.isExternal && destUtxo !== originUtxo) { - throw new Error('Cross-shard & cross-ledger transactions are not supported'); + throw new Error('Cross-zone & cross-ledger transactions are not supported'); } const hexString = this.serialized.startsWith('0x') ? this.serialized.substring(2) : this.serialized; @@ -138,7 +139,7 @@ export class QuaiTransaction extends AbstractTransaction implements Q const hashHex = keccak256(dataBuffer); const hashBuffer = Buffer.from(hashHex.substring(2), 'hex'); - const origin = this.originShard ? parseInt(this.originShard, 16) : 0; + const origin = this.originZone ? parseInt(this.originZone.slice(2), 16) : 0; hashBuffer[0] = origin; hashBuffer[1] &= 0x7f; hashBuffer[2] = origin; @@ -147,14 +148,16 @@ export class QuaiTransaction extends AbstractTransaction implements Q return '0x' + hashBuffer.toString('hex'); } - get originShard(): string | undefined { + get originZone(): Zone | undefined { const senderAddr = this.from; - return getShardForAddress(senderAddr)?.byte.slice(2); + const zone = getZoneForAddress(senderAddr); + return zone ?? undefined; } - get destShard(): string | undefined { - return this.to !== null ? getShardForAddress(this.to || "")?.byte.slice(2) : undefined; + get destZone(): Zone | undefined { + const zone = this.to !== null ? getZoneForAddress(this.to || '') : undefined; + return zone ?? undefined; } /** @@ -362,8 +365,8 @@ export class QuaiTransaction extends AbstractTransaction implements Q gas_fee_cap: formatNumber(this.maxFeePerGas || 0, 'maxFeePerGas'), gas: Number(this.gasLimit || 0), to: this.to != null ? getBytes(this.to as string) : null, - value: formatNumber(this.value || 0, "value"), - data: getBytes(this.data || "0x"), + value: formatNumber(this.value || 0, 'value'), + data: getBytes(this.data || '0x'), access_list: { access_tuples: [] }, }; @@ -449,14 +452,10 @@ export class QuaiTransaction extends AbstractTransaction implements Q let address; if (protoTx.v && protoTx.r && protoTx.s) { // check if protoTx.r is zero - if (protoTx.r.reduce((acc, val) => acc += val, 0) == 0) { - throw new Error("Proto decoding only supported for signed transactions") + if (protoTx.r.reduce((acc, val) => (acc += val), 0) == 0) { + throw new Error('Proto decoding only supported for signed transactions'); } - const signatureFields = [ - hexlify(protoTx.v!), - hexlify(protoTx.r!), - hexlify(protoTx.s!), - ]; + const signatureFields = [hexlify(protoTx.v!), hexlify(protoTx.r!), hexlify(protoTx.s!)]; signature = _parseSignature(signatureFields); const protoTxCopy = structuredClone(protoTx); diff --git a/src/utils/index.ts b/src/utils/index.ts index 2c8954f5..6a03eac0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -56,7 +56,7 @@ export { formatQuai, parseQuai, formatEther, parseEther, formatUnits, parseUnits export { uuidV4 } from './uuid.js'; -export { getTxType, getShardForAddress, getAddressDetails, isQiAddress } from './shards.js'; +export { getTxType, getZoneForAddress, getAddressDetails, isQiAddress } from './shards.js'; ///////////////////////////// // Types diff --git a/src/utils/shards.ts b/src/utils/shards.ts index e4a3761a..7b5d42b4 100644 --- a/src/utils/shards.ts +++ b/src/utils/shards.ts @@ -1,4 +1,4 @@ -import { ShardData } from "../constants/shards.js"; +import { toZone, Zone } from '../constants/zones.js'; /** * Retrieves the shard information for a given address based on its byte prefix. The function parses the address to * extract its byte prefix, then filters the ShardData to find a matching shard entry. If no matching shard is found, it @@ -10,43 +10,28 @@ import { ShardData } from "../constants/shards.js"; * * @returns {Object | null} An object containing the shard information, or null if no */ -export function getShardForAddress( - address: string, -): { name: string; nickname: string; shard: string; context: number; byte: string } | null { - const addressByte = address.substring(2, 4); - const filteredShards = ShardData.filter((obj) => { - return parseInt(addressByte, 16) === parseInt(obj.byte, 16); - }); - - if (filteredShards.length === 0) { +export function getZoneForAddress(address: string): Zone | null { + try { + return toZone(address.slice(0, 4)); + } catch (error) { return null; } - return filteredShards[0]; } /** - * Extracts both shard and UTXO information from a given blockchain address. This function first determines the - * address's shard by its byte prefix, then checks the 9th bit of the address to ascertain if it's a UTXO or non-UTXO - * address. + * Extracts both zone and UTXO information from a given blockchain address. This function first determines the address's + * zone by its byte prefix, then checks the 9th bit of the address to ascertain if it's a UTXO or non-UTXO address. * * @category Utils * @param {string} address - The blockchain address to be analyzed, expected to start with "0x" followed by its * hexadecimal representation. * - * @returns {Object | null} An object containing the shard and UTXO information, or null if no address is found. + * @returns {Object | null} An object containing the zone and UTXO information, or null if no address is found. */ -export function getAddressDetails(address: string): { shard: any; isUTXO: boolean } | null { - const addressByte = address.substring(2, 4); +export function getAddressDetails(address: string): { zone: Zone; isUTXO: boolean } | null { const isUTXO = (parseInt(address.substring(4, 5), 16) & 0x1) === 1; - const filteredShards = ShardData.filter((obj) => { - return parseInt(addressByte, 16) === parseInt(obj.byte, 16); - }); - - if (filteredShards.length === 0) { - return null; - } - return { shard: filteredShards[0], isUTXO }; + return { zone: toZone(address.substring(0, 4)), isUTXO }; } /** @@ -63,9 +48,9 @@ export function getAddressDetails(address: string): { shard: any; isUTXO: boolea * @returns {number} The transaction type based on the addresses. */ export function getTxType(from: string | null, to: string | null): number { - if (from === null || to === null) return 0; - const fromUTXO = isQiAddress(from); - const toUTXO = isQiAddress(to); + if (from === null || to === null) return 0; + const fromUTXO = isQiAddress(from); + const toUTXO = isQiAddress(to); switch (true) { case fromUTXO && toUTXO: @@ -78,19 +63,19 @@ export function getTxType(from: string | null, to: string | null): number { } /** - * Checks whether a given blockchain address is a UTXO address based on the 9th bit of - * the address. This function extracts the second byte of the address and checks its - * first bit to determine the UTXO status. - * - * @param {string} address - The blockchain address to be analyzed, expected to start with "0x" followed by its hexadecimal representation. - * @returns {boolean} True if the address is a UTXO address, false otherwise. - * - * @category Utils - */ + * Checks whether a given blockchain address is a UTXO address based on the 9th bit of the address. This function + * extracts the second byte of the address and checks its first bit to determine the UTXO status. + * + * @category Utils + * @param {string} address - The blockchain address to be analyzed, expected to start with "0x" followed by its + * hexadecimal representation. + * + * @returns {boolean} True if the address is a UTXO address, false otherwise. + */ export function isQiAddress(address: string): boolean { - const secondByte = address.substring(4, 6); - const binaryString = parseInt(secondByte, 16).toString(2).padStart(8, '0'); - const isUTXO = binaryString[0] === '1'; + const secondByte = address.substring(4, 6); + const binaryString = parseInt(secondByte, 16).toString(2).padStart(8, '0'); + const isUTXO = binaryString[0] === '1'; - return isUTXO; + return isUTXO; } diff --git a/src/wallet/hdwallet.ts b/src/wallet/hdwallet.ts index 13fbe22c..f8482b01 100644 --- a/src/wallet/hdwallet.ts +++ b/src/wallet/hdwallet.ts @@ -18,7 +18,7 @@ import { assert, assertArgument, hexlify, - getShardForAddress, + getZoneForAddress, isQiAddress, BytesLike, Numeric, @@ -30,7 +30,7 @@ import { decodeBase58 } from '../encoding/base58.js'; import { BaseWallet } from './base-wallet.js'; import { Mnemonic } from './mnemonic.js'; import { encryptKeystoreJson, encryptKeystoreJsonSync } from './json-keystore.js'; -import { N } from '../constants/index.js'; +import { N, Zone } from '../constants/index.js'; import type { ProgressCallback } from '../crypto/index.js'; import type { Wordlist } from '../wordlists/index.js'; import type { KeystoreAccount } from './json-keystore.js'; @@ -42,10 +42,10 @@ const MAX_ADDRESS_DERIVATION_ATTEMPTS = 10000000; // Used to type the instantiation of a child wallet class from static methods export interface HDWalletStatic { - new(...args: any[]): T; - _fromSeed(_seed: BytesLike, mnemonic: null | Mnemonic): T; - isValidPath(path: string): boolean; - derivePath(path: string): T; + new (...args: any[]): T; + _fromSeed(_seed: BytesLike, mnemonic: null | Mnemonic): T; + isValidPath(path: string): boolean; + derivePath(path: string): T; } export type AddressInfo = { @@ -160,54 +160,53 @@ export abstract class HDWallet extends BaseWallet implements HDNodeLike this)(...params); } - protected account(): KeystoreAccount { - const account: KeystoreAccount = { - address: this.address, - privateKey: this.privateKey, - }; - const m = this.mnemonic; - if (this.path && m && m.wordlist.locale === "en" && m.password === "") { - account.mnemonic = { - path: this.path, - locale: "en", - entropy: m.entropy, - }; - } + protected account(): KeystoreAccount { + const account: KeystoreAccount = { + address: this.address, + privateKey: this.privateKey, + }; + const m = this.mnemonic; + if (this.path && m && m.wordlist.locale === 'en' && m.password === '') { + account.mnemonic = { + path: this.path, + locale: 'en', + entropy: m.entropy, + }; + } return account; } - /** - * Resolves to a [JSON Keystore Wallet](json-wallets) encrypted with - * `password`. - * - * If `progressCallback` is specified, it will receive periodic - * updates as the encryption process progreses. - * - * @param {Uint8Array | string} password - The password to encrypt the wallet with. - * @param {ProgressCallback} [progressCallback] - An optional callback to receive progress updates. - * @returns {Promise} The encrypted JSON Keystore Wallet. - */ - async encrypt(password: Uint8Array | string,progressCallback?: ProgressCallback): Promise { - return await encryptKeystoreJson(this.account(), password, {progressCallback}); - } - - /** - * Returns a [JSON Keystore Wallet](json-wallets) encryped with - * `password`. - * - * It is preferred to use the [async version](encrypt) instead, - * which allows a {@link ProgressCallback | **ProgressCallback**} to keep the user informed. - * - * This method will block the event loop (freezing all UI) until - * it is complete, which may be a non-trivial duration. - * - * @param {Uint8Array | string} password - The password to encrypt the wallet with. - * @returns {string} The encrypted JSON Keystore Wallet. - */ - encryptSync(password: Uint8Array | string): string { - return encryptKeystoreJsonSync(this.account(), password); - } + /** + * Resolves to a [JSON Keystore Wallet](json-wallets) encrypted with `password`. + * + * If `progressCallback` is specified, it will receive periodic updates as the encryption process progreses. + * + * @param {Uint8Array | string} password - The password to encrypt the wallet with. + * @param {ProgressCallback} [progressCallback] - An optional callback to receive progress updates. + * + * @returns {Promise} The encrypted JSON Keystore Wallet. + */ + async encrypt(password: Uint8Array | string, progressCallback?: ProgressCallback): Promise { + return await encryptKeystoreJson(this.account(), password, { progressCallback }); + } + + /** + * Returns a [JSON Keystore Wallet](json-wallets) encryped with `password`. + * + * It is preferred to use the [async version](encrypt) instead, which allows a + * {@link ProgressCallback | **ProgressCallback**} to keep the user informed. + * + * This method will block the event loop (freezing all UI) until it is complete, which may be a non-trivial + * duration. + * + * @param {Uint8Array | string} password - The password to encrypt the wallet with. + * + * @returns {string} The encrypted JSON Keystore Wallet. + */ + encryptSync(password: Uint8Array | string): string { + return encryptKeystoreJsonSync(this.account(), password); + } /** * The extended key. @@ -496,37 +495,40 @@ export abstract class HDWallet extends BaseWallet implements HDNodeLike { - const shardNickname = getShardForAddress(address)?.nickname.toLowerCase(); - const isCorrectShard = shardNickname === zone; - const isCorrectCoinType = newWallet.coinType === this.coinType; - const isCorrectLedger = (ledgerType === 'Qi') ? isQiAddress(address) : !isQiAddress(address); - - return isCorrectShard && isCorrectCoinType && isCorrectLedger; - } - - let addrIndex: number = startingIndex; - do { - newWallet = this.derivePath(addrIndex.toString()); - addrIndex++; - // put a hard limit on the number of addresses to derive - if (addrIndex - startingIndex > MAX_ADDRESS_DERIVATION_ATTEMPTS) { - throw new Error(`Failed to derive a valid address for the zone ${zone} after MAX_ADDRESS_DERIVATION_ATTEMPTS attempts.`); - } - } while (!isValidAddressForZone(newWallet.address)); + const isValidAddressForZone = (address: string) => { + const addressZone = getZoneForAddress(address); + const isCorrectShard = addressZone === zone; + const isCorrectCoinType = newWallet.coinType === this.coinType; + const isCorrectLedger = ledgerType === 'Qi' ? isQiAddress(address) : !isQiAddress(address); + + return isCorrectShard && isCorrectCoinType && isCorrectLedger; + }; + + let addrIndex: number = startingIndex; + do { + newWallet = this.derivePath(addrIndex.toString()); + addrIndex++; + // put a hard limit on the number of addresses to derive + if (addrIndex - startingIndex > MAX_ADDRESS_DERIVATION_ATTEMPTS) { + throw new Error( + `Failed to derive a valid address for the zone ${zone} after MAX_ADDRESS_DERIVATION_ATTEMPTS attempts.`, + ); + } + } while (!isValidAddressForZone(newWallet.address)); const addresInfo = { address: newWallet.address, privKey: newWallet.privateKey, index: addrIndex - 1 }; diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index 013ece45..ab7d24f1 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -1,6 +1,6 @@ -import { ShardData } from '../constants/index.js'; +import { ShardData, toShard, Zone } from '../constants/index.js'; import { SigningKey, keccak256 as addressKeccak256 } from '../crypto/index.js'; -import { getBytes, getShardForAddress, hexlify } from '../utils/index.js'; +import { getBytes, getZoneForAddress, hexlify } from '../utils/index.js'; import { Provider, QiTransactionRequest } from '../providers/index.js'; import { TransactionLike, QiTransaction, TxInput } from '../transaction/index.js'; import { Mnemonic } from './mnemonic.js'; @@ -90,11 +90,11 @@ export class QiHDWallet extends HDWallet { * Initializes the wallet by generating addresses and private keys for the specified zone. The wallet will generate * addresses until it has `GAP` number of naked addresses. A provider must be set before calling this method. * - * @param {string} zone - Zone identifier used to validate the derived address. + * @param {Zone} zone - Zone identifier used to validate the derived address. * * @returns {Promise} */ - public async init(zone: string): Promise { + public async init(zone: Zone): Promise { if (!this.validateZone(zone)) throw new Error(`Invalid zone: ${zone}`); if (!this.provider) throw new Error('Provider not set'); @@ -108,7 +108,7 @@ export class QiHDWallet extends HDWallet { let derivationIndex = 0; while (nakedCount < GAP) { - const addressInfo = this.deriveAddress(derivationIndex, zone, "Qi"); + const addressInfo = this.deriveAddress(derivationIndex, zone, 'Qi'); // store the address, private key and index shardWalletData.addressesInfo.push(addressInfo); // query the network node for the outpoints of the address and update the balance @@ -182,11 +182,12 @@ export class QiHDWallet extends HDWallet { const pubKey = input.pub_key; const address = this.getAddressFromPubKey(hexlify(pubKey)); // get shard from address - const shard = getShardForAddress(address); + const zone = getZoneForAddress(address); + const shard = zone ? toShard(zone) : undefined; if (!shard) throw new Error(`Invalid shard location for address: ${address}`); // get the wallet data corresponding to the shard - const shardWalletData = this.#shardWalletsMap.get(shard.nickname); - if (!shardWalletData) throw new Error(`Missing wallet data for shard: ${shard.name}`); + const shardWalletData = this.#shardWalletsMap.get(shard); + if (!shardWalletData) throw new Error(`Missing wallet data for shard: ${shard}`); // get the private key corresponding to the address const privKey = shardWalletData.addressesInfo.find((utxoAddr) => utxoAddr.address === address)?.privKey; if (!privKey) throw new Error(`Missing private key for ${hexlify(pubKey)}`); @@ -207,11 +208,11 @@ export class QiHDWallet extends HDWallet { const address = computeAddress(hexlify(input.pub_key)); // get shard from address - const shard = getShardForAddress(address); + const shard = getZoneForAddress(address); if (!shard) throw new Error(`Invalid address: ${address}`); // get the wallet data corresponding to the shard - const shardWalletData = this.#shardWalletsMap.get(shard.nickname); - if (!shardWalletData) throw new Error(`Missing wallet data for shard: ${(shard.name, shard.nickname)}`); + const shardWalletData = this.#shardWalletsMap.get(shard); + if (!shardWalletData) throw new Error(`Missing wallet data for shard: ${shard}`); const utxoAddrObj = shardWalletData.addressesInfo.find((utxoAddr) => utxoAddr.address === address); if (!utxoAddrObj) { diff --git a/src/wallet/quai-hdwallet.ts b/src/wallet/quai-hdwallet.ts index 9df7a1e6..0218034a 100644 --- a/src/wallet/quai-hdwallet.ts +++ b/src/wallet/quai-hdwallet.ts @@ -3,17 +3,16 @@ * * @section api/wallet:HD Wallets [hd-wallets] */ -import { SigningKey } from "../crypto/index.js"; -import { Mnemonic } from "./mnemonic.js"; -import type { Provider } from "../providers/index.js"; -import { HDWallet, AddressInfo} from "./hdwallet.js"; -import { QUAI_COIN_TYPE } from '../constants/index.js'; - +import { SigningKey } from '../crypto/index.js'; +import { Mnemonic } from './mnemonic.js'; +import type { Provider } from '../providers/index.js'; +import { HDWallet, AddressInfo } from './hdwallet.js'; +import { QUAI_COIN_TYPE, Zone } from '../constants/index.js'; // keeps track of the addresses and outpoints for a given shard (zone) type ShardWalletData = { addressesInfo: AddressInfo[]; -} +}; /** * An **QuaiHDWallet** is a [Signer](../interfaces/Signer) backed by the private key derived from an HD Node using the @@ -25,16 +24,14 @@ type ShardWalletData = { * @category Wallet */ export class QuaiHDWallet extends HDWallet { - /** - * The Quai cointype. + * The Quai cointype. */ readonly coinType: number = QUAI_COIN_TYPE; /** - * Map of shard name (zone) to shardWalletData - * shardWalletData contains the private keys, addresses and derive indexes for the shard - * that are known to the wallet + * Map of shard name (zone) to shardWalletData shardWalletData contains the private keys, addresses and derive + * indexes for the shard that are known to the wallet */ #shardWalletsMap: Map = new Map(); @@ -44,24 +41,34 @@ export class QuaiHDWallet extends HDWallet { set shardWallets(shardWallets: Map) { this.#shardWalletsMap = shardWallets; - } - - constructor(guard: any, signingKey: SigningKey, accountFingerprint: string, chainCode: string, path: null | string, index: number, depth: number, mnemonic: null | Mnemonic, provider: null | Provider) { + } + + constructor( + guard: any, + signingKey: SigningKey, + accountFingerprint: string, + chainCode: string, + path: null | string, + index: number, + depth: number, + mnemonic: null | Mnemonic, + provider: null | Provider, + ) { super(guard, signingKey, accountFingerprint, chainCode, path, index, depth, mnemonic, provider); } - async getAddress(zone: string): Promise { + async getAddress(zone: Zone): Promise { let index = 0; let shardWalletData: ShardWalletData | undefined = this.#shardWalletsMap.get(zone); if (shardWalletData) { const pos = shardWalletData.addressesInfo.length; - index = shardWalletData!.addressesInfo[pos-1].index + 1; + index = shardWalletData!.addressesInfo[pos - 1].index + 1; } else { - shardWalletData = {addressesInfo: []}; + shardWalletData = { addressesInfo: [] }; this.#shardWalletsMap.set(zone, shardWalletData); } - const addressInfo = this.deriveAddress(index, zone, "Quai"); + const addressInfo = this.deriveAddress(index, zone, 'Quai'); shardWalletData.addressesInfo.push(addressInfo); return addressInfo.address; }