From 028469e07d735399176493f2ca5a48730c5a5a3e Mon Sep 17 00:00:00 2001 From: Adam A Date: Wed, 7 Aug 2024 15:29:30 +0300 Subject: [PATCH] wip: @fadroma/tendermint --- packages/cw/{cw.cli.mjs => CW.cli.mjs} | 0 packages/cw/{cw.test.ts => CW.test.ts} | 0 packages/cw/{cw.ts => CW.ts} | 15 +- packages/cw/{cw-bank.ts => CWBank.ts} | 4 +- packages/cw/{cw-chains.ts => CWChains.ts} | 0 packages/cw/{cw-compute.ts => CWCompute.ts} | 4 +- .../cw/{cw-connection.ts => CWConnection.ts} | 20 +- .../cw/{cw-governance.ts => CWGovernance.ts} | 0 packages/cw/{cw-identity.ts => CWIdentity.ts} | 10 +- packages/cw/CWStaking.ts | 69 ++++ packages/cw/{cw-tx.ts => CWTX.ts} | 6 +- packages/cw/archway/archway.ts | 6 +- packages/cw/axelar/axelar.ts | 6 +- packages/cw/cw-base.ts | 21 - packages/cw/cw-staking.ts | 97 ----- packages/cw/injective/injective.ts | 6 +- packages/cw/okp4/okp4.ts | 7 +- packages/cw/osmosis/osmosis.ts | 6 +- packages/cw/package.json | 5 +- packages/cw/tsconfig.json | 8 +- packages/tendermint/TM.ts | 22 ++ packages/tendermint/TMBase.ts | 16 + packages/tendermint/TMChain.ts | 241 ++++++++++++ packages/tendermint/package.json | 32 ++ pnpm-lock.yaml | 203 +++++----- src/API.ts | 274 +++++++++++++ src/Agent.ts | 363 +++++++++--------- src/Chain.ts | 299 --------------- src/Util.ts | 5 +- src/dlt/Staking.ts | 20 +- 30 files changed, 995 insertions(+), 770 deletions(-) rename packages/cw/{cw.cli.mjs => CW.cli.mjs} (100%) rename packages/cw/{cw.test.ts => CW.test.ts} (100%) rename packages/cw/{cw.ts => CW.ts} (86%) rename packages/cw/{cw-bank.ts => CWBank.ts} (93%) rename packages/cw/{cw-chains.ts => CWChains.ts} (100%) rename packages/cw/{cw-compute.ts => CWCompute.ts} (97%) rename packages/cw/{cw-connection.ts => CWConnection.ts} (92%) rename packages/cw/{cw-governance.ts => CWGovernance.ts} (100%) rename packages/cw/{cw-identity.ts => CWIdentity.ts} (96%) create mode 100644 packages/cw/CWStaking.ts rename packages/cw/{cw-tx.ts => CWTX.ts} (88%) delete mode 100644 packages/cw/cw-base.ts delete mode 100644 packages/cw/cw-staking.ts create mode 100644 packages/tendermint/TM.ts create mode 100644 packages/tendermint/TMBase.ts create mode 100644 packages/tendermint/TMChain.ts create mode 100644 packages/tendermint/package.json create mode 100644 src/API.ts diff --git a/packages/cw/cw.cli.mjs b/packages/cw/CW.cli.mjs similarity index 100% rename from packages/cw/cw.cli.mjs rename to packages/cw/CW.cli.mjs diff --git a/packages/cw/cw.test.ts b/packages/cw/CW.test.ts similarity index 100% rename from packages/cw/cw.test.ts rename to packages/cw/CW.test.ts diff --git a/packages/cw/cw.ts b/packages/cw/CW.ts similarity index 86% rename from packages/cw/cw.ts rename to packages/cw/CW.ts index 96f12afe45f..8de4f87f20c 100644 --- a/packages/cw/cw.ts +++ b/packages/cw/CW.ts @@ -20,26 +20,25 @@ export * as CosmJS from '@hackbg/cosmjs-esm' export { CWError as Error, CWConsole as Console -} from './cw-base' +} from './CWBase' export { CWChain as Chain, CWConnection as Connection, -} from './cw-connection' +} from './CWConnection' export { CWBlock as Block, CWTransaction as Transaction, CWBatch as Batch, -} from './cw-tx' +} from './CWTX' export { CWIdentity as Identity, CWSignerIdentity as SignerIdentity, CWMnemonicIdentity as MnemonicIdentity, encodeSecp256k1Signature -} from './cw-identity' -export * from './cw-chains' -export * as Staking from './cw-staking' - -import { CWChain } from './cw-connection' +} from './CWIdentity' +export * from './CWChains' +export * as Staking from './CWStaking' +import { CWChain } from './CWConnection' export function connect (...args: Parameters) { return CWChain.connect(...args) diff --git a/packages/cw/cw-bank.ts b/packages/cw/CWBank.ts similarity index 93% rename from packages/cw/cw-bank.ts rename to packages/cw/CWBank.ts index 65dafc8d7b6..5047a100eed 100644 --- a/packages/cw/cw-bank.ts +++ b/packages/cw/CWBank.ts @@ -1,8 +1,8 @@ import { optionallyParallel } from '@hackbg/fadroma' import type { CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cosmjs-esm' import type { Address, Token, Chain, Connection, SigningConnection } from '@hackbg/fadroma' -import type { CWChain, CWConnection } from './cw-connection' -import type { CWAgent, CWSigningConnection } from './cw-identity' +import type { CWChain, CWConnection } from './CWConnection' +import type { CWAgent, CWSigningConnection } from './CWIdentity' export async function fetchBalance (chain: CWConnection, { parallel = false, diff --git a/packages/cw/cw-chains.ts b/packages/cw/CWChains.ts similarity index 100% rename from packages/cw/cw-chains.ts rename to packages/cw/CWChains.ts diff --git a/packages/cw/cw-compute.ts b/packages/cw/CWCompute.ts similarity index 97% rename from packages/cw/cw-compute.ts rename to packages/cw/CWCompute.ts index 7061beab762..1d3b67c22de 100644 --- a/packages/cw/cw-compute.ts +++ b/packages/cw/CWCompute.ts @@ -2,8 +2,8 @@ import type { CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cosmjs-esm' import { Connection, SigningConnection, Agent, UploadedCode, Contract } from '@hackbg/fadroma' import type { Address, CodeId, Message, Token } from '@hackbg/fadroma' import { Amino } from '@hackbg/cosmjs-esm' -import type { CWChain, CWConnection } from './cw-connection' -import type { CWAgent, CWSigningConnection } from './cw-identity' +import type { CWChain, CWConnection } from './CWConnection' +import type { CWAgent, CWSigningConnection } from './CWIdentity' export async function fetchCodeInfo ( chain: CWConnection, args: Parameters[0] diff --git a/packages/cw/cw-connection.ts b/packages/cw/CWConnection.ts similarity index 92% rename from packages/cw/cw-connection.ts rename to packages/cw/CWConnection.ts index 5739a5cf76a..8efb807a686 100644 --- a/packages/cw/cw-connection.ts +++ b/packages/cw/CWConnection.ts @@ -1,11 +1,11 @@ import { bold, assign, Chain, Connection } from '@hackbg/fadroma' import type { Address, Message, CodeId, CodeHash, Token, ChainId } from '@hackbg/fadroma' -import { CWAgent, CWSigningConnection, CWIdentity, CWMnemonicIdentity, CWSignerIdentity } from './cw-identity' -import { CWConsole as Console, CWError as Error } from './cw-base' -import { CWBlock, CWBatch } from './cw-tx' -import * as CWBank from './cw-bank' -import * as CWCompute from './cw-compute' -import * as CWStaking from './cw-staking' +import { CWAgent, CWSigningConnection, CWIdentity, CWMnemonicIdentity, CWSignerIdentity } from './CWIdentity' +import { CWConsole as Console, CWError as Error } from './CWBase' +import { CWBlock, CWBatch } from './CWTX' +import * as CWBank from './CWBank' +import * as CWCompute from './CWCompute' +import * as CWStaking from './CWStaking' import { Amino, Proto, CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cosmjs-esm' import type { Block } from '@hackbg/cosmjs-esm' @@ -208,13 +208,13 @@ export class CWConnection extends Connection { return await CWCompute.query(this, ...args) as T } - fetchValidatorsImpl ({ details = false }: { - details?: boolean + fetchValidatorsImpl ({ fetchDetails = false }: { + fetchDetails?: boolean } = {}) { - return this.tendermintClient.then(()=>CWStaking.getValidators(this, { details })) + return this.tendermintClient.then(()=>CWStaking.fetchValidatorList(this, { fetchDetails })) } - fetchValidatorInfoImpl (address: Address): Promise { + fetchValidatorInfoImpl (address: Address): Promise { return Promise.all([ this.queryClient, this.tendermintClient diff --git a/packages/cw/cw-governance.ts b/packages/cw/CWGovernance.ts similarity index 100% rename from packages/cw/cw-governance.ts rename to packages/cw/CWGovernance.ts diff --git a/packages/cw/cw-identity.ts b/packages/cw/CWIdentity.ts similarity index 96% rename from packages/cw/cw-identity.ts rename to packages/cw/CWIdentity.ts index 7f3157c6501..45715ee555d 100644 --- a/packages/cw/cw-identity.ts +++ b/packages/cw/CWIdentity.ts @@ -20,11 +20,11 @@ import { Batch } from '@hackbg/fadroma' import type { Address } from '@hackbg/fadroma' -import { CWError as Error } from './cw-base' -import { CWBatch } from './cw-tx' -import * as CWBank from './cw-bank' -import * as CWCompute from './cw-compute' -import * as CWStaking from './cw-staking' +import { CWError as Error } from './CWBase' +import { CWBatch } from './CWTX' +import * as CWBank from './CWBank' +import * as CWCompute from './CWCompute' +import * as CWStaking from './CWStaking' export class CWAgent extends Agent { constructor (properties: ConstructorParameters[0] & { diff --git a/packages/cw/CWStaking.ts b/packages/cw/CWStaking.ts new file mode 100644 index 00000000000..37ce0e0c7e6 --- /dev/null +++ b/packages/cw/CWStaking.ts @@ -0,0 +1,69 @@ +import { base16, SHA256, Validator } from '@hackbg/fadroma' +import type { Address } from '@hackbg/fadroma' +import { Amino, Proto } from '@hackbg/cosmjs-esm' +import type { CWChain, CWConnection } from './CWConnection' + +export interface CWValidator extends Validator { + readonly chain: CWChain + readonly publicKey: string + readonly publicKeyBytes?: Uint8Array + readonly publicKeyHash?: string + readonly votingPower?: bigint + readonly proposerPriority?: bigint +} + +export async function fetchValidatorList (connection: CWConnection, options: { + pagination?: [number, number], + fetchDetails?: boolean +} = {}): Promise> { + const { pagination, fetchDetails } = options || {} + const tendermintClient = await connection.tendermintClient! + let response + if (pagination && (pagination as Array).length !== 0) { + if (pagination.length !== 2) { + throw new Error("pagination format: [page, per_page]") + } + response = await tendermintClient!.validators({ + page: pagination[0], + per_page: pagination[1], + }) + } else { + response = await tendermintClient!.validatorsAll() + } + // Sort validators by voting power in descending order. + const validators = [...response.validators].sort((a,b)=>( + (a.votingPower < b.votingPower) ? 1 : + (a.votingPower > b.votingPower) ? -1 : 0 + )) + const result: Record = {} + for (const { address, pubkey, votingPower, proposerPriority } of validators) { + let validator: CWValidator = { + chain: connection.chain, + address: base16.encode(address), + publicKey: pubkey?.data, + votingPower, + proposerPriority, + } + if (fetchDetails) { + validator = await fetchValidatorDetails(connection, validator) + } + result[validator.address] = validator + } + return result +} + +export async function fetchValidatorDetails ( + connection: CWConnection, + validator: CWValidator +): Promise { + const request = Proto.Cosmos.Staking.v1beta1.Query.QueryValidatorRequest.encode({ + validatorAddr: validator.address + }).finish() + const value = await connection.abciQuery('/cosmos.staking.v1beta1.Query/Validator', request) + const decoded = Proto.Cosmos.Staking.v1beta1.Query.QueryValidatorResponse.decode(value) + return { ...validator, ...decoded } +} + +export { + CWValidator as Validator +} diff --git a/packages/cw/cw-tx.ts b/packages/cw/CWTX.ts similarity index 88% rename from packages/cw/cw-tx.ts rename to packages/cw/CWTX.ts index 97864945a50..664ed9b3e1a 100644 --- a/packages/cw/cw-tx.ts +++ b/packages/cw/CWTX.ts @@ -1,9 +1,9 @@ import { Block, Batch } from '@hackbg/fadroma' -import { CWError as Error } from './cw-base' +import { CWError as Error } from './CWBase' import { Chain } from '@hackbg/fadroma' import { Transaction } from '@hackbg/fadroma' -import type { CWChain, CWConnection } from './cw-connection' -import type { CWAgent } from './cw-identity' +import type { CWChain, CWConnection } from './CWConnection' +import type { CWAgent } from './CWIdentity' type CWBlockParameters = ConstructorParameters[0] & Partial> diff --git a/packages/cw/archway/archway.ts b/packages/cw/archway/archway.ts index 66eaf440eea..f596f0caaf0 100644 --- a/packages/cw/archway/archway.ts +++ b/packages/cw/archway/archway.ts @@ -1,6 +1,6 @@ -import { CLI } from '../cw-base' -import { CWConnection } from '../cw-connection' -import { CWMnemonicIdentity } from '../cw-identity' +import { CLI } from '../CWBase' +import { CWConnection } from '../CWConnection' +import { CWMnemonicIdentity } from '../CWIdentity' class ArchwayCLI extends CLI {} diff --git a/packages/cw/axelar/axelar.ts b/packages/cw/axelar/axelar.ts index 8e51ee21be2..3953671f3a8 100644 --- a/packages/cw/axelar/axelar.ts +++ b/packages/cw/axelar/axelar.ts @@ -1,6 +1,6 @@ -import { CLI } from '../cw-base' -import { CWConnection } from '../cw-connection' -import { CWMnemonicIdentity } from '../cw-identity' +import { CLI } from '../CWBase' +import { CWConnection } from '../CWConnection' +import { CWMnemonicIdentity } from '../CWIdentity' class AxelarCLI extends CLI {} diff --git a/packages/cw/cw-base.ts b/packages/cw/cw-base.ts deleted file mode 100644 index 4c7ab6de05e..00000000000 --- a/packages/cw/cw-base.ts +++ /dev/null @@ -1,21 +0,0 @@ -import CLI from '@hackbg/cmds' -import { Error, Console } from '@hackbg/fadroma' - -export class CWError extends Error {} - -export class CWConsole extends Console { - constructor (label: string = '@fadroma/cw') { - super(label) - } -} - -class CWBaseCLI extends CLI { - constructor (...args: ConstructorParameters) { - super(...args) - this.log.label = `` - } -} - -export { - CWBaseCLI as CLI -} diff --git a/packages/cw/cw-staking.ts b/packages/cw/cw-staking.ts deleted file mode 100644 index 6c6ecd2e8eb..00000000000 --- a/packages/cw/cw-staking.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { base16, SHA256, Validator } from '@hackbg/fadroma' -import type { Address } from '@hackbg/fadroma' -import { Amino, Proto } from '@hackbg/cosmjs-esm' -import type { CWChain, CWConnection } from './cw-connection' - -export async function getValidators ( - connection: CWConnection, - { pagination, details, Validator = CWValidator as V }: { - pagination?: [number, number], - details?: boolean, - Validator?: V - } = {} -): Promise>> { - const tendermintClient = await connection.tendermintClient! - let response - if (pagination && (pagination as Array).length !== 0) { - if (pagination.length !== 2) { - throw new Error("pagination format: [page, per_page]") - } - response = await tendermintClient!.validators({ - page: pagination[0], - per_page: pagination[1], - }) - } else { - response = await tendermintClient!.validatorsAll() - } - // Sort validators by voting power in descending order. - const validators = [...response.validators].sort((a,b)=>( - (a.votingPower < b.votingPower) ? 1 : - (a.votingPower > b.votingPower) ? -1 : 0 - )) - const result: Array> = [] - for (const { address, pubkey, votingPower, proposerPriority } of validators) { - const info = new Validator({ - chain: connection.chain, - address: base16.encode(address), - publicKey: pubkey?.data, - votingPower, - proposerPriority, - }) as InstanceType - result.push(info) - if (details) { - await info.fetchDetails() - } - } - return result -} - -class CWValidator extends Validator { - constructor ({ - publicKey, votingPower, proposerPriority, ...properties - }: ConstructorParameters[0] & { - publicKey?: string|Uint8Array|Array - votingPower?: string|number|bigint - proposerPriority?: string|number|bigint - }) { - super(properties) - if ((publicKey instanceof Uint8Array)||(publicKey instanceof Array)) { - publicKey = base16.encode(new Uint8Array(publicKey)) - } - this.publicKey = publicKey! - if (votingPower) { - this.votingPower = BigInt(votingPower) - } - if (proposerPriority) { - this.proposerPriority = BigInt(proposerPriority) - } - } - publicKey: string - votingPower?: bigint - proposerPriority?: bigint - get publicKeyBytes () { - return base16.decode(this.publicKey) - } - get publicKeyHash () { - return base16.encode(SHA256(this.publicKeyBytes).slice(0, 20)) - } - get chain (): CWChain { - return super.chain as unknown as CWChain - } - - async fetchDetails (): Promise { - const request = Proto.Cosmos.Staking.v1beta1.Query.QueryValidatorRequest.encode({ - validatorAddr: this.address - }).finish() - const value = await this.chain.getConnection().abciQuery( - '/cosmos.staking.v1beta1.Query/Validator', - request - ) - const decoded = Proto.Cosmos.Staking.v1beta1.Query.QueryValidatorResponse.decode(value) - return this - } -} - -export { - CWValidator as Validator -} diff --git a/packages/cw/injective/injective.ts b/packages/cw/injective/injective.ts index 89bf1e7947c..610bbbaf4d9 100644 --- a/packages/cw/injective/injective.ts +++ b/packages/cw/injective/injective.ts @@ -1,6 +1,6 @@ -import { CLI } from '../cw-base' -import { CWConnection } from '../cw-connection' -import { CWMnemonicIdentity } from '../cw-identity' +import { CLI } from '../CWBase' +import { CWConnection } from '../CWConnection' +import { CWMnemonicIdentity } from '../CWIdentity' class InjectiveCLI extends CLI {} diff --git a/packages/cw/okp4/okp4.ts b/packages/cw/okp4/okp4.ts index d91d7f06bc1..9f9df02234c 100644 --- a/packages/cw/okp4/okp4.ts +++ b/packages/cw/okp4/okp4.ts @@ -1,7 +1,6 @@ -import { CLI } from '../cw-base' -import { CWError as Error } from '../cw-base' -import { CWChain, CWConnection } from '../cw-connection' -import { CWMnemonicIdentity } from '../cw-identity' +import { CLI, CWError as Error } from '../CWBase' +import { CWChain, CWConnection } from '../CWConnection' +import { CWMnemonicIdentity } from '../CWIdentity' import { Objectarium, objectariumCodeIds } from './okp4-objectarium' import { Cognitarium, cognitariumCodeIds } from './okp4-cognitarium' diff --git a/packages/cw/osmosis/osmosis.ts b/packages/cw/osmosis/osmosis.ts index 691c936f517..48ae7915fb8 100644 --- a/packages/cw/osmosis/osmosis.ts +++ b/packages/cw/osmosis/osmosis.ts @@ -1,6 +1,6 @@ -import { CLI } from '../cw-base' -import { CWConnection } from '../cw-connection' -import { CWMnemonicIdentity } from '../cw-identity' +import { CLI } from '../CWBase' +import { CWConnection } from '../CWConnection' +import { CWMnemonicIdentity } from '../CWIdentity' class OsmosisCLI extends CLI {} diff --git a/packages/cw/package.json b/packages/cw/package.json index 00257e41660..d1d1728d03f 100644 --- a/packages/cw/package.json +++ b/packages/cw/package.json @@ -20,10 +20,7 @@ "description": "CosmJS Stargate integration for Fadroma.", "dependencies": { "@hackbg/cosmjs-esm": "workspace:*", - "@hackbg/cmds": "workspace:*", - "@hackbg/borshest": "workspace:*", - "borsh": "^2.0.0", - "borsher": "^1.2.1" + "@hackbg/cmds": "workspace:*" }, "peerDependencies": { "@hackbg/fadroma": "workspace:*" diff --git a/packages/cw/tsconfig.json b/packages/cw/tsconfig.json index 48785e6e8ff..54f84d9483c 100644 --- a/packages/cw/tsconfig.json +++ b/packages/cw/tsconfig.json @@ -1,9 +1,9 @@ { "files": [ - "cw.ts", - "cw-base.ts", - "cw-identity.ts", - "cw-connection.ts", + "CW.ts", + "CWBase.ts", + "CWIdentity.ts", + "CWConnection.ts", "okp4/okp4.ts", "okp4/okp4-cognitarium.ts", "okp4/okp4-law-stone.ts", diff --git a/packages/tendermint/TM.ts b/packages/tendermint/TM.ts new file mode 100644 index 00000000000..87dad7be5fb --- /dev/null +++ b/packages/tendermint/TM.ts @@ -0,0 +1,22 @@ +export * as CosmJS from '@hackbg/cosmjs-esm' +export * from './TMBase' +export * from './TMChain' + +export { + CWBlock as Block, + CWTransaction as Transaction, + CWBatch as Batch, +} from './CWTX' +export { + CWIdentity as Identity, + CWSignerIdentity as SignerIdentity, + CWMnemonicIdentity as MnemonicIdentity, + encodeSecp256k1Signature +} from './CWIdentity' +export * from './CWChains' +export * as Staking from './CWStaking' +import { CWChain } from './CWConnection' + +export function connect (...args: Parameters) { + return CWChain.connect(...args) +} diff --git a/packages/tendermint/TMBase.ts b/packages/tendermint/TMBase.ts new file mode 100644 index 00000000000..1aa375b2d3a --- /dev/null +++ b/packages/tendermint/TMBase.ts @@ -0,0 +1,16 @@ +import { Error, Console, CLI } from '@hackbg/fadroma' + +/** Base class for console loggers belonging to this package. */ +class FadromaTendermintConsole extends Console { label = '@fadroma/tendermint' } + +/** Base class for command-line interfaces belonging to this package. */ +class FadromaTendermintBaseCLI extends CLI {} + +/** Base class for errors thrown by this package. */ +class FadromaTendermintError extends Error {} + +export { + FadromaTendermintError as Error, + FadromaTendermintConsole as Console, + FadromaTendermintBaseCLI as CLI +} diff --git a/packages/tendermint/TMChain.ts b/packages/tendermint/TMChain.ts new file mode 100644 index 00000000000..24429544037 --- /dev/null +++ b/packages/tendermint/TMChain.ts @@ -0,0 +1,241 @@ +import { bold, assign, Chain, Connection } from '@hackbg/fadroma' +import type { Address, Message, CodeId, CodeHash, Token, ChainId } from '@hackbg/fadroma' +import { Console, Error } from './TMBase' + +import { CWAgent, CWSigningConnection, CWIdentity, CWMnemonicIdentity, CWSignerIdentity } from './CWIdentity' +import { CWBlock, CWBatch } from './CWTX' +import * as CWBank from './CWBank' +import * as CWCompute from './CWCompute' +import * as CWStaking from './CWStaking' + +import { Amino, Proto, CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cosmjs-esm' +import type { Block } from '@hackbg/cosmjs-esm' + +class TendermintChain extends Chain { + constructor ( + properties: ConstructorParameters[0] + & Pick + ) { + super(properties) + this.coinType = properties.coinType + this.bech32Prefix = properties.bech32Prefix + this.hdAccountIndex = properties.hdAccountIndex + this.connections = properties.connections + } + + static async connect ( + properties: ({ url: string|URL }|{ urls: Iterable }) & { + chainId?: ChainId, + bech32Prefix?: string + coinType?: number, + hdAccountIndex?: number + } + ): Promise { + const { + chainId, url, urls = [ url ], bech32Prefix, coinType, hdAccountIndex + } = (properties || {}) as any + const chain = new this({ + chainId, + connections: [], + bech32Prefix, + coinType, + hdAccountIndex, + }) + const connections: TendermintConnection[] = urls + .filter(Boolean) + .map(async (url: string|URL)=>new this.Connection({ + api: await CosmWasmClient.connect(String(url)), + chain, + url: String(url) + })) + if (connections.length === 0) { + new Console(this.constructor.name).warn( + 'No connection URLs provided. RPC operations will fail.' + ) + } + chain.connections = await Promise.all(connections) + return chain + } + + /** The bech32 prefix for the account's address */ + bech32Prefix: string + /** The coin type in the HD derivation path */ + coinType?: number + /** The account index in the HD derivation path */ + hdAccountIndex?: number + + connections: TendermintConnection[] + getConnection (): TendermintConnection { + if (!this.connections[0]) { + throw new Error('No active connection.') + } + return this.connections[0] + } + + async authenticate ( + ...args: Parameters + ): Promise { + let identity: CWIdentity + if (!args[0]) { + identity = new CWMnemonicIdentity({ + bech32Prefix: this.bech32Prefix, + coinType: this.coinType, + hdAccountIndex: this.hdAccountIndex + }) + } else if (typeof (args[0] as any).mnemonic === 'string') { + identity = new CWMnemonicIdentity({ + mnemonic: (args[0] as any).mnemonic, + bech32Prefix: this.bech32Prefix, + coinType: this.coinType, + hdAccountIndex: this.hdAccountIndex + }) + } else if (args[0] instanceof CWIdentity) { + identity = args[0] + } else if (typeof args[0] === 'object') { + identity = new CWIdentity(args[0] as Partial) + } else { + throw Object.assign(new Error('Invalid arguments'), { args }) + } + return new CWAgent({ + chain: this, + identity, + connection: new CWSigningConnection({ + chain: this, + identity, + api: await SigningCosmWasmClient.connectWithSigner( + this.getConnection().url, + identity.signer, + ) + }) + }) + } +} + +interface TendermintConnection extends Connection { + readonly api: CosmWasmClient + readonly queryClient: ReturnType + readonly tendermintClient: ReturnType + abciQuery (path: string, params?: Uint8Array): Promise +} + +/** Read-only client for CosmWasm-enabled chains. */ +//class TendermintConnection extends Connection { + //[>* API connects asynchronously, so API handle is a promise. <] + //declare api: CosmWasmClient + + //constructor (properties: ConstructorParameters[0] & { api: CosmWasmClient }) { + //super(properties) + //this.api = properties.api + ////if (!this.url) { + ////throw new Error('No connection URL.') + ////} + ////if (this.identity?.signer) { + ////this.log.debug('Connecting and authenticating via', bold(this.url)) + ////this.api = SigningCosmWasmClient.connectWithSigner( + ////this.url, + ////this.identity.signer + ////) + ////} else { + ////this.log.debug('Connecting anonymously via', bold(this.url)) + ////this.api = CosmWasmClient.connect(this.url) + ////} + //} + + //[>* Handle to the API's internal query client. <] + //get queryClient (): Promise> { + //return Promise.resolve(this.api).then(api=>(api as any)?.queryClient) + //} + + //[>* Handle to the API's internal Tendermint transaction client. <] + //get tendermintClient (): Promise> { + //return Promise.resolve(this.api).then(api=>(api as any)?.tmClient) + //} + + //abciQuery (path: string, params = new Uint8Array()) { + //return this.queryClient.then(async client=>{ + //this.log.debug('ABCI query:', path) + //const { value } = await client!.queryAbci(path, params) + //return value + //}) + //} + + //async fetchBlockImpl (parameter?: { height: number|bigint }|{ hash: string }): + //Promise + //{ + //const api = await this.api + //if ((parameter as { height: number|bigint })?.height) { + //const { id, header, txs } = await api.getBlock((parameter as { height: number }).height) + //return new CWBlock({ + //chain: this.chain, + //hash: id, + //height: BigInt(header.height), + //transactions: [], + //rawTransactions: txs as Uint8Array[], + //}) + //} else if ((parameter as { hash: string })?.hash) { + //throw new Error('TendermintConnection.fetchBlock({ hash }): unimplemented!') + //} else { + //const { id, header, txs } = await api.getBlock() + //return new CWBlock({ + //chain: this.chain, + //hash: id, + //height: BigInt(header.height), + //transactions: [], + //rawTransactions: txs as Uint8Array[], + //}) + //} + //} + + //async fetchHeightImpl () { + //const { height } = await this.fetchBlockImpl() + //return height! + //} + + //[>* Query native token balance. <] + //fetchBalanceImpl (...args: Parameters) { + //return CWBank.fetchBalance(this, ...args) + //} + + //fetchCodeInfoImpl (...args: Parameters) { + //return CWCompute.fetchCodeInfo(this, ...args) + //} + + //fetchCodeInstancesImpl (...args: Parameters) { + //return CWCompute.fetchCodeInstances(this, ...args) + + //} + + //fetchContractInfoImpl (...args: Parameters) { + //return CWCompute.fetchContractInfo(this, ...args) + //} + + //override async queryImpl ( + //...args: Parameters + //) { + //return await CWCompute.query(this, ...args) as T + //} + + //fetchValidatorsImpl ({ fetchDetails = false }: { + //fetchDetails?: boolean + //} = {}) { + //return this.tendermintClient.then(()=>CWStaking.fetchValidatorList(this, { fetchDetails })) + //} + + //fetchValidatorInfoImpl (address: Address): Promise { + //return Promise.all([ + //this.queryClient, + //this.tendermintClient + //]).then(()=>new CWStaking.Validator({ + //chain: this.chain, + //address + //}).fetchDetails()) + //} +//} + +export { + TendermintChain as Chain, +} + +export type { + TendermintConnection as Connection, +} diff --git a/packages/tendermint/package.json b/packages/tendermint/package.json new file mode 100644 index 00000000000..d8af5c86fe2 --- /dev/null +++ b/packages/tendermint/package.json @@ -0,0 +1,32 @@ +{ + "name": "@fadroma/tendermint", + "type": "module", + "main": "TM.ts", + "files": [ "*.ts" ], + "version": "0.1.0", + "license": "AGPL-3.0-only", + "keywords": [ + "tendermint", "cosmwasm", "interchain", "cosmos", "cosmjs", "stargate" + ], + "description": "Tendermint integration for Fadroma using CosmJS Stargate.", + "dependencies": { + "@hackbg/cosmjs-esm": "workspace:*" + }, + "peerDependencies": { + "@hackbg/fadroma": "workspace:*" + }, + "devDependencies": { + "@hackbg/fadroma": "workspace:*" + }, + "scripts": { + "check": "time tsc --noEmit", + "test": "time ensuite cw.test.ts", + "cov": "time ensuite-cov -r text -r lcov -- cw.test.ts", + "schema": "./schema/cw-gen-types.cjs", + "clean": "rm -rf .ubik *.dist.*", + + "release": "time sh -c 'pnpm clean && pnpm i && pnpm check && pnpm cov all && ubik release --otp 123123'", + "release:fast": "time sh -c 'pnpm clean && pnpm i && pnpm check && ubik release --otp 123123'", + "release:faster": "time sh -c 'pnpm clean && pnpm i && ubik release --otp 123123'" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e18caea3fd1..ccae752ac34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,7 +125,7 @@ importers: version: 8.0.3 lint-staged: specifier: ^13.3.0 - version: 13.3.0 + version: 13.3.0(enquirer@2.4.1) typedoc: specifier: 0.25.13 version: 0.25.13(typescript@5.3.3) @@ -195,21 +195,12 @@ importers: packages/cw: dependencies: - '@hackbg/borshest': - specifier: workspace:* - version: link:../../toolbox/borshest '@hackbg/cmds': specifier: workspace:* version: link:../../toolbox/cmds '@hackbg/cosmjs-esm': specifier: workspace:* version: link:../../vendored/cosmjs-esm - borsh: - specifier: ^2.0.0 - version: 2.0.0 - borsher: - specifier: ^1.2.1 - version: 1.2.1 devDependencies: '@hackbg/fadroma': specifier: workspace:* @@ -325,6 +316,16 @@ importers: specifier: workspace:* version: link:../.. + packages/tendermint: + dependencies: + '@hackbg/cosmjs-esm': + specifier: workspace:* + version: link:../../vendored/cosmjs-esm + devDependencies: + '@hackbg/fadroma': + specifier: workspace:* + version: link:../.. + toolbox: devDependencies: '@ganesha/esbuild': @@ -347,7 +348,7 @@ importers: version: 8.0.3 lint-staged: specifier: ^13.3.0 - version: 13.3.0 + version: 13.3.0(enquirer@2.4.1) typedoc: specifier: ^0.25.4 version: 0.25.4(typescript@5.3.3) @@ -744,7 +745,7 @@ importers: version: 5.2.1 isomorphic-ws: specifier: ^4.0.1 - version: 4.0.1(ws@7.5.9) + version: 4.0.1(ws@8.14.2) libsodium-wrappers-sumo: specifier: ^0.7.13 version: 0.7.13 @@ -805,7 +806,7 @@ importers: version: 6.0.4 '@typescript-eslint/eslint-plugin': specifier: ^5.62.0 - version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint@7.32.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: ^5.62.0 version: 5.62.0(eslint@7.32.0)(typescript@5.3.3) @@ -823,10 +824,10 @@ importers: version: 0.3.9 eslint-plugin-import: specifier: ^2.29.0 - version: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) + version: 2.29.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint@7.32.0) eslint-plugin-prettier: specifier: ^3.4.1 - version: 3.4.1(eslint-config-prettier@8.10.0)(eslint@7.32.0)(prettier@2.8.8) + version: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8) eslint-plugin-simple-import-sort: specifier: ^7.0.0 version: 7.0.0(eslint@7.32.0) @@ -856,7 +857,7 @@ importers: version: 5.1.0(karma@6.4.2) karma-jasmine-html-reporter: specifier: ^1.7.0 - version: 1.7.0(karma-jasmine@5.1.0)(karma@6.4.2) + version: 1.7.0(jasmine-core@4.6.0)(karma-jasmine@5.1.0(karma@6.4.2))(karma@6.4.2) nyc: specifier: ^15.1.0 version: 15.1.0 @@ -1104,10 +1105,10 @@ importers: version: 1.0.3 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + version: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) node-polyfill-webpack-plugin: specifier: 1.1.4 - version: 1.1.4(webpack@5.76.0) + version: 1.1.4(webpack@5.76.0(webpack-cli@4.9.2)) prettier: specifier: 2.5.1 version: 2.5.1 @@ -1116,10 +1117,10 @@ importers: version: 14.0.0 ts-jest: specifier: 29.1.1 - version: 29.1.1(jest@29.7.0)(typescript@5.3.3) + version: 29.1.1(@babel/core@7.23.3)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.3))(jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)))(typescript@5.3.3) ts-loader: specifier: 9.2.6 - version: 9.2.6(typescript@5.3.3)(webpack@5.76.0) + version: 9.2.6(typescript@5.3.3)(webpack@5.76.0(webpack-cli@4.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@18.11.2)(typescript@5.3.3) @@ -1131,10 +1132,10 @@ importers: version: 0.22.11(typescript@5.3.3) typedoc-plugin-extras: specifier: 2.2.3 - version: 2.2.3(typedoc@0.22.11) + version: 2.2.3(typedoc@0.22.11(typescript@5.3.3)) typedoc-plugin-missing-exports: specifier: 0.22.6 - version: 0.22.6(typedoc@0.22.11) + version: 0.22.6(typedoc@0.22.11(typescript@5.3.3)) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -2685,12 +2686,6 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - borsh@2.0.0: - resolution: {integrity: sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==} - - borsher@1.2.1: - resolution: {integrity: sha512-vP5g3q2BWxqjgGuQeLKJr1ymr3yvFDSFAtN+UZa5L1BB82eFCYnrLCEj+6uiJnzhtJ7IXZfYUYu4EYBnI949Xg==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -6675,7 +6670,7 @@ packages: babel-jest: ^29.0.0 esbuild: '*' jest: ^29.0.0 - typescript: '>=4.3 <6 || ^5' + typescript: '>=4.3 <6' peerDependenciesMeta: '@babel/core': optional: true @@ -6700,7 +6695,7 @@ packages: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' '@types/node': '*' - typescript: '>=2.7 || ^5' + typescript: '>=2.7' peerDependenciesMeta: '@swc/core': optional: true @@ -6712,7 +6707,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true peerDependencies: - typescript: '>=2.7 || ^5' + typescript: '>=2.7' ts-poet@4.15.0: resolution: {integrity: sha512-sLLR8yQBvHzi9d4R1F4pd+AzQxBfzOSSjfxiJxQhkUoH5bL7RsAC6wgvtVUQdGqiCsyS9rT6/8X2FI7ipdir5g==} @@ -6739,18 +6734,18 @@ packages: deprecated: TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information. hasBin: true peerDependencies: - typescript: '>=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev || ^5' + typescript: '>=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev' tsutils@2.29.0: resolution: {integrity: sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==} peerDependencies: - typescript: '>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev || ^5' + typescript: '>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev' tsutils@3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta || ^5' + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' tty-browserify@0.0.1: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} @@ -6840,21 +6835,21 @@ packages: engines: {node: '>= 12.10.0'} hasBin: true peerDependencies: - typescript: 4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || ^5 + typescript: 4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x typedoc@0.25.13: resolution: {integrity: sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==} engines: {node: '>= 16'} hasBin: true peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || ^5 + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x typedoc@0.25.4: resolution: {integrity: sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==} engines: {node: '>= 16'} hasBin: true peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || ^5 + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} @@ -7968,6 +7963,7 @@ snapshots: '@ganesha/ts': 2.0.1(typescript@5.3.3) cross-spawn: 7.0.3 pkg-up: 4.0.0 + optionalDependencies: typescript: 5.3.3 '@hackbg/ubik@2.0.8': @@ -8052,7 +8048,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.1)': + '@jest/core@29.7.0(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -8066,7 +8062,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.20)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.11.20)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -8567,7 +8563,7 @@ snapshots: '@types/zen-observable@0.8.7': {} - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint@7.32.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) @@ -8581,6 +8577,7 @@ snapshots: natural-compare-lite: 1.4.0 semver: 7.5.4 tsutils: 3.21.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -8592,6 +8589,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) debug: 4.3.4 eslint: 7.32.0 + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -8608,6 +8606,7 @@ snapshots: debug: 4.3.4 eslint: 7.32.0 tsutils: 3.21.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -8623,6 +8622,7 @@ snapshots: is-glob: 4.0.3 semver: 7.5.4 tsutils: 3.21.0(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -8801,31 +8801,31 @@ snapshots: '@webassemblyjs/ast': 1.11.6 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.89.0)': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack@5.89.0))(webpack@5.89.0(webpack-cli@4.10.0))': dependencies: webpack: 5.89.0(webpack-cli@4.10.0) webpack-cli: 4.10.0(webpack@5.89.0) - '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.2)(webpack@5.76.0)': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.2(webpack@5.76.0))(webpack@5.76.0(webpack-cli@4.9.2))': dependencies: webpack: 5.76.0(webpack-cli@4.9.2) webpack-cli: 4.9.2(webpack@5.76.0) - '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)': + '@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack@5.89.0))': dependencies: envinfo: 7.10.0 webpack-cli: 4.10.0(webpack@5.89.0) - '@webpack-cli/info@1.5.0(webpack-cli@4.9.2)': + '@webpack-cli/info@1.5.0(webpack-cli@4.9.2(webpack@5.76.0))': dependencies: envinfo: 7.10.0 webpack-cli: 4.9.2(webpack@5.76.0) - '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)': + '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0(webpack@5.89.0))': dependencies: webpack-cli: 4.10.0(webpack@5.89.0) - '@webpack-cli/serve@1.7.0(webpack-cli@4.9.2)': + '@webpack-cli/serve@1.7.0(webpack-cli@4.9.2(webpack@5.76.0))': dependencies: webpack-cli: 4.9.2(webpack@5.76.0) @@ -9320,13 +9320,6 @@ snapshots: transitivePeerDependencies: - supports-color - borsh@2.0.0: {} - - borsher@1.2.1: - dependencies: - borsh: 2.0.0 - buffer: 6.0.3 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -9927,13 +9920,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1): + create-jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -10450,18 +10443,18 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0): + eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint@7.32.0): dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 @@ -10470,7 +10463,7 @@ snapshots: doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -10480,17 +10473,20 @@ snapshots: object.values: 1.1.7 semver: 6.3.1 tsconfig-paths: 3.14.2 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0)(eslint@7.32.0)(prettier@2.8.8): + eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8): dependencies: eslint: 7.32.0 - eslint-config-prettier: 8.10.0(eslint@7.32.0) prettier: 2.8.8 prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 8.10.0(eslint@7.32.0) eslint-plugin-simple-import-sort@7.0.0(eslint@7.32.0): dependencies: @@ -11716,6 +11712,10 @@ snapshots: dependencies: ws: 7.5.9 + isomorphic-ws@4.0.1(ws@8.14.2): + dependencies: + ws: 8.14.2 + istanbul-lib-coverage@3.2.0: {} istanbul-lib-coverage@3.2.2: {} @@ -11830,16 +11830,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.11.2)(ts-node@10.9.1): + jest-cli@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -11849,12 +11849,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.11.2)(ts-node@10.9.1): + jest-config@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)): dependencies: '@babel/core': 7.23.3 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.11.2 babel-jest: 29.7.0(@babel/core@7.23.3) chalk: 4.1.2 ci-info: 3.9.0 @@ -11874,17 +11873,18 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 18.11.2 ts-node: 10.9.1(@types/node@18.11.2)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.11.20)(ts-node@10.9.1): + jest-config@29.7.0(@types/node@20.11.20)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)): dependencies: '@babel/core': 7.23.3 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.20 babel-jest: 29.7.0(@babel/core@7.23.3) chalk: 4.1.2 ci-info: 3.9.0 @@ -11904,6 +11904,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.11.20 ts-node: 10.9.1(@types/node@18.11.2)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros @@ -11995,7 +11997,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -12139,12 +12141,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1): + jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -12244,8 +12246,9 @@ snapshots: is-wsl: 2.2.0 which: 2.0.2 - karma-jasmine-html-reporter@1.7.0(karma-jasmine@5.1.0)(karma@6.4.2): + karma-jasmine-html-reporter@1.7.0(jasmine-core@4.6.0)(karma-jasmine@5.1.0(karma@6.4.2))(karma@6.4.2): dependencies: + jasmine-core: 4.6.0 karma: 6.4.2 karma-jasmine: 5.1.0(karma@6.4.2) @@ -12381,14 +12384,14 @@ snapshots: dependencies: uc.micro: 1.0.6 - lint-staged@13.3.0: + lint-staged@13.3.0(enquirer@2.4.1): dependencies: chalk: 5.3.0 commander: 11.0.0 debug: 4.3.4 execa: 7.2.0 lilconfig: 2.1.0 - listr2: 6.6.1 + listr2: 6.6.1(enquirer@2.4.1) micromatch: 4.0.5 pidtree: 0.6.0 string-argv: 0.3.2 @@ -12397,7 +12400,7 @@ snapshots: - enquirer - supports-color - listr2@6.6.1: + listr2@6.6.1(enquirer@2.4.1): dependencies: cli-truncate: 3.1.0 colorette: 2.0.20 @@ -12405,6 +12408,8 @@ snapshots: log-update: 5.0.1 rfdc: 1.3.0 wrap-ansi: 8.1.0 + optionalDependencies: + enquirer: 2.4.1 load-json-file@1.1.0: dependencies: @@ -12819,7 +12824,7 @@ snapshots: node-int64@0.4.0: {} - node-polyfill-webpack-plugin@1.1.4(webpack@5.76.0): + node-polyfill-webpack-plugin@1.1.4(webpack@5.76.0(webpack-cli@4.9.2)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -14224,7 +14229,7 @@ snapshots: merge-stream: 2.0.0 through2: 3.0.2 - terser-webpack-plugin@5.3.9(webpack@5.76.0): + terser-webpack-plugin@5.3.9(webpack@5.76.0(webpack-cli@4.9.2)): dependencies: '@jridgewell/trace-mapping': 0.3.20 jest-worker: 27.5.1 @@ -14233,7 +14238,7 @@ snapshots: terser: 5.24.0 webpack: 5.76.0(webpack-cli@4.9.2) - terser-webpack-plugin@5.3.9(webpack@5.89.0): + terser-webpack-plugin@5.3.9(webpack@5.89.0(webpack-cli@4.10.0)): dependencies: '@jridgewell/trace-mapping': 0.3.20 jest-worker: 27.5.1 @@ -14348,11 +14353,11 @@ snapshots: triple-beam@1.3.0: {} - ts-jest@29.1.1(jest@29.7.0)(typescript@5.3.3): + ts-jest@29.1.1(@babel/core@7.23.3)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.3))(jest@29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)))(typescript@5.3.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.11.2)(ts-node@10.9.1(@types/node@18.11.2)(typescript@5.3.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -14360,8 +14365,12 @@ snapshots: semver: 7.5.4 typescript: 5.3.3 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.23.3 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.23.3) - ts-loader@9.2.6(typescript@5.3.3)(webpack@5.76.0): + ts-loader@9.2.6(typescript@5.3.3)(webpack@5.76.0(webpack-cli@4.9.2)): dependencies: chalk: 4.1.2 enhanced-resolve: 5.15.0 @@ -14522,11 +14531,11 @@ snapshots: typedarray@0.0.6: {} - typedoc-plugin-extras@2.2.3(typedoc@0.22.11): + typedoc-plugin-extras@2.2.3(typedoc@0.22.11(typescript@5.3.3)): dependencies: typedoc: 0.22.11(typescript@5.3.3) - typedoc-plugin-missing-exports@0.22.6(typedoc@0.22.11): + typedoc-plugin-missing-exports@0.22.6(typedoc@0.22.11(typescript@5.3.3)): dependencies: typedoc: 0.22.11(typescript@5.3.3) @@ -14781,9 +14790,9 @@ snapshots: webpack-cli@4.10.0(webpack@5.89.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.89.0) - '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack@5.89.0))(webpack@5.89.0(webpack-cli@4.10.0)) + '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0(webpack@5.89.0)) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0(webpack@5.89.0)) colorette: 2.0.20 commander: 7.2.0 cross-spawn: 7.0.3 @@ -14797,9 +14806,9 @@ snapshots: webpack-cli@4.9.2(webpack@5.76.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.2)(webpack@5.76.0) - '@webpack-cli/info': 1.5.0(webpack-cli@4.9.2) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.2) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.2(webpack@5.76.0))(webpack@5.76.0(webpack-cli@4.9.2)) + '@webpack-cli/info': 1.5.0(webpack-cli@4.9.2(webpack@5.76.0)) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.2(webpack@5.76.0)) colorette: 2.0.20 commander: 7.2.0 execa: 5.1.1 @@ -14841,10 +14850,11 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.76.0) + terser-webpack-plugin: 5.3.9(webpack@5.76.0(webpack-cli@4.9.2)) watchpack: 2.4.0 - webpack-cli: 4.9.2(webpack@5.76.0) webpack-sources: 3.2.3 + optionalDependencies: + webpack-cli: 4.9.2(webpack@5.76.0) transitivePeerDependencies: - '@swc/core' - esbuild @@ -14873,10 +14883,11 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(webpack@5.89.0(webpack-cli@4.10.0)) watchpack: 2.4.0 - webpack-cli: 4.10.0(webpack@5.89.0) webpack-sources: 3.2.3 + optionalDependencies: + webpack-cli: 4.10.0(webpack@5.89.0) transitivePeerDependencies: - '@swc/core' - esbuild diff --git a/src/API.ts b/src/API.ts new file mode 100644 index 00000000000..5fbb05231de --- /dev/null +++ b/src/API.ts @@ -0,0 +1,274 @@ +import type { + Address, ChainId, Uint128, CodeId, UploadedCode, Contract, Message, Token, Logged, Into, + UploadStore, CompiledCode +} from '../index' + +/** Represents an instance of a blockchain. */ +export interface Chain extends Logged { + /** Unique identifier of chain. */ + readonly id: ChainId + /** Get a read-only connection to the API endpoint. */ + getConnection (): + Connection + /** Authenticate to the chain, obtaining an Agent instance that can send transactions. */ + authenticate (identity?: { mnemonic: string }|Identity): + Promise + /** Get the current block height. */ + fetchHeight (): + Promise + /** Wait until the block height increments, or until `this.alive` is set to false. */ + fetchNextBlock (): + Promise + /** Get info about the latest block. */ + fetchBlock (): + Promise + /** Get info about the block with a specific height. */ + fetchBlock ({ height }: { height: number|bigint, raw?: boolean }): + Promise + /** Get info about the block with a specific hash. */ + fetchBlock ({ hash }: { hash: string, raw?: boolean }): + Promise + /** Fetch balance of 1 address in 1 token. */ + fetchBalance (address: Address, token: string): + Promise + /** Fetch balance of 1 address in multiple (or all) tokens. */ + fetchBalance (address: Address, tokens?: string[]): + Promise> + /** Fetch balance of multiple addresses in 1 token. */ + fetchBalance (addresses: Address[], token: string): + Promise> + /** Fetch balance of multiple addresses in multiple (or all) tokens. */ + fetchBalance (addresses: Address[], tokens?: string): + Promise>> + /** Fetch info about all code IDs uploaded to the chain. */ + fetchCodeInfo (): + Promise> + /** Fetch info about a single code ID. */ + fetchCodeInfo (codeId: CodeId, options?: { parallel?: boolean }): + Promise + /** Fetch info about multiple code IDs. */ + fetchCodeInfo (codeIds: Iterable, options?: { parallel?: boolean }): + Promise> + /** Fetch all instances of a code ID. */ + fetchCodeInstances (codeId: CodeId): + Promise> + /** Fetch all instances of a code ID, with custom client class. */ + fetchCodeInstances (Contract: C, codeId: CodeId): + Promise>> + /** Fetch all instances of multple code IDs. */ + fetchCodeInstances (codeIds: Iterable, options?: { parallel?: boolean }): + Promise>> + /** Fetch all instances of multple code IDs, with custom client class. */ + fetchCodeInstances ( + Contract: C, codeIds: Iterable, options?: { parallel?: boolean } + ): Promise>>> + /** Fetch all instances of multple code IDs, with multiple custom client classes. */ + fetchCodeInstances (codeIds: { [id: CodeId]: typeof Contract }, options?: { parallel?: boolean }): + Promise<{[x in keyof typeof codeIds]: Record>}> + /** Fetch a contract's details wrapped in a `Contract` instance. */ + fetchContractInfo (address: Address): Promise + /** Fetch a contract's details wrapped in a custom class instance. */ + fetchContractInfo (Contract: T, address: Address): + Promise> + /** Fetch multiple contracts' details wrapped in `Contract` instance. */ + fetchContractInfo (addresses: Address[], options?: { parallel?: boolean }): + Promise> + /** Fetch multiple contracts' details wrapped in instances of a custom class. */ + fetchContractInfo ( + Contract: T, addresses: Address[], options?: { parallel?: boolean } + ): Promise>> + /** Fetch multiple contracts' details, specifying a custom class for each. */ + fetchContractInfo ( + contracts: { [address: Address]: typeof Contract }, options?: { parallel?: boolean } + ): Promise<{[x in keyof typeof contracts]: InstanceType}> + /** Query a contract by address. */ + query (contract: Address, message: Message): + Promise + /** Query a contract object. */ + query (contract: { address: Address }, message: Message): + Promise +} + +/** Represents a remote API endpoint. */ +export interface Connection extends Logged { + /** Chain to which this Connection belongs. */ + readonly chain: Chain + /** RPC URL to which this connection sends requests. */ + readonly url: string + /** Whether the connection is alive or we should stop retrying. */ + readonly alive: boolean + /** Chain-specific implementation of fetchBlock. */ + fetchBlockImpl (parameters?: { raw?: boolean } & ({ height: number|bigint }|{ hash: string })): + Promise + /** Chain-specific implementation of fetchHeight. */ + fetchHeightImpl (): + Promise + /** Chain-specific implementation of fetchBalance. */ + fetchBalanceImpl (parameters: { addresses: Record, parallel?: boolean }): + Promise>> + /** Chain-specific implementation of fetchCodeInfo. */ + fetchCodeInfoImpl (parameters?: { codeIds?: CodeId[], parallel?: boolean }): + Promise> + /** Chain-specific implementation of fetchCodeInstances. */ + fetchCodeInstancesImpl (parameters: { + codeIds: { [id: CodeId]: typeof Contract }, parallel?: boolean + }): Promise<{[x in keyof typeof parameters["codeIds"]]: Record< + Address, InstanceType>}> + /** Chain-specific implementation of fetchContractInfo. */ + fetchContractInfoImpl (parameters: { + contracts: { [address: Address]: typeof Contract }, parallel?: boolean + }): Promise> + /** Chain-specific implementation of query. */ + queryImpl (parameters: { address: Address, codeHash?: string, message: Message }): + Promise +} + +export interface Identity extends Logged { + /** Display name. */ + readonly name?: Address + /** Address of account. */ + readonly address?: Address + /** Sign some data with the identity's private key. */ + sign (doc: any): unknown +} + +export interface Agent extends Logged { + /** The chain on which this agent operates. */ + readonly chain: Chain + /** The identity that will sign the transactions. */ + readonly identity: Identity + /** Return the address of this agent. */ + readonly address?: Address + /** Default transaction fees. */ + readonly fees?: Token.FeeMap<'send'|'upload'|'init'|'exec'> + /** Get a signing connection to the RPC endpoint. */ + getConnection (): SigningConnection + /** Construct a transaction batch that will be broadcast by this agent. */ + batch (): Batch + /** Fetch balance of this agent in many (or all) tokens. */ + fetchBalance (tokens?: string[]|string): Promise> + /** Send one or more kinds of native tokens to one or more recipients. */ + send ( + outputs: Record>, + options?: Omit[0], 'outputs'> + ): Promise + /** Upload a contract's code, generating a new code id/hash pair. */ + upload ( + code: string|URL|Uint8Array|Partial, + options?: Omit[0], 'binary'>, + ): Promise + /** Instantiate a new program from a code id, label and init message. */ + instantiate ( + contract: CodeId|Partial, + options: Partial & { initMsg: Into, initSend?: Token.ICoin[] } + ): Promise + /** Call a given program's transaction method. */ + execute ( + contract: Address|Partial, + message: Message, + options?: Omit[0], 'address'|'codeHash'|'message'> + ): Promise +} + +export interface SigningConnection extends Logged { + readonly chain: Chain + readonly chainId: ChainId + readonly identity: Identity + readonly address: Address + /** Chain-specific implementation of native token transfer. */ + sendImpl (parameters: SendOptions): + Promise + /** Chain-specific implementation of code upload. */ + uploadImpl (parameters: UploadOptions): + Promise> + /** Chain-specific implementation of contract instantiation. */ + instantiateImpl (parameters: InstantiateOptions): + Promise + /** Chain-specific implementation of contract transaction. */ + executeImpl (parameters: ExecuteOptions): + Promise +} + +export interface Batch extends Logged { + /** The chain targeted by the batch. */ + readonly chain: Chain + /** The agent that will broadcast the batch. */ + readonly agent: Agent + /** Add an upload message to the batch. */ + upload (...args: Parameters): this + /** Add an instantiate message to the batch. */ + instantiate (...args: Parameters): this + /** Add an execute message to the batch. */ + execute (...args: Parameters): this + /** Submit the batch. */ + submit (...args: unknown[]): Promise +} + +interface SendOptions { + outputs: Record>, + sendFee?: Token.IFee, + sendMemo?: string, + parallel?: boolean +} + +interface UploadOptions { + binary: Uint8Array, + reupload?: boolean, + uploadStore?: UploadStore, + uploadFee?: Token.IFee + uploadMemo?: string +} + +interface InstantiateOptions extends Partial { + initMsg: Into + initFee?: Token.IFee + initSend?: Token.ICoin[] + initMemo?: string +} + +interface ExecuteOptions { + address: Address + codeHash?: string + message: Message + execFee?: Token.IFee + execSend?: Token.ICoin[] + execMemo?: string +} + +export interface Block { + /** Chain to which this block belongs. */ + readonly chain: Chain + /** ID of chain to which this block belongs. */ + readonly chainId: ChainId + /** Unique ID of block. */ + readonly hash?: string + /** Unique ID of block. */ + readonly id?: string + /** Unique identifying hash of block. */ + readonly height?: bigint + /** Contents of block header. */ + readonly header?: { height?: string|number|bigint } + /** Transactions in block */ + readonly transactions?: Transaction[] +} + +/** A transaction in a block on a chain. */ +export interface Transaction { + /** Block to which this transaction belongs. */ + readonly block: Block + /** Hash of block to which this transaction belongs. */ + readonly blockHash: string + /** Height of block to which this transaction belongs. */ + readonly blockHeight: string + /** Chain to which this transaction belongs. */ + readonly chain: Chain + /** ID of chain to which this transaction belongs. */ + readonly chainId: string + /** Unique identifying hash of transaction. */ + readonly hash: string + /** Unique ID of block. */ + readonly id: string + /** Any custom data attached to the transaction. */ + readonly data?: unknown +} diff --git a/src/Agent.ts b/src/Agent.ts index c5bb2989522..742f0b1e385 100644 --- a/src/Agent.ts +++ b/src/Agent.ts @@ -2,212 +2,203 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . **/ import type { Address, Uint128, ChainId, CodeId, Token, Message, Into } from '../index' -import { assign, timed, bold, Logged, into } from './Util' -import { Chain } from './Chain' +import type { + Chain, Connection, Agent, Batch, Identity, SigningConnection, Transaction, Block +} from './API' +import { assign, timed, bold, Logged, into, Error } from './Util' import { send } from './dlt/Bank' import { CompiledCode } from './compute/Compile' import { UploadedCode, UploadStore, upload } from './compute/Upload' import { Contract, instantiate, execute } from './compute/Contract' -/** A cryptographic identity. */ -export class Identity extends Logged { - constructor ( - properties: ConstructorParameters[0] & Pick = {} - ) { - super(properties) - assign(this, properties, ['name', 'address']) - } - /** Display name. */ - name?: Address - /** Address of account. */ - address?: Address - /** Sign some data with the identity's private key. */ - sign (doc: any): unknown { - throw new Error("can't sign: stub") +export function makeIdentity ( + { name, address }: Pick +) { + return { + name, + address, + sign (doc: any) { throw Error.Unimplemented() } } } -/** Enables non-read-only transactions by binding an `Identity` to a `Connection`. */ -export abstract class Agent extends Logged { - constructor ( - properties: ConstructorParameters[0] - & Pick - & Partial> - ) { - super() - this.chain = properties.chain - this.identity = properties.identity - this.fees = properties.fees - } - /** The chain on which this agent operates. */ - chain: Chain - /** The identity that will sign the transactions. */ - identity: Identity - /** Default transaction fees. */ - fees?: Token.FeeMap<'send'|'upload'|'init'|'exec'> - /** Get a signing connection to the RPC endpoint. */ - abstract getConnection (): SigningConnection - /** Construct a transaction batch that will be broadcast by this agent. */ - abstract batch (): Batch - /** Return the address of this agent. */ - get address (): Address|undefined { - return this.identity?.address - } - async fetchBalance (tokens?: string[]|string): Promise> { - throw new Error("unimplemented!") +export function makeAgent ( + { chain, identity, fees }: Pick +): Agent { + //if ((this.identity && (this.identity.name||this.identity.address))) { + //const identityColor = randomColor({ // address takes priority in determining color + //luminosity: 'dark', seed: this.identity.address||this.identity.name + //}) + //this.log.label += ' ' + //this.log.label += colors.bgHex(identityColor).whiteBright( + //` ${this.identity.name||this.identity.address} ` + //) + //} + //if ((this.identity && (this.identity.name||this.identity.address))) { + //let myTag = `${this.identity.name||this.identity.address}` + //const myColor = randomColor({ luminosity: 'dark', seed: myTag }) + //myTag = colors.bgHex(myColor).whiteBright.bold(myTag) + //tag = [tag, myTag].filter(Boolean).join(':') + //} + return { + fees, + get chain () { return chain }, + get identity () { return identity }, + get address () { return identity.address }, + fetchBalance (...args) { throw Error.Unimplemented() }, + getConnection () { throw Error.Unimplemented() }, + batch () { throw Error.Unimplemented() }, + send (...args) { return send(this, ...args) }, + upload (...args) { return upload(this, ...args) }, + instantiate (...args) { return instantiate(this, ...args) }, + execute (...args) { return execute(this, ...args) } } - /** Send one or more kinds of native tokens to one or more recipients. */ - async send ( - outputs: Record>, - options?: Omit[0], - 'outputs'> - ): Promise { - return send(this, outputs, options) - } - /** Upload a contract's code, generating a new code id/hash pair. */ - async upload ( - code: string|URL|Uint8Array|Partial, - options?: Omit[0], - 'binary'>, - ): Promise { - return upload(this, code, options) +} + +/** Extend the object returned by this function to implement transaction support. */ +export function makeSigningConnection ( + { chain, identity }: Pick +): SigningConnection { + return { + get chain () { return chain }, + get chainId () { return chain.id }, + get identity () { return identity }, + get address () { return identity.address }, + sendImpl (...args) { throw Error.Unimplemented() }, + uploadImpl (...args) { throw Error.Unimplemented() }, + instantiateImpl (...args) { throw Error.Unimplemented() }, + executeImpl (...args) { throw Error.Unimplemented() }, } - /** Instantiate a new program from a code id, label and init message. */ - async instantiate ( - contract: CodeId|Partial, - options: Partial & { - initMsg: Into, - initSend?: Token.ICoin[] +} + +/** Extend the object returned by this function to implement batched transaction support. */ +export function makeBatch ({ agent }: Pick): Batch { + return { + get agent () { return agent }, + get chain () { return agent.chain }, + upload (...args: Parameters) { + this.log.warn('upload: stub (not implemented)') + return this + }, + instantiate (...args: Parameters) { + this.log.warn('instantiate: stub (not implemented)') + return this + }, + execute (...args: Parameters) { + this.log.warn('execute: stub (not implemented)') + return this + }, + submit (...args: unknown[]): Promise { + this.log.warn('submit: stub (not implemented)') + throw Error.Unimplemented() } - ): Promise { - return instantiate(this, contract, options) - } - /** Call a given program's transaction method. */ - async execute ( - contract: Address|Partial, - message: Message, - options?: Omit[0], - 'address'|'codeHash'|'message'> - ): Promise { - return await execute(this, contract, message, options) as T } } -/** Extend this class and implement the abstract methods to add support for a new kind of chain. */ -export abstract class SigningConnection { - constructor (properties: { chain: Chain, identity: Identity }) { - this.#chain = properties.chain - this.#identity = properties.identity - } - #chain: Chain - /** Chain to which this connection points. */ - get chain (): Chain { - return this.#chain - } - get chainId (): ChainId { - return this.chain.chainId +export function makeBlock ({ + chain, hash, height, header, transactions +}: Pick): Block { + return { + get chain () { return chain }, + get chainId () { return chain.id }, + get hash () { return hash }, + get id () { return hash }, + get height () { return height }, + get header () { return header }, + get transactions () { return transactions } } - #identity: Identity - get identity (): Identity { - return this.#identity - } - get address (): Address { - return this.identity.address! - } - /** Chain-specific implementation of native token transfer. */ - abstract sendImpl (parameters: { - outputs: Record>, - sendFee?: Token.IFee, - sendMemo?: string, - parallel?: boolean - }): Promise - /** Chain-specific implementation of code upload. */ - abstract uploadImpl (parameters: { - binary: Uint8Array, - reupload?: boolean, - uploadStore?: UploadStore, - uploadFee?: Token.IFee - uploadMemo?: string - }): Promise> - /** Chain-specific implementation of contract instantiation. */ - abstract instantiateImpl (parameters: Partial & { - initMsg: Into - initFee?: Token.IFee - initSend?: Token.ICoin[] - initMemo?: string - }): - Promise - /** Chain-specific implementation of contract transaction. */ - abstract executeImpl (parameters: { - address: Address - codeHash?: string - message: Message - execFee?: Token.IFee - execSend?: Token.ICoin[] - execMemo?: string - }): Promise } - //if ((this.identity && (this.identity.name||this.identity.address))) { - //const identityColor = randomColor({ // address takes priority in determining color - //luminosity: 'dark', seed: this.identity.address||this.identity.name - //}) - //this.log.label += ' ' - //this.log.label += colors.bgHex(identityColor).whiteBright( - //` ${this.identity.name||this.identity.address} ` - //) - //} - //if ((this.identity && (this.identity.name||this.identity.address))) { - //let myTag = `${this.identity.name||this.identity.address}` - //const myColor = randomColor({ luminosity: 'dark', seed: myTag }) - //myTag = colors.bgHex(myColor).whiteBright.bold(myTag) - //tag = [tag, myTag].filter(Boolean).join(':') - //} - -/** Builder object for batched transactions. */ -export class Batch extends Logged { - constructor ( - properties: ConstructorParameters[0] & Pick - ) { - super(properties) - this.agent = properties.agent +export function makeTransaction ({ + block, hash, data +}: Pick): Transaction { + return { + get block () { return block }, + get blockHash () { return block.hash }, + get blockHeight () { return block.height }, + get chain () { return block.chain }, + get chainId () { return block.chainId }, + get hash () { return hash }, + get id () { return hash }, + get data () { return data }, } +} - /** The chain targeted by the batch. */ - get chain (): Chain { - return this.agent.chain +export function makeConnection (): Connection { + /** FIXME: find these guys a new home: + * + constructor ( + properties: ConstructorParameters[0] + & Pick + & Partial> + ) { + super(properties) + this.#chain = properties.chain + this.url = properties.url + this.alive = properties.alive ?? true + this.log.label = [ + this.constructor.name, + '(', this[Symbol.toStringTag] ? `(${bold(this[Symbol.toStringTag])})` : null, ')' + ].filter(Boolean).join('') + this.log.label = new.target.constructor.name + const chainColor = randomColor({ luminosity: 'dark', seed: this.url }) + this.log.label = colors.bgHex(chainColor).whiteBright(` ${this.url} `) + } + get [Symbol.toStringTag] () { + if (this.url) { + const color = randomColor({ luminosity: 'dark', seed: this.url }) + return colors.bgHex(color).whiteBright(this.url) + } + } */ + return { + get chain () { return chain }, + get url () { return url }, + get alive () { return true }, + fetchBlockImpl () { throw Error.Unimplemented() }, + fetchHeightImpl () { throw Error.Unimplemented() }, + fetchBalanceImpl () { throw Error.Unimplemented() }, + fetchCodeInfoImpl () { throw Error.Unimplemented() }, + fetchCodeInstancesImpl () { throw Error.Unimplemented() }, + fetchContractInfoImpl () { throw Error.Unimplemented() }, + queryImpl () { throw Error.Unimplemented() }, } +} - /** The agent that will broadcast the batch. */ - agent: Agent - - /** Add an upload message to the batch. */ - upload (...args: Parameters): this { - this.log.warn('upload: stub (not implemented)') - return this - } - /** Add an instantiate message to the batch. */ - instantiate (...args: Parameters): this { - this.log.warn('instantiate: stub (not implemented)') - return this - } - /** Add an execute message to the batch. */ - execute (...args: Parameters): this { - this.log.warn('execute: stub (not implemented)') - return this - } - /** Submit the batch. */ - async submit (...args: unknown[]): Promise { - this.log.warn('submit: stub (not implemented)') - return {} +export function makeChain (): Chain { + return { + get id () { + return id + }, + getConnection () { + throw Error.Unimplemented() + }, + authenticate () { + throw Error.Unimplemented() + }, + fetchHeight () { + this.log.debug('Querying block height') + return this.getConnection().fetchHeightImpl() + }, + fetchNextBlock () { + this.log.debug('Querying block height') + return fetchNextBlock(this) + }, + fetchBlock () { + return fetchBlock(this, ...args as Parameters) + }, + fetchBalance () { + return fetchBalance(this, ...args as Parameters) + }, + fetchCodeInfo () { + return fetchCodeInfo(this, ...args as Parameters) + }, + fetchCodeInstances () { + return fetchCodeInstances(this, ...args as Parameters) + }, + fetchContractInfo () { + return fetchContractInfo(this, ...args as Parameters) + }, + query () { + return query(this, ...args as Parameters) + }, } } - diff --git a/src/Chain.ts b/src/Chain.ts index ffb69e6c7e0..4815199565c 100644 --- a/src/Chain.ts +++ b/src/Chain.ts @@ -9,305 +9,6 @@ import type { Address, Agent, ChainId, CodeId, Identity, Message, Token, Uint128, UploadStore, Into, } from '../index' -export abstract class Chain extends Logged { - - static get Connection () { - return Connection - } - - constructor ( - properties: ConstructorParameters[0] - & Pick - & Partial> - ) { - super(properties||{}) - this.chainId = properties.chainId - } - - /** Chain ID. This is a string that uniquely identifies a chain. - * A project's mainnet and testnet have different chain IDs. */ - chainId: ChainId - get id () { - return this.chainId - } - - /** Time to ping for next block. */ - blockInterval = 250 - - /** Get a read-only connection to the API endpoint. */ - abstract getConnection (): Connection - - /** Authenticate to the chain, obtaining an Agent instance that can send transactions. */ - abstract authenticate (properties?: { mnemonic: string }|Identity): Promise - - /** Get the current block height. */ - fetchHeight (): Promise { - this.log.debug('Querying block height') - return this.getConnection().fetchHeightImpl() - } - - /** Wait until the block height increments, or until `this.alive` is set to false. */ - fetchNextBlock (): Promise { - this.log.debug('Querying block height') - return fetchNextBlock(this) - } - - /** Get info about the latest block. */ - fetchBlock (): - Promise - /** Get info about the block with a specific height. */ - fetchBlock ({ height }: { height: number|bigint, raw?: boolean }): - Promise - /** Get info about the block with a specific hash. */ - fetchBlock ({ hash }: { hash: string, raw?: boolean }): - Promise - fetchBlock (...args: unknown[]): Promise { - return fetchBlock(this, ...args as Parameters) - } - - /** Fetch balance of 1 or many addresses in 1 or many native tokens. */ - fetchBalance (address: Address, token: string): - Promise - fetchBalance (address: Address, tokens?: string[]): - Promise> - fetchBalance (addresses: Address[], token: string): - Promise> - fetchBalance (addresses: Address[], tokens?: string): - Promise>> - async fetchBalance (...args: unknown[]): Promise { - return fetchBalance(this, ...args as Parameters) - } - - /** Fetch info about all code IDs uploaded to the chain. */ - fetchCodeInfo (): - Promise> - /** Fetch info about a single code ID. */ - fetchCodeInfo (codeId: CodeId, options?: { parallel?: boolean }): - Promise - /** Fetch info about multiple code IDs. */ - fetchCodeInfo (codeIds: Iterable, options?: { parallel?: boolean }): - Promise> - fetchCodeInfo (...args: unknown[]): Promise { - return fetchCodeInfo(this, ...args as Parameters) - } - - /** Fetch all instances of a code ID. */ - fetchCodeInstances ( - codeId: CodeId - ): Promise> - /** Fetch all instances of a code ID, with custom client class. */ - fetchCodeInstances ( - Contract: C, - codeId: CodeId - ): Promise>> - /** Fetch all instances of multple code IDs. */ - fetchCodeInstances ( - codeIds: Iterable, - options?: { parallel?: boolean } - ): Promise>> - /** Fetch all instances of multple code IDs, with custom client class. */ - fetchCodeInstances ( - Contract: C, - codeIds: Iterable, - options?: { parallel?: boolean } - ): Promise>>> - /** Fetch all instances of multple code IDs, with multiple custom client classes. */ - fetchCodeInstances ( - codeIds: { [id: CodeId]: typeof Contract }, - options?: { parallel?: boolean } - ): Promise<{ - [codeId in keyof typeof codeIds]: Record> - }> - async fetchCodeInstances (...args: unknown[]): Promise { - return fetchCodeInstances(this, ...args as Parameters) - } - - /** Fetch a contract's details wrapped in a `Contract` instance. */ - fetchContractInfo ( - address: Address - ): Promise - /** Fetch a contract's details wrapped in a custom class instance. */ - fetchContractInfo ( - Contract: T, - address: Address - ): Promise> - /** Fetch multiple contracts' details wrapped in `Contract` instance. */ - fetchContractInfo ( - addresses: Address[], - options?: { parallel?: boolean } - ): Promise> - /** Fetch multiple contracts' details wrapped in instances of a custom class. */ - fetchContractInfo ( - Contract: T, - addresses: Address[], - options?: { parallel?: boolean } - ): Promise>> - /** Fetch multiple contracts' details, specifying a custom class for each. */ - fetchContractInfo ( - contracts: { [address: Address]: typeof Contract }, - options?: { parallel?: boolean } - ): Promise<{ - [address in keyof typeof contracts]: InstanceType - }> - async fetchContractInfo (...args: unknown[]): Promise { - return fetchCodeInstances(this, ...args as Parameters) - } - - /** Query a contract by address. */ - query (contract: Address, message: Message): - Promise - /** Query a contract object. */ - query (contract: { address: Address }, message: Message): - Promise - query (...args: unknown[]): Promise { - return query(this, ...args as Parameters) - } -} - -/** Represents a remote API endpoint. - * - * * Use one of its subclasses in `@fadroma/scrt`, `@fadroma/cw`, `@fadroma/namada` - * to connect to the corresponding chain. - * * Or, extend this class to implement support for new kinds of blockchains. */ -export abstract class Connection extends Logged { - constructor ( - properties: ConstructorParameters[0] - & Pick - & Partial> - ) { - super(properties) - this.#chain = properties.chain - this.url = properties.url - this.alive = properties.alive ?? true - this.log.label = [ - this.constructor.name, - '(', this[Symbol.toStringTag] ? `(${bold(this[Symbol.toStringTag])})` : null, ')' - ].filter(Boolean).join('') - this.log.label = new.target.constructor.name - const chainColor = randomColor({ luminosity: 'dark', seed: this.url }) - this.log.label = colors.bgHex(chainColor).whiteBright(` ${this.url} `) - } - get [Symbol.toStringTag] () { - if (this.url) { - const color = randomColor({ luminosity: 'dark', seed: this.url }) - return colors.bgHex(color).whiteBright(this.url) - } - } - #chain: Chain - /** Chain to which this connection points. */ - get chain (): Chain { - return this.#chain - } - /** ID of chain to which this connection points. */ - get chainId (): ChainId { - return this.chain.chainId - } - /** Connection URL. - * - * The same chain may be accessible via different endpoints, so - * this property contains the URL to which requests are sent. */ - url: string - /** Setting this to false stops retries. */ - alive: boolean = true - - /** Chain-specific implementation of fetchBlock. */ - abstract fetchBlockImpl (parameters?: - { raw?: boolean } & ({ height: number|bigint }|{ hash: string }) - ): Promise - /** Chain-specific implementation of fetchHeight. */ - abstract fetchHeightImpl (): - Promise - /** Chain-specific implementation of fetchBalance. */ - abstract fetchBalanceImpl (parameters: { - addresses: Record, - parallel?: boolean - }): Promise>> - /** Chain-specific implementation of fetchCodeInfo. */ - abstract fetchCodeInfoImpl (parameters?: { - codeIds?: CodeId[] - parallel?: boolean - }): Promise> - /** Chain-specific implementation of fetchCodeInstances. */ - abstract fetchCodeInstancesImpl (parameters: { - codeIds: { [id: CodeId]: typeof Contract }, - parallel?: boolean - }): Promise<{ - [codeId in keyof typeof parameters["codeIds"]]: - Record> - }> - /** Chain-specific implementation of fetchContractInfo. */ - abstract fetchContractInfoImpl (parameters: { - contracts: { [address: Address]: typeof Contract }, - parallel?: boolean - }): Promise> - /** Chain-specific implementation of query. */ - abstract queryImpl (parameters: { - address: Address - codeHash?: string - message: Message - }): Promise -} - -/** The building block of a blockchain, as obtained by - * [the `fetchBlock` method of `Connection`](#method-connectionfetchblock) - * - * Contains zero or more transactions. */ -export abstract class Block { - constructor (properties: Pick) { - const height = properties?.height ?? properties?.header?.height - if (!height) { - throw new Error("Can't construct Block without at least specifying height") - } - this.#chain = properties?.chain - this.hash = properties?.hash - this.header = properties?.header - this.height = BigInt(height) - this.transactions = properties?.transactions || [] - } - /** Private reference to chain to which this block belongs. */ - readonly #chain?: Chain - /** Chain to which this block belongs. */ - get chain () { return this.#chain } - /** ID of chain to which this block belongs. */ - get chainId () { return this.chain?.id } - /** Unique ID of block. */ - readonly hash?: string - /** Unique ID of block. */ - get id () { return this.hash } - /** Unique identifying hash of block. */ - readonly height?: bigint - /** Contents of block header. */ - readonly header?: { height?: string|number|bigint } - /** Transactions in block */ - readonly transactions?: Transaction[] = [] -} - -/** A transaction in a block on a chain. */ -export class Transaction { - constructor (properties: Pick) { - this.#block = properties?.block - this.hash = properties?.hash - this.data = properties?.data - } - readonly #block?: Block - /** Block to which this transaction belongs. */ - get block () { return this.#block } - /** Hash of block to which this transaction belongs. */ - get blockHash () { return this.block?.hash } - /** Height of block to which this transaction belongs. */ - get blockHeight () { return this.block?.height } - /** Chain to which this transaction belongs. */ - get chain () { return this.block?.chain } - /** ID of chain to which this transaction belongs. */ - get chainId () { return this.block?.chain?.id } - /** Unique identifying hash of transaction. */ - readonly hash?: string - /** Unique ID of block. */ - get id () { return this.hash } - /** Any custom data attached to the transaction. */ - readonly data?: unknown -} - /** Implementation of Connection#fetchBlock -> Connection#fetchBlockImpl */ export async function fetchBlock (chain: Chain, ...args: Parameters): Promise diff --git a/src/Util.ts b/src/Util.ts index 0c2076868b0..cce8d4c4f32 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -4,7 +4,9 @@ import { Error } from '@hackbg/oops' import { Console, Logged, bold, colors } from '@hackbg/logs' -class FadromaError extends Error {} +class FadromaError extends Error { + static Unimplemented = () => new this('not implemented!') +} export { FadromaError as Error } @@ -13,6 +15,7 @@ export * from '@hackbg/into' export * from '@hackbg/hide' export * from '@hackbg/4mat' export * from '@hackbg/dump' +export { default as CLI } from '@hackbg/cmds' export async function timed ( fn: ()=>Promise, cb: (ctx: { elapsed: string, result: T })=>unknown diff --git a/src/dlt/Staking.ts b/src/dlt/Staking.ts index 385d73126d6..a6e6dbaf6f6 100644 --- a/src/dlt/Staking.ts +++ b/src/dlt/Staking.ts @@ -1,17 +1,5 @@ -/** Fadroma. Copyright (C) 2023 Hack.bg. License: GNU AGPLv3 or custom. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . **/ -import { assign } from '../Util' -import type { Chain, Address } from '../../index' - -export class Validator { - constructor (properties: Pick) { - this.#chain = properties.chain - assign(this, properties, [ "address" ]) - } - #chain: Chain - get chain () { - return this.#chain - } - address!: Address +import type { Address, Chain } from '../../index' +export interface Validator { + readonly chain: Chain + readonly address: Address }