diff --git a/packages/agent/src/Block.ts b/packages/agent/src/Block.ts
index 9cd2ad0eee..e73383b9e3 100644
--- a/packages/agent/src/Block.ts
+++ b/packages/agent/src/Block.ts
@@ -1,7 +1,7 @@
/** 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 { bold } from './Util'
+import { assign, bold } from './Util'
import type { Chain, Transaction } from '../index'
/** The building block of a blockchain, as obtained by
@@ -9,22 +9,24 @@ import type { Chain, Transaction } from '../index'
*
* Contains zero or more transactions. */
export abstract class Block {
- constructor (properties: Pick) {
- this.#chain = properties.chain
- this.height = properties.height
- this.id = properties.id
- this.timestamp = properties.timestamp
+ constructor (
+ properties: Pick
+ ) {
+ this.#chain = properties.chain
+ assign(this, properties, [ "id", "height", "timestamp", "transactions" ])
}
#chain: Chain
get chain () { return this.#chain }
/** Unique ID of block. */
- id: string
+ id!: string
/** Monotonically incrementing ID of block. */
- height: number
+ height!: number
/** Timestamp of block */
- timestamp?: string
+ timestamp?: string
+ /** Transactions in block */
+ transactions!: unknown[]
}
export async function fetchBlock (chain: Chain, ...args: Parameters):
diff --git a/packages/agent/src/Chain.ts b/packages/agent/src/Chain.ts
index 1c06fd1846..097c87f6a8 100644
--- a/packages/agent/src/Chain.ts
+++ b/packages/agent/src/Chain.ts
@@ -1,32 +1,17 @@
/** 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 {
- Logged, assign, bold, timed
-} from './Util'
-import {
- fetchBalance
-} from './dlt/Bank'
-import {
- Contract,
- fetchCodeInstances,
- query
-} from './compute/Contract'
-import {
- UploadedCode,
- fetchCodeInfo,
-} from './compute/Upload'
-import {
- Block,
- fetchBlock,
- nextBlock
-} from './Block'
+import { Logged, assign, bold, timed } from './Util'
+import { fetchBalance } from './dlt/Bank'
+import { Contract, fetchCodeInstances, query } from './compute/Contract'
+import { UploadedCode, fetchCodeInfo, } from './compute/Upload'
+import { Block, fetchBlock, nextBlock } from './Block'
+import { Connection } from './Connection'
import type {
Address,
Agent,
ChainId,
CodeId,
- Connection,
Identity,
Message,
Token,
@@ -34,6 +19,11 @@ import type {
} from '../index'
export abstract class Chain extends Logged {
+
+ static get Connection () {
+ return Connection
+ }
+
constructor (
properties: ConstructorParameters[0]
& Pick
@@ -50,15 +40,11 @@ export abstract class Chain extends Logged {
/** Time to ping for next block. */
blockInterval = 250
- /** Get a connection to the API endpoint. */
+ /** Get a read-only connection to the API endpoint. */
abstract getConnection (): Connection
- /** Authenticate with a random identity. */
- abstract authenticate (): Promise
- /** Authenticate with a mnemonic. */
- abstract authenticate (mnemonic: string): Promise
- /** Authenticate with the provided identity. */
- abstract authenticate (identity: Identity): Promise
+ /** Authenticate to the chain, obtaining an Agent instance that can send transactions. */
+ abstract authenticate (properties?: { mnemonic: string }|Identity): Promise
/** Get the current block height. */
get height (): Promise {
diff --git a/packages/agent/src/Connection.ts b/packages/agent/src/Connection.ts
index 282f094673..70c92fa712 100644
--- a/packages/agent/src/Connection.ts
+++ b/packages/agent/src/Connection.ts
@@ -6,7 +6,7 @@ import {
} from './Util'
import type {
Address, Block, Chain, ChainId, CodeId, Message, Token, Uint128,
- UploadStore, UploadedCode, Contract, Into
+ UploadStore, UploadedCode, Contract, Into, Identity
} from '../index'
/** Represents a remote API endpoint.
@@ -17,14 +17,12 @@ import type {
export abstract class Connection extends Logged {
constructor (
properties: ConstructorParameters[0]
- & Pick
+ & Pick
& Partial>
) {
super(properties)
- this.#chain = properties.chain
- this.url = properties.url
- this.alive = properties.alive || true
- this.api = properties.api
+ this.#chain = properties.chain
+ assign(this, properties, [ "url", "alive" ])
this.log.label = [
this.constructor.name,
'(', this[Symbol.toStringTag] ? `(${bold(this[Symbol.toStringTag])})` : null, ')'
@@ -33,7 +31,12 @@ export abstract class Connection extends Logged {
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 {
@@ -43,31 +46,14 @@ export abstract class Connection extends Logged {
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
- /** Instance of platform SDK. This must be provided in a subclass.
- *
- * Since most chain SDKs initialize asynchronously, this is usually a `Promise`
- * that resolves to an instance of the underlying client class (e.g. `CosmWasmClient` or `SecretNetworkClient`).
- *
- * Since transaction and query methods are always asynchronous as well, well-behaved
- * implementations of Fadroma Agent begin each method that talks to the chain with
- * e.g. `const { api } = await this.api`, making sure an initialized platform SDK instance
- * is available. */
- api: unknown
+ url!: string
/** Setting this to false stops retries. */
alive: boolean = true
- get [Symbol.toStringTag] () {
- if (this.url) {
- const color = randomColor({ luminosity: 'dark', seed: this.url })
- return colors.bgHex(color).whiteBright(this.url)
- }
- }
/** Chain-specific implementation of fetchBlock. */
abstract fetchBlockImpl (parameters?:
{ height: number }|{ hash: string }
@@ -77,9 +63,9 @@ export abstract class Connection extends Logged {
Promise
/** Chain-specific implementation of fetchBalance. */
abstract fetchBalanceImpl (parameters: {
- token?: string,
- address?: string
- }): Promise
+ addresses: Record,
+ parallel?: boolean
+ }): Promise>>
/** Chain-specific implementation of fetchCodeInfo. */
abstract fetchCodeInfoImpl (parameters?: {
codeIds?: CodeId[]
@@ -108,6 +94,25 @@ export abstract class Connection extends Logged {
/** 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
+ }
+ #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>,
@@ -120,14 +125,19 @@ export abstract class SigningConnection {
binary: Uint8Array,
reupload?: boolean,
uploadStore?: UploadStore,
- uploadFee?: Token.ICoin[]|'auto',
+ uploadFee?: Token.IFee
uploadMemo?: string
}): Promise>
/** Chain-specific implementation of contract instantiation. */
- abstract instantiateImpl (parameters: Partial & { initMsg: Into }):
+ abstract instantiateImpl (parameters: Partial & {
+ initMsg: Into
+ initFee?: Token.IFee
+ initSend?: Token.ICoin[]
+ initMemo?: string
+ }):
Promise
/** Chain-specific implementation of contract transaction. */
abstract executeImpl (parameters: {
diff --git a/packages/agent/src/Identity.ts b/packages/agent/src/Identity.ts
index 3d80a22759..cb1c2f71a0 100644
--- a/packages/agent/src/Identity.ts
+++ b/packages/agent/src/Identity.ts
@@ -6,13 +6,15 @@ import type { Address } from './Types'
/** A cryptographic identity. */
export class Identity extends Logged {
- constructor (properties?: Partial) {
+ constructor (
+ properties: ConstructorParameters[0] & Pick = {}
+ ) {
super(properties)
assign(this, properties, ['name', 'address'])
}
/** Display name. */
- name?: Address
- /** Unique identifier. */
+ name?: Address
+ /** Address of account. */
address?: Address
/** Sign some data with the identity's private key. */
sign (doc: any): unknown {
diff --git a/packages/agent/src/Util.ts b/packages/agent/src/Util.ts
index 72cb0e8d5f..0c2076868b 100644
--- a/packages/agent/src/Util.ts
+++ b/packages/agent/src/Util.ts
@@ -26,3 +26,14 @@ export async function timed (
})
return result
}
+
+export async function optionallyParallel (parallel: boolean|undefined, thunks: Array<()=>Promise>) {
+ if (parallel) {
+ return await Promise.all(thunks.map(thunk=>thunk()))
+ }
+ const results = []
+ for (const thunk of thunks) {
+ results.push(await thunk())
+ }
+ return results
+}
diff --git a/packages/agent/src/dlt/Staking.ts b/packages/agent/src/dlt/Staking.ts
index 85a6641928..385d73126d 100644
--- a/packages/agent/src/dlt/Staking.ts
+++ b/packages/agent/src/dlt/Staking.ts
@@ -2,13 +2,16 @@
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 { Connection, Address } from '../../index'
+import type { Chain, Address } from '../../index'
export class Validator {
- chain?: Connection
- address: Address
- constructor (properties: Pick & Partial>) {
- this.chain = properties.chain
- this.address = properties.address
+ constructor (properties: Pick) {
+ this.#chain = properties.chain
+ assign(this, properties, [ "address" ])
}
+ #chain: Chain
+ get chain () {
+ return this.#chain
+ }
+ address!: Address
}
diff --git a/packages/agent/stub/StubChain.ts b/packages/agent/stub/StubChain.ts
index 5354a9a2df..9a881a29af 100644
--- a/packages/agent/stub/StubChain.ts
+++ b/packages/agent/stub/StubChain.ts
@@ -51,11 +51,7 @@ export class StubChain extends Chain {
}
getConnection (): StubConnection {
- return new StubConnection({
- chain: this,
- url: 'stub',
- api: {},
- })
+ return new StubConnection({ chain: this, url: 'stub', })
}
}
@@ -80,21 +76,22 @@ export class StubConnection extends Connection {
override fetchBlockImpl (): Promise {
const timestamp = new Date()
return Promise.resolve(new StubBlock({
- chain: this.chain,
- id: `stub${+timestamp}`,
- height: +timestamp,
- timestamp: timestamp.toISOString()
+ chain: this.chain,
+ id: `stub${+timestamp}`,
+ height: +timestamp,
+ timestamp: timestamp.toISOString(),
+ transactions: []
}))
}
override fetchBalanceImpl (
...args: Parameters
- ): Promise {
+ ) {
throw new Error('unimplemented!')
//token ??= this.defaultDenom
//const balance = (this.backend.balances.get(address!)||{})[token] ?? 0
//return Promise.resolve(String(balance))
- return Promise.resolve('')
+ return Promise.resolve({})
}
override fetchCodeInfoImpl (
diff --git a/packages/agent/stub/StubIdentity.ts b/packages/agent/stub/StubIdentity.ts
index a81b5651a5..2ef746ad1c 100644
--- a/packages/agent/stub/StubIdentity.ts
+++ b/packages/agent/stub/StubIdentity.ts
@@ -24,8 +24,8 @@ export class StubAgent extends Agent {
getConnection (): StubSigningConnection {
return new StubSigningConnection({
- address: this.identity.address!,
- backend: this.chain.backend
+ chain: this.chain,
+ identity: this.identity,
})
}
@@ -35,15 +35,12 @@ export class StubAgent extends Agent {
}
export class StubSigningConnection extends SigningConnection {
- constructor (properties: Pick) {
- super()
- this.backend = properties.backend
- this.address = properties.address
+ get chain (): StubChain {
+ return super.chain as unknown as StubChain
+ }
+ get backend (): StubBackend {
+ return this.chain.backend
}
-
- backend: StubBackend
-
- address: Address
async sendImpl (...args: Parameters): Promise {
const { backend } = this
diff --git a/packages/cw/cw-bank.ts b/packages/cw/cw-bank.ts
index 540fecdbe6..3cea773e8f 100644
--- a/packages/cw/cw-bank.ts
+++ b/packages/cw/cw-bank.ts
@@ -1,30 +1,58 @@
+import { optionallyParallel } from '@fadroma/agent'
import type { CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cosmjs-esm'
import type { Address, Token, Chain, Connection, SigningConnection } from '@fadroma/agent'
import type { CWChain, CWConnection } from './cw-connection'
import type { CWAgent, CWSigningConnection } from './cw-identity'
-export async function fetchBalance (
- connection: CWConnection, balances: Parameters[0]
-) {
- if (!address) {
- throw new Error('getBalance: need address')
+export async function fetchBalance (chain: CWConnection, {
+ parallel = false,
+ addresses,
+}: Parameters[0]) {
+ const queries = []
+ for (const [address, tokens] of Object.entries(addresses)) {
+ for (const token of tokens) {
+ queries.push(()=>chain.api.getBalance(address, token).then(balance=>({
+ address, token, balance
+ })))
+ }
}
- const { amount } = await connection.api.getBalance(address, token)
- return amount
+ const result: Record> = {}
+ const responses = await optionallyParallel(parallel, queries)
+ for (const { address, token, balance } of responses) {
+ result[address] ??= {}
+ result[address][token] = balance.amount
+ }
+ return result
}
-export async function send (
- { api, address }: CWSigningConnection,
- { outputs
- , sendFee = 'auto'
- , sendMemo
- , parallel }: Parameters[0]
-) {
- return api.sendTokens(
- address!,
- recipient as string,
- amounts,
- sendFee,
- sendMemo
- )
+export async function send (agent: CWSigningConnection, {
+ parallel = false,
+ outputs,
+ sendFee,
+ sendMemo,
+}: Parameters[0]) {
+ const sender = agent.address
+ const transactions = []
+ for (const [recipient, amounts] of Object.entries(outputs)) {
+ transactions.push(()=>agent.api.sendTokens(
+ sender,
+ recipient,
+ Object.entries(amounts).map(([denom, amount])=>({amount, denom})),
+ sendFee || 'auto',
+ sendMemo
+ ).then(transaction=>({
+ sender, recipient, amounts, transaction
+ })))
+ }
+ const result: Record
+ transaction: unknown
+ }> = {}
+ const responses = await optionallyParallel(parallel, transactions)
+ for (const response of responses) {
+ result[response.recipient] = response
+ }
+ return result
}
diff --git a/packages/cw/cw-compute.ts b/packages/cw/cw-compute.ts
index 54db6cc746..f4bbd032ae 100644
--- a/packages/cw/cw-compute.ts
+++ b/packages/cw/cw-compute.ts
@@ -68,7 +68,7 @@ export async function getCodes (
const results = await chain.api.getCodes()
for (const { id, checksum, creator } of results||[]) {
codes[id!] = new UploadedCode({
- chainId: chainId,
+ chainId: chain.chainId,
codeId: String(id),
codeHash: checksum,
uploadBy: creator
@@ -130,7 +130,7 @@ export async function query (
}
export async function upload (
- { api, address }: CWSigningConnection,
+ { chainId, address, api }: CWSigningConnection,
options: Parameters[0]
) {
if (!address) {
@@ -139,7 +139,7 @@ export async function upload (
const result = await api.upload(
address!,
options.binary,
- fees?.upload || 'auto',
+ options.uploadFee as Amino.StdFee || 'auto',
"Uploaded by Fadroma"
)
return {
@@ -153,7 +153,7 @@ export async function upload (
}
export async function instantiate (
- { address, api }: CWSigningConnection,
+ { chain, address, api }: CWSigningConnection,
options: Parameters[0]
) {
const result = await (api as SigningCosmWasmClient).instantiate(
@@ -165,18 +165,17 @@ export async function instantiate (
{ admin: address, funds: options.initSend, memo: options.initMemo }
)
return new Contract({
+ chain,
codeId: options.codeId,
codeHash: options.codeHash,
label: options.label,
- initMsg: options.initMsg,
- chainId: chainId,
address: result.contractAddress,
- initTx: result.transactionHash,
- initGas: result.gasUsed,
+ //initTx: result.transactionHash,
+ //initGas: result.gasUsed,
initBy: address,
- initFee: options.initFee || 'auto',
- initSend: options.initSend,
- initMemo: options.initMemo
+ //initFee: options.initFee || 'auto',
+ //initSend: options.initSend,
+ //initMemo: options.initMemo
}) as Contract & { address: Address }
}
diff --git a/packages/cw/cw-connection.ts b/packages/cw/cw-connection.ts
index 8d0e900086..febd26d863 100644
--- a/packages/cw/cw-connection.ts
+++ b/packages/cw/cw-connection.ts
@@ -1,7 +1,6 @@
import { bold, assign, Chain, Connection } from '@fadroma/agent'
-import type { Address, Message, CodeId, CodeHash, Token } from '@fadroma/agent'
-import { CWAgent } from './cw-identity'
-import type { CWIdentity, CWMnemonicIdentity, CWSignerIdentity } from './cw-identity'
+import type { Address, Message, CodeId, CodeHash, Token, ChainId } from '@fadroma/agent'
+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'
@@ -11,42 +10,92 @@ import { Amino, Proto, CosmWasmClient, SigningCosmWasmClient } from '@hackbg/cos
import type { Block } from '@hackbg/cosmjs-esm'
export class CWChain extends Chain {
- constructor (properties: Partial = {}) {
+
+ static get Connection () {
+ return CWConnection
+ }
+
+ static async connect (
+ properties: { chainId?: ChainId }&({ url: string|URL }|{ urls: Iterable })
+ ): Promise {
+ const { chainId, url, urls = [ url ] } = properties as any
+ const chain = new this({
+ chainId,
+ connections: [],
+ bech32Prefix: 'tnam'
+ })
+ const connections: CWConnection[] = urls.map(async (url: string|URL)=>new this.Connection({
+ api: await CosmWasmClient.connect(String(url)),
+ chain,
+ url: String(url)
+ }))
+ chain.connections = await Promise.all(connections)
+ return chain
+ }
+
+ constructor (
+ properties: ConstructorParameters[0]
+ & Pick
+ ) {
super(properties)
- assign(this, properties, [
- 'coinType',
- 'bech32Prefix',
- 'hdAccountIndex'
- ])
+ this.coinType = properties.coinType
+ this.bech32Prefix = properties.bech32Prefix
+ this.hdAccountIndex = properties.hdAccountIndex
+ this.connections = properties.connections
}
/** The bech32 prefix for the account's address */
- bech32Prefix?: string
+ bech32Prefix: string
/** The coin type in the HD derivation path */
coinType?: number
/** The account index in the HD derivation path */
hdAccountIndex?: number
- #connection: CWConnection
+ connections: CWConnection[]
getConnection (): CWConnection {
- return this.#connection
- }
-
- async authenticate (...args): Promise {
- return new CWAgent({ chain: this })
+ return this.connections[0]
+ }
+
+ async authenticate (
+ ...args: Parameters
+ ): Promise {
+ let identity: CWIdentity
+ if (!args[0]) {
+ identity = new CWMnemonicIdentity({})
+ } else if (typeof (args[0] as any).mnemonic === 'string') {
+ identity = new CWMnemonicIdentity({
+ mnemonic: (args[0] as any).mnemonic
+ })
+ } 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,
+ )
+ })
+ })
}
}
-/** Generic agent for CosmWasm-enabled chains. */
+/** Read-only client for CosmWasm-enabled chains. */
export class CWConnection extends Connection {
/** API connects asynchronously, so API handle is a promise. */
declare api: CosmWasmClient
- constructor (properties: Partial) {
+ constructor (properties: ConstructorParameters[0] & { api: CosmWasmClient }) {
super(properties)
- assign(this, properties, [
- 'api',
- ])
+ this.api = properties.api
//if (!this.url) {
//throw new Error('No connection URL.')
//}
@@ -62,10 +111,6 @@ export class CWConnection extends Connection {
//}
}
- override authenticate (identity: CWIdentity): CWAgent {
- return new CWAgent({ connection: this, identity })
- }
-
/** Handle to the API's internal query client. */
get queryClient (): Promise> {
return Promise.resolve(this.api).then(api=>(api as any)?.queryClient)
@@ -76,7 +121,7 @@ export class CWConnection extends Connection {
return Promise.resolve(this.api).then(api=>(api as any)?.tmClient)
}
- abciQuery (path, params = new Uint8Array()) {
+ 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)
@@ -88,20 +133,26 @@ export class CWConnection extends Connection {
Promise
{
const api = await this.api
- if ((parameter as { height })?.height) {
- const { id, header, txs } = await api.getBlock((parameter as { height }).height)
+ if ((parameter as { height: number })?.height) {
+ const { id, header, txs } = await api.getBlock((parameter as { height: number }).height)
return new CWBlock({
- hash: id,
+ chain: this.chain,
+ id,
height: header.height,
+ timestamp: header.time,
+ transactions: [],
rawTxs: txs as Uint8Array[],
})
- } else if ((parameter as { hash })?.hash) {
+ } else if ((parameter as { hash: string })?.hash) {
throw new Error('CWConnection.fetchBlock({ hash }): unimplemented!')
} else {
const { id, header, txs } = await api.getBlock()
return new CWBlock({
- hash: id,
+ chain: this.chain,
+ id,
height: header.height,
+ timestamp: header.time,
+ transactions: [],
rawTxs: txs as Uint8Array[],
})
}
@@ -146,6 +197,9 @@ export class CWConnection extends Connection {
return Promise.all([
this.queryClient,
this.tendermintClient
- ]).then(()=>new CWStaking.Validator({ address }).fetchDetails(this))
+ ]).then(()=>new CWStaking.Validator({
+ chain: this.chain,
+ address
+ }).fetchDetails())
}
}
diff --git a/packages/cw/cw-identity.ts b/packages/cw/cw-identity.ts
index 2586302885..0947d41cba 100644
--- a/packages/cw/cw-identity.ts
+++ b/packages/cw/cw-identity.ts
@@ -27,10 +27,15 @@ import * as CWCompute from './cw-compute'
import * as CWStaking from './cw-staking'
export class CWAgent extends Agent {
+ constructor (properties: ConstructorParameters[0] & {
+ connection: CWSigningConnection
+ }) {
+ super(properties)
+ this.#connection = properties.connection
+ }
override batch (): CWBatch {
return new CWBatch({ agent: this })
}
-
#connection: CWSigningConnection
getConnection (): CWSigningConnection {
return this.#connection
@@ -38,11 +43,17 @@ export class CWAgent extends Agent {
}
export class CWSigningConnection extends SigningConnection {
+ constructor (
+ properties: ConstructorParameters[0]
+ & Pick
+ ) {
+ super(properties)
+ this.api = properties.api
+ }
+
/** API connects asynchronously, so API handle is a promise. */
declare api: SigningCosmWasmClient
- address: Address
-
async sendImpl (...args: Parameters) {
return await CWBank.send(this, ...args)
}
diff --git a/packages/cw/cw-staking.ts b/packages/cw/cw-staking.ts
index e199781850..105b47b301 100644
--- a/packages/cw/cw-staking.ts
+++ b/packages/cw/cw-staking.ts
@@ -1,13 +1,10 @@
-import { base16, SHA256 } from '@fadroma/agent'
+import { base16, SHA256, Validator } from '@fadroma/agent'
import type { Address } from '@fadroma/agent'
import { Amino, Proto } from '@hackbg/cosmjs-esm'
-import type { CWConnection } from './cw-connection'
+import type { CWChain, CWConnection } from './cw-connection'
export async function getValidators (
- connection: {
- tendermintClient,
- abciQuery
- },
+ connection: CWConnection,
{ pagination, details, Validator = CWValidator as V }: {
pagination?: [number, number],
details?: boolean,
@@ -35,36 +32,33 @@ export async function getValidators (
const result: Array> = []
for (const { address, pubkey, votingPower, proposerPriority } of validators) {
const info = new Validator({
- address: base16.encode(address),
- publicKey: pubkey.data,
+ chain: connection.chain,
+ address: base16.encode(address),
+ publicKey: pubkey?.data,
votingPower,
proposerPriority,
}) as InstanceType
result.push(info)
if (details) {
- await info.fetchDetails(connection)
+ await info.fetchDetails()
}
}
return result
}
-class CWValidator {
- address: string
- publicKey: string
- votingPower?: bigint
- proposerPriority?: bigint
-
- constructor ({ address, publicKey, votingPower, proposerPriority }: {
- address?: Address
+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!
- this.address = address!
if (votingPower) {
this.votingPower = BigInt(votingPower)
}
@@ -72,19 +66,24 @@ class CWValidator {
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 (connection: { abciQuery }): Promise {
+ async fetchDetails (): Promise {
const request = Proto.Cosmos.Staking.v1beta1.Query.QueryValidatorRequest.encode({
validatorAddr: this.address
}).finish()
- const value = await connection.abciQuery(
+ const value = await this.chain.getConnection().abciQuery(
'/cosmos.staking.v1beta1.Query/Validator',
request
)
diff --git a/packages/cw/okp4/okp4.ts b/packages/cw/okp4/okp4.ts
index 4fa03869aa..9acecde3bf 100644
--- a/packages/cw/okp4/okp4.ts
+++ b/packages/cw/okp4/okp4.ts
@@ -16,8 +16,18 @@ export * from './okp4-law-stone'
class OKP4CLI extends CLI {}
-class OKP4Chain extends CWChain {
+export default class OKP4Chain extends CWChain {
+
+ /** Connect to OKP4 in testnet mode. */
+ static testnet (options: Partial = {}): Promise {
+ return OKP4Chain.connect({
+ chainId: chainIds.testnet,
+ urls: [...testnets], ...options||{}
+ })
+ }
+
declare connections: OKP4Connection[]
+
}
/** Connection for OKP4. */
@@ -26,16 +36,17 @@ class OKP4Connection extends CWConnection {
/** Default denomination of gas token. */
static gasToken = new Token.Native('uknow')
- /** Transaction fees for this agent. */
- fees = {
- upload: OKP4Connection.gasToken.fee(10000000),
- init: OKP4Connection.gasToken.fee(1000000),
- exec: OKP4Connection.gasToken.fee(1000000),
- send: OKP4Connection.gasToken.fee(1000000),
- }
-
- constructor (options: Partial) {
- super({ ...defaults, ...options } as Partial)
+ constructor (properties: ConstructorParameters[0]) {
+ super({
+ ...defaults,
+ //fees: {
+ //upload: OKP4Connection.gasToken.fee(10000000),
+ //init: OKP4Connection.gasToken.fee(1000000),
+ //exec: OKP4Connection.gasToken.fee(1000000),
+ //send: OKP4Connection.gasToken.fee(1000000),
+ //},
+ ...properties
+ })
}
/** Get clients for all Cognitarium instances, keyed by address. */
@@ -86,11 +97,18 @@ class OKP4Connection extends CWConnection {
class OKP4MnemonicIdentity extends CWMnemonicIdentity {
constructor (properties?: { mnemonic?: string } & Partial) {
- super({ ...defaults, ...properties||{} })
+ super({
+ ...defaults,
+ ...properties||{}
+ })
}
}
-const defaults = { coinType: 118, bech32Prefix: 'okp4', hdAccountIndex: 0, }
+const defaults = {
+ coinType: 118,
+ bech32Prefix: 'okp4',
+ hdAccountIndex: 0,
+}
export {
OKP4CLI as CLI,
@@ -101,11 +119,3 @@ export {
export const chainIds = { testnet: 'okp4-nemeton-1', }
export const testnets = new Set([ 'https://okp4-testnet-rpc.polkachu.com/' ])
-
-/** Connect to OKP4 in testnet mode. */
-export const testnet = (options: Partial = {}): OKP4Connection => {
- return OKP4Chain.connect({
- chainId: chainIds.testnet,
- urls: [...testnets], ...options||{}
- })
-}
diff --git a/packages/cw/tsconfig.json b/packages/cw/tsconfig.json
index ddb8e91514..48785e6e8f 100644
--- a/packages/cw/tsconfig.json
+++ b/packages/cw/tsconfig.json
@@ -10,15 +10,20 @@
"okp4/okp4-objectarium.ts"
],
"include": [],
- "exclude": [ ".ubik", "*.dist.*", "node_modules" ],
+ "exclude": [
+ ".ubik",
+ "*.dist.*",
+ "node_modules"
+ ],
"compilerOptions": {
- "target": "esnext",
- "module": "esnext",
- "moduleResolution": "node",
- "esModuleInterop": true,
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
- "noEmitOnError": true,
- "skipLibCheck": true,
- "experimentalDecorators": true
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+ "strict": true
}
}
diff --git a/packages/namada/namada-cli.ts b/packages/namada/namada-cli.ts
index 6593da5ca5..e1f1089429 100644
--- a/packages/namada/namada-cli.ts
+++ b/packages/namada/namada-cli.ts
@@ -6,7 +6,7 @@ import {
} from './namada-console'
import {
NamadaMnemonicIdentity
-} from './namada-connection'
+} from './namada-identity'
import Namada from './namada'
/** Namada CLI commands. */
@@ -277,7 +277,12 @@ export default class NamadaCLI extends CLI {
process.exit(1)
}
const namada = await Namada.connect({ url })
- const {proposal, votes, result} = await namada.getProposalInfo(Number(number))
+ const proposalInfo = await namada.getProposalInfo(Number(number))
+ if (!proposalInfo) {
+ this.log.error(`No proposal #${number}`)
+ process.exit(1)
+ }
+ const {proposal, votes, result} = proposalInfo
this.log
.log()
.log('Proposal: ', bold(number))
@@ -336,7 +341,12 @@ export default class NamadaCLI extends CLI {
process.exit(1)
}
const namada = await Namada.connect({ url })
- const {proposal, votes, result} = await namada.getProposalInfo(Number(number))
+ const proposalInfo = await namada.getProposalInfo(Number(number))
+ if (!proposalInfo) {
+ this.log.error(`No proposal #${number}`)
+ process.exit(1)
+ }
+ const {proposal, votes, result} = proposalInfo
this.log
.log()
.log('Proposal: ', bold(number))
@@ -386,7 +396,7 @@ export default class NamadaCLI extends CLI {
height = Number(height)
}
const namada = await Namada.connect({ url })
- const block = await namada.fetchBlock({ height })
+ const block = await namada.fetchBlock({ height: height! })
this.log.log()
.log('Block:', bold(block.height))
.log('ID: ', bold(block.id))
@@ -410,13 +420,13 @@ export default class NamadaCLI extends CLI {
let block
do {
block = await namada.fetchBlock({ height: Number(height) })
- height = block.header.height
+ height = block.height
this.log.log()
- .log('Block:', bold(block.header.height))
+ .log('Block:', bold(block.height))
.log('ID: ', bold(block.id))
- .log('Time: ', bold(block.header.time))
+ .log('Time: ', bold(block.timestamp))
.log(bold('Transactions:'))
- for (const tx of block.txsDecoded) {
+ for (const tx of block.transactions) {
this.log.log(tx)
}
height--
diff --git a/packages/namada/namada-connection.ts b/packages/namada/namada-connection.ts
index 0830262796..cb88208713 100644
--- a/packages/namada/namada-connection.ts
+++ b/packages/namada/namada-connection.ts
@@ -83,6 +83,10 @@ export class Namada extends CW.Chain {
return getValidatorAddresses(this.getConnection())
}
+ getValidator (address: string) {
+ return getValidator(this, address)
+ }
+
getValidators (options?: {
details?: boolean,
pagination?: [number, number]
@@ -91,7 +95,7 @@ export class Namada extends CW.Chain {
parallel?: boolean,
parallelDetails?: boolean,
}) {
- return getValidators(this.getConnection(), options)
+ return getValidators(this, options)
}
getValidatorsConsensus () {
@@ -102,10 +106,6 @@ export class Namada extends CW.Chain {
return getValidatorsBelowCapacity(this.getConnection())
}
- getValidator (address: string) {
- return getValidator(this.getConnection(), address)
- }
-
getDelegations (address: string) {
return getDelegations(this.getConnection(), address)
}
diff --git a/packages/namada/namada-decode.ts b/packages/namada/namada-decode.ts
index 8b99c51e80..b358c2113b 100644
--- a/packages/namada/namada-decode.ts
+++ b/packages/namada/namada-decode.ts
@@ -1,8 +1,8 @@
import * as TX from './namada-tx'
import init, { Decode } from './pkg/fadroma_namada.js'
-export function decodeTxs (txs, height): TX.Transaction[] {
- const txsDecoded = []
+export function decodeTxs (txs: unknown[], height: number|bigint): TX.Transaction[] {
+ const txsDecoded: TX.Transaction[] = []
for (const i in txs) {
try {
txsDecoded[i] = TX.Transaction.fromDecoded(txs[i] as any)
diff --git a/packages/namada/namada-pos.ts b/packages/namada/namada-pos.ts
index 1a023a6680..ad1a24a558 100644
--- a/packages/namada/namada-pos.ts
+++ b/packages/namada/namada-pos.ts
@@ -2,6 +2,8 @@ import type { Address } from '@fadroma/agent'
import { Console, assign, base16, optionallyParallel} from '@fadroma/agent'
import { Staking } from '@fadroma/cw'
import { decode, u8, u64, u256, array, set } from '@hackbg/borshest'
+import { NamadaConnection } from './namada-connection'
+import type { Namada } from './namada-connection'
class NamadaPoSParameters {
maxProposalPeriod!: bigint
@@ -45,30 +47,37 @@ class NamadaPoSParameters {
}
class NamadaValidatorMetadata {
+ constructor (
+ properties: Pick
+ ) {
+ assign(this, properties, [ 'email', 'description', 'website', 'discordHandle', 'avatar', ])
+ }
email!: string
description!: string|null
website!: string|null
discordHandle!: string|null
avatar!: string|null
- constructor (properties: Partial) {
- assign(this, properties, [
- 'email',
- 'description',
- 'website',
- 'discordHandle',
- 'avatar',
- ])
- }
}
class NamadaValidator extends Staking.Validator {
- static fromNamadaAddress = (namadaAddress: string) => Object.assign(new this({}), { namadaAddress })
+ constructor (properties: Omit[0], 'chain'> & {
+ chain: Namada,
+ namadaAddress: string
+ }) {
+ super(properties)
+ this.namadaAddress = properties.namadaAddress
+ }
+ get chain (): Namada {
+ return super.chain as unknown as Namada
+ }
namadaAddress!: Address
metadata!: NamadaValidatorMetadata
commission!: NamadaCommissionPair
state!: unknown
stake!: bigint
- async fetchDetails (connection: Connection, options?: { parallel?: boolean }) {
+ async fetchDetails (options?: { parallel?: boolean }) {
+
+ const connection = this.chain.getConnection()
if (!this.namadaAddress) {
const addressBinary = await connection.abciQuery(`/vp/pos/validator_by_tm_addr/${this.address}`)
@@ -76,15 +85,20 @@ class NamadaValidator extends Staking.Validator {
}
const requests: Array<()=>Promise> = [
+
() => connection.abciQuery(`/vp/pos/validator/metadata/${this.namadaAddress}`)
.then(binary => {
if (binary[0] === 1) {
- this.metadata = new NamadaValidatorMetadata(connection.decode.pos_validator_metadata(binary.slice(1)))
+ this.metadata = new NamadaValidatorMetadata(
+ connection.decode.pos_validator_metadata(binary.slice(1)) as
+ ConstructorParameters[0]
+ )
}
})
.catch(e => connection.log.warn(
`Failed to decode validator metadata for ${this.namadaAddress}`
)),
+
() => connection.abciQuery(`/vp/pos/validator/commission/${this.namadaAddress}`)
.then(binary => {
if (binary[0] === 1) {
@@ -94,6 +108,7 @@ class NamadaValidator extends Staking.Validator {
.catch(e => connection.log.warn(
`Failed to decode validator commission pair for ${this.namadaAddress}`
)),
+
() => connection.abciQuery(`/vp/pos/validator/state/${this.namadaAddress}`)
.then(binary => {
if (binary[0] === 1) {
@@ -103,6 +118,7 @@ class NamadaValidator extends Staking.Validator {
.catch(e => connection.log.warn(
`Failed to decode validator state for ${this.namadaAddress}`
)),
+
() => connection.abciQuery(`/vp/pos/validator/stake/${this.namadaAddress}`)
.then(binary => {
if (binary[0] === 1) {
@@ -112,6 +128,7 @@ class NamadaValidator extends Staking.Validator {
.catch(e => connection.log.warn(
`Failed to decode validator stake for ${this.namadaAddress}`
)),
+
]
if (this.namadaAddress && !this.publicKey) {
@@ -159,37 +176,18 @@ export {
NamadaCommissionPair as CommissionPair,
}
-type Connection = {
- log: Console,
- abciQuery: (path: string)=>Promise
- tendermintClient
- decode: {
- address (binary: Uint8Array): string
- addresses (binary: Uint8Array): string[]
- address_to_amount (binary: Uint8Array): object
- pos_parameters (binary: Uint8Array): Partial
- pos_validator_metadata (binary: Uint8Array): Partial
- pos_commission_pair (binary: Uint8Array): Partial
- pos_validator_state (binary: Uint8Array): string
- pos_validator_set (binary: Uint8Array): Array<{
- address: string,
- bondedStake: bigint,
- }>
- }
-}
-
-export async function getStakingParameters (connection: Connection) {
+export async function getStakingParameters (connection: NamadaConnection) {
const binary = await connection.abciQuery("/vp/pos/pos_params")
return new NamadaPoSParameters(connection.decode.pos_parameters(binary))
}
-export async function getTotalStaked (connection: Connection) {
+export async function getTotalStaked (connection: NamadaConnection) {
const binary = await connection.abciQuery("/vp/pos/total_stake")
return decode(u64, binary)
}
export async function getValidators (
- connection: Connection,
+ chain: Namada,
options: Partial[1]> & {
addresses?: string[],
allStates?: boolean,
@@ -198,6 +196,7 @@ export async function getValidators (
interval?: number,
} = {}
) {
+ const connection = chain.getConnection()
if (options.allStates) {
let { addresses } = options
addresses ??= await getValidatorAddresses(connection)
@@ -208,12 +207,16 @@ export async function getValidators (
const [page, perPage] = options.pagination
addresses = addresses.slice((page - 1)*perPage, page*perPage)
}
- const validators = addresses.map(address=>NamadaValidator.fromNamadaAddress(address))
+ const validators = addresses.map(namadaAddress=>new NamadaValidator({
+ chain,
+ address: '',
+ namadaAddress
+ }))
if (options.details) {
if (options.parallel && !options.pagination) {
- throw new Error("set parallel=false or pagination, so as not to bombard the node")
+ throw new Error("set pagination or parallel=false, so as not to bombard the node")
}
- const thunks = validators.map(validator=>()=>validator.fetchDetails(connection, {
+ const thunks = validators.map(validator=>()=>validator.fetchDetails({
parallel: options?.parallelDetails
}))
if (options?.parallel) {
@@ -234,41 +237,48 @@ export async function getValidators (
}
}
-export async function getValidatorAddresses (connection: Connection): Promise {
+export async function getValidatorAddresses (connection: NamadaConnection): Promise {
const binary = await connection.abciQuery("/vp/pos/validator/addresses")
return connection.decode.addresses(binary)
}
-export async function getValidatorsConsensus (connection: Connection) {
+export async function getValidatorsConsensus (connection: NamadaConnection) {
const binary = await connection.abciQuery("/vp/pos/validator_set/consensus")
return connection.decode.pos_validator_set(binary).sort(byBondedStake)
}
-export async function getValidatorsBelowCapacity (connection: Connection) {
+export async function getValidatorsBelowCapacity (connection: NamadaConnection) {
const binary = await connection.abciQuery("/vp/pos/validator_set/below_capacity")
return connection.decode.pos_validator_set(binary).sort(byBondedStake)
}
-const byBondedStake = (a, b)=> (a.bondedStake > b.bondedStake) ? -1
+const byBondedStake = (
+ a: { bondedStake: number|bigint },
+ b: { bondedStake: number|bigint },
+)=> (a.bondedStake > b.bondedStake) ? -1
: (a.bondedStake < b.bondedStake) ? 1
: 0
-export async function getValidator (connection: Connection, address: Address) {
- return await NamadaValidator.fromNamadaAddress(address).fetchDetails(connection)
+export async function getValidator (chain: Namada, namadaAddress: Address) {
+ return await new NamadaValidator({
+ chain,
+ address: '',
+ namadaAddress
+ }).fetchDetails()
}
-export async function getValidatorStake (connection: Connection, address: Address) {
+export async function getValidatorStake (connection: NamadaConnection, address: Address) {
const totalStake = await connection.abciQuery(`/vp/pos/validator/stake/${address}`)
return decode(u256, totalStake)
}
-export async function getDelegations (connection: Connection, address: Address) {
+export async function getDelegations (connection: NamadaConnection, address: Address) {
const binary = await connection.abciQuery(`/vp/pos/delegations/${address}`)
return connection.decode.addresses(binary)
}
export async function getDelegationsAt (
- connection: Connection, address: Address, epoch?: number
+ connection: NamadaConnection, address: Address, epoch?: number
): Promise> {
let query = `/vp/pos/delegations_at/${address}`
epoch = Number(epoch)
diff --git a/packages/namada/namada-tx-base.ts b/packages/namada/namada-tx-base.ts
index d11fd1d992..30cf06b84b 100644
--- a/packages/namada/namada-tx-base.ts
+++ b/packages/namada/namada-tx-base.ts
@@ -3,7 +3,19 @@ import * as Sections from './namada-tx-section'
class NamadaTransaction {
- static fromDecoded = ({ sections, ...header }) => new this({
+ static fromDecoded = ({ sections, ...header }: {
+ sections: Array<
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ | Partial
+ >
+ }) => new this({
...header,
sections: sections.map(section=>{
switch (section.type) {
diff --git a/packages/namada/namada-tx.ts b/packages/namada/namada-tx.ts
index 80791bc558..149c107888 100644
--- a/packages/namada/namada-tx.ts
+++ b/packages/namada/namada-tx.ts
@@ -10,14 +10,13 @@ import { Transaction } from './namada-tx-base'
export class NamadaBlock extends Block {
constructor ({
- transactions, rawTransactions, ...properties
+ rawTransactions, ...properties
}: ConstructorParameters[0]
- & Pick
+ & Pick
) {
super(properties)
- this.transactions = [...transactions||[]]
this.rawTransactions = rawTransactions
}
- transactions: Transaction[]
+ declare transactions: Transaction[]
rawTransactions?: unknown[]
}
diff --git a/packages/namada/tsconfig.json b/packages/namada/tsconfig.json
index b1ddb9df87..18a8b04e94 100644
--- a/packages/namada/tsconfig.json
+++ b/packages/namada/tsconfig.json
@@ -1,14 +1,21 @@
{
- "include": [ "*.ts" ],
- "exclude": [ ".ubik", "*.dist.*", "node_modules" ],
+ "include": [
+ "*.ts"
+ ],
+ "exclude": [
+ ".ubik",
+ "*.dist.*",
+ "node_modules"
+ ],
"compilerOptions": {
- "target": "esnext",
- "module": "esnext",
- "moduleResolution": "node",
- "esModuleInterop": true,
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
- "noEmitOnError": true,
- "skipLibCheck": true,
- "experimentalDecorators": true
+ "noEmitOnError": true,
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+ "strict": true
}
}
diff --git a/toolbox b/toolbox
index d1ea1bbed7..a803b6d908 160000
--- a/toolbox
+++ b/toolbox
@@ -1 +1 @@
-Subproject commit d1ea1bbed78fda8c6d52220b7c484fddebfff125
+Subproject commit a803b6d908e4a8a3934ec6bd691257e7ceeff64c