From ac0581e66d55c2ef48330029dfed20fa0bc9febc Mon Sep 17 00:00:00 2001 From: Adam A Date: Mon, 10 Jun 2024 17:16:06 +0300 Subject: [PATCH] wip: refactor(namada): cleanup --- packages/namada/Namada.ts | 29 ++ packages/namada/NamadaBlock.ts | 133 ++++++++ packages/namada/NamadaChain.ts | 139 ++++++++ packages/namada/NamadaConnection.ts | 112 ++++++ packages/namada/NamadaConsole.ts | 47 +++ .../{namada-decode.ts => NamadaDecode.ts} | 1 - .../{namada-epoch.ts => NamadaEpoch.ts} | 6 +- .../namada/{namada-gov.ts => NamadaGov.ts} | 64 ++-- packages/namada/NamadaIdentity.ts | 15 + .../namada/{namada-pgf.ts => NamadaPGF.ts} | 6 +- .../namada/{namada-pos.ts => NamadaPoS.ts} | 28 +- packages/namada/NamadaTransaction.ts | 68 ++++ packages/namada/namada-connection.ts | 195 ----------- packages/namada/namada-console.ts | 221 ------------ packages/namada/namada-gov-tx.ts | 39 --- packages/namada/namada-identity.ts | 13 - packages/namada/namada-pgf-tx.ts | 23 -- packages/namada/namada-pos-tx.ts | 177 ---------- packages/namada/namada-tx-content.ts | 137 -------- packages/namada/namada-tx-section.ts | 191 ----------- packages/namada/namada-tx.ts | 323 ------------------ packages/namada/namada.test.ts | 11 +- packages/namada/namada.ts | 39 --- packages/namada/package.json | 2 +- src/Agent.ts | 2 +- 25 files changed, 603 insertions(+), 1418 deletions(-) create mode 100644 packages/namada/Namada.ts create mode 100644 packages/namada/NamadaBlock.ts create mode 100644 packages/namada/NamadaChain.ts create mode 100644 packages/namada/NamadaConnection.ts create mode 100644 packages/namada/NamadaConsole.ts rename packages/namada/{namada-decode.ts => NamadaDecode.ts} (89%) rename packages/namada/{namada-epoch.ts => NamadaEpoch.ts} (50%) rename packages/namada/{namada-gov.ts => NamadaGov.ts} (94%) create mode 100644 packages/namada/NamadaIdentity.ts rename packages/namada/{namada-pgf.ts => NamadaPGF.ts} (83%) rename packages/namada/{namada-pos.ts => NamadaPoS.ts} (91%) create mode 100644 packages/namada/NamadaTransaction.ts delete mode 100644 packages/namada/namada-connection.ts delete mode 100644 packages/namada/namada-console.ts delete mode 100644 packages/namada/namada-gov-tx.ts delete mode 100644 packages/namada/namada-identity.ts delete mode 100644 packages/namada/namada-pgf-tx.ts delete mode 100644 packages/namada/namada-pos-tx.ts delete mode 100644 packages/namada/namada-tx-content.ts delete mode 100644 packages/namada/namada-tx-section.ts delete mode 100644 packages/namada/namada-tx.ts delete mode 100644 packages/namada/namada.ts diff --git a/packages/namada/Namada.ts b/packages/namada/Namada.ts new file mode 100644 index 0000000000..5bbcc547b1 --- /dev/null +++ b/packages/namada/Namada.ts @@ -0,0 +1,29 @@ +import NamadaConsole from './NamadaConsole' +import NamadaChain from './NamadaChain' +import NamadaConnection from './NamadaConnection' +import NamadaTransaction from './NamadaTransaction' +import { Decode, initDecoder } from './NamadaDecode' +import * as Identity from './NamadaIdentity' +export { + Decode, + initDecoder, + NamadaConsole as Console, + NamadaChain as Chain, + NamadaConnection as Connection, + NamadaTransaction as Transaction, + Identity +} +export const testnetChainId = NamadaChain.testnetChainId +export const testnetURLs = NamadaChain.testnetURLs +export function connect (...args: Parameters) { + return NamadaChain.connect(...args) +} +export function testnet (...args: Parameters) { + return NamadaChain.testnet(...args) +} +export function mainnet (...args: never) { + throw new Error( + 'Connection details for Namada mainnet are not built into Fadroma yet. ' + + 'You can pass them to Namada.connect function if you have them.' + ) +} diff --git a/packages/namada/NamadaBlock.ts b/packages/namada/NamadaBlock.ts new file mode 100644 index 0000000000..b4cee6c4c1 --- /dev/null +++ b/packages/namada/NamadaBlock.ts @@ -0,0 +1,133 @@ +import { Block } from '@fadroma/cw' +import type { Chain as Namada } from './Namada' +import { Decode } from './NamadaDecode' +import Transaction, { NamadaUndecodedTransaction } from './NamadaTransaction' + +export default class NamadaBlock extends Block { + + constructor ({ + header, responses, ...properties + }: ConstructorParameters[0] + & Pick + ) { + super(properties) + this.#chain = properties.chain + this.#responses = responses + this.header = header + } + + #chain: Namada + get chain (): Namada { + return this.#chain + } + + #responses?: { + block: { url: string, response: string } + results: { url: string, response: string } + } + get responses () { + return this.#responses + } + + /** Block header. */ + header: { + version: object + chainId: string + height: bigint + time: string + lastBlockId: string + lastCommitHash: string + dataHash: string + validatorsHash: string + nextValidatorsHash: string + consensusHash: string + appHash: string + lastResultsHash: string + evidenceHash: string + proposerAddress: string + } + + /** Transaction in block. */ + declare transactions: Transaction[] + + /** Responses from block API endpoints. */ + + static async fetchByHeight ( + { url, decode = Decode, chain }: { + url: string|URL, decode?: typeof Decode, chain?: Namada + }, + { height, raw }: { + height: number|string|bigint, + raw?: boolean, + } + ): Promise { + if (!url) { + throw new Error("Can't fetch block: missing connection URL") + } + // Fetch block and results as undecoded JSON + const blockUrl = `${url}/block?height=${height}` + const resultsUrl = `${url}/block_results?height=${height}` + const [block, results] = await Promise.all([ + fetch(blockUrl).then(response=>response.text()), + fetch(resultsUrl).then(response=>response.text()), + ]) + return this.fromResponses({ + block: { url: blockUrl, response: block, }, + results: { url: resultsUrl, response: results, }, + }, { chain, decode, height }) + } + + static async fetchByHash ( + _1: { url: string|URL, decode?: typeof Decode, chain?: Namada }, + _2: { hash: string, raw?: boolean }, + ): Promise { + throw new Error('NamadaBlock.fetchByHash: not implemented') + } + + static fromResponses ( + responses: NonNullable, + { decode = Decode, chain, height }: { + decode?: typeof Decode + chain?: Namada, + height?: string|number|bigint + }, + ): NamadaBlock { + const { id, header, txs } = decode.block( + responses.block.response, + responses.results.response + ) as { + id: string, + txs: Partial[] + header: NamadaBlock["header"] + } + + return new NamadaBlock({ + id, + header, + + chain: chain!, + height: Number(header.height), + timestamp: header.time, + + transactions: txs.map((tx, i)=>{ + try { + return Transaction.fromDecoded({ + height, + ...tx as any + }) + } catch (error) { + console.error(error) + console.warn(`Failed to decode transaction #${i} in block ${height}, see above for details.`) + return new NamadaUndecodedTransaction({ + error: error as any, + data: tx as any, + }) + } + }), + + responses + }) + + } + +} diff --git a/packages/namada/NamadaChain.ts b/packages/namada/NamadaChain.ts new file mode 100644 index 0000000000..413d64f698 --- /dev/null +++ b/packages/namada/NamadaChain.ts @@ -0,0 +1,139 @@ +import * as CW from '@fadroma/cw' +import type { ChainId } from '@hackbg/fadroma' +import NamadaConnection from './NamadaConnection' +import { Decode, initDecoder } from './NamadaDecode' + +export default class NamadaChain extends CW.Chain { + decode = Decode + + /** Connect to Namada over one or more endpoints. */ + static async connect ( + properties: Parameters[0] & { + chainId?: ChainId + decoder?: string|URL|Uint8Array + } + ): Promise { + if (properties?.decoder) { + await initDecoder(properties.decoder) + } else { + new CW.Console('@fadroma/namada').warn( + "Decoder binary not provided; trying to decode Namada objects will fail." + ) + } + properties ??= {} as any + properties.bech32Prefix ??= "tnam" + return await super.connect(properties || ({} as any)) as NamadaChain + } + + /** Connect to Namada using `testnetChainId` and `testnetURLs`. */ + static testnet (properties: Parameters[0]) { + return this.connect({ + chainId: properties?.chainId || NamadaChain.testnetChainId, + urls: (properties as any)?.url + ? [(properties as any).url] + : ((properties as any)?.urls || [...NamadaChain.testnetURLs]), + }) + } + + /** Default chain ID of testnet. */ + static testnetChainId = 'shielded-expedition.88f17d1d14' + + /** Default RPC endpoints for testnet. */ + static testnetURLs = new Set([ + 'https://namada-testnet-rpc.itrocket.net', + 'https://namada-rpc.stake-machine.com', + 'https://namadarpc.songfi.xyz', + 'https://rpc.testnet.one', + ]) + + static get Connection () { + return NamadaConnection + } + + getConnection (): NamadaConnection { + return this.connections[0] as NamadaConnection + } + + authenticate (...args: unknown[]): never { + throw new Error('Transacting on Namada is currently not supported.') + } + + fetchPGFParameters () { + return this.getConnection().fetchPGFParametersImpl() + } + + fetchPGFStewards () { + return this.getConnection().fetchPGFStewardsImpl() + } + + fetchPGFFundings () { + return this.getConnection().fetchPGFFundingsImpl() + } + + isPGFSteward (address: string) { + return this.getConnection().isPGFStewardImpl(address) + } + + fetchStakingParameters () { + return this.getConnection().fetchStakingParametersImpl() + } + + fetchValidatorAddresses () { + return this.getConnection().fetchValidatorAddressesImpl() + } + + fetchValidator (address: string) { + return this.getConnection().fetchValidatorImpl(address) + } + + fetchValidators (options?: { + details?: boolean, + pagination?: [number, number] + allStates?: boolean, + addresses?: string[], + parallel?: boolean, + parallelDetails?: boolean, + }) { + return this.getConnection().fetchValidators(options) + } + + fetchValidatorsConsensus () { + return this.getConnection().fetchValidatorsConsensusImpl() + } + + fetchValidatorsBelowCapacity () { + return this.getConnection().fetchValidatorsBelowCapacityImpl() + } + + fetchDelegations (address: string) { + return this.getConnection().fetchDelegationsImpl(address) + } + + fetchDelegationsAt (address: string, epoch?: number) { + return this.getConnection().fetchDelegationsAtImpl(address, epoch) + } + + fetchGovernanceParameters () { + return this.getConnection().fetchGovernanceParametersImpl() + } + + fetchProposalCount () { + return this.getConnection().fetchProposalCountImpl() + } + + fetchProposalInfo (id: number) { + return this.getConnection().fetchProposalInfoImpl(id) + } + + fetchCurrentEpoch () { + return this.getConnection().fetchCurrentEpochImpl() + } + + fetchTotalStaked () { + return this.getConnection().fetchTotalStakedImpl() + } + + fetchValidatorStake (address: string) { + return this.getConnection().fetchValidatorStakeImpl(address) + } +} diff --git a/packages/namada/NamadaConnection.ts b/packages/namada/NamadaConnection.ts new file mode 100644 index 0000000000..727bd35f79 --- /dev/null +++ b/packages/namada/NamadaConnection.ts @@ -0,0 +1,112 @@ +import * as CW from '@fadroma/cw' +import Block from './NamadaBlock' +import * as PoS from './NamadaPoS' +import * as PGF from './NamadaPGF' +import * as Gov from './NamadaGov' +import * as Epoch from './NamadaEpoch' +import type { Chain as Namada } from './Namada' + +export default class NamadaConnection extends CW.Connection { + get chain (): Namada { + return super.chain as unknown as Namada + } + get decode () { + return this.chain.decode + } + override async fetchBlockImpl ( + parameter?: ({ height: number }|{ hash: string }) & { raw?: boolean } + ): Promise { + if (!this.url) { + throw new CW.Error("Can't fetch block: missing connection URL") + } + if (!parameter) { + parameter = {} as any + } + if ('height' in parameter!) { + return Block.fetchByHeight(this, parameter) + } else if ('hash' in parameter!) { + return Block.fetchByHash(this, parameter) + } else { + throw new Error('Pass { height } or { hash }') + } + } + + fetchCurrentEpochImpl () { + return Epoch.fetchCurrentEpoch(this) + } + + fetchGovernanceParametersImpl () { + return Gov.fetchGovernanceParameters(this) + } + + fetchProposalCountImpl () { + return Gov.fetchProposalCount(this) + } + + fetchProposalInfoImpl (id: number) { + return Gov.fetchProposalInfo(this, id) + } + + fetchPGFParametersImpl () { + return PGF.fetchPGFParameters(this) + } + + fetchPGFStewardsImpl () { + return PGF.fetchPGFStewards(this) + } + + fetchPGFFundingsImpl () { + return PGF.fetchPGFFundings(this) + } + + isPGFStewardImpl (address: string) { + return PGF.isPGFSteward(this) + } + + fetchStakingParametersImpl () { + return PoS.fetchStakingParameters(this) + } + + fetchValidatorAddressesImpl () { + return PoS.fetchValidatorAddresses(this) + } + + fetchValidatorImpl (address: string) { + return PoS.fetchValidator(this.chain, address) + } + + fetchValidatorsImpl (options?: { + details?: boolean, + pagination?: [number, number] + allStates?: boolean, + addresses?: string[], + parallel?: boolean, + parallelDetails?: boolean, + }) { + return PoS.fetchValidators(this.chain, options) + } + + fetchValidatorsConsensusImpl () { + return PoS.fetchValidatorsConsensus(this) + } + + fetchValidatorsBelowCapacityImpl () { + return PoS.fetchValidatorsBelowCapacity(this) + } + + fetchDelegationsImpl (address: string) { + return PoS.fetchDelegations(this, address) + } + + fetchDelegationsAtImpl (address: string, epoch?: number) { + return PoS.fetchDelegationsAt(this, address, epoch) + } + + fetchTotalStakedImpl () { + return PoS.fetchTotalStaked(this) + } + + fetchValidatorStakeImpl (address: string) { + return PoS.fetchValidatorStake(this, address) + } +} diff --git a/packages/namada/NamadaConsole.ts b/packages/namada/NamadaConsole.ts new file mode 100644 index 0000000000..d3f73508bd --- /dev/null +++ b/packages/namada/NamadaConsole.ts @@ -0,0 +1,47 @@ +import { Console, bold } from '@hackbg/fadroma' +import type Transaction from './NamadaTransaction' +import type { Proposal } from './NamadaGov' +import type { Validator } from './NamadaPoS' + +export default class NamadaConsole extends Console { + + printTx (tx: Partial = {}, indent = 0) { + this.log('-', bold(`${tx.txType} transaction:`)) + .log(' Chain ID: ', bold(tx.chainId)) + .log(' Timestamp: ', bold(tx.timestamp)) + .log(' Expiration:', bold(tx.expiration)) + .log(' Sections: ', bold(tx.sections?.length)) + } + + printValidator (validator: Validator) { + return this + .log('Validator: ', bold(validator.namadaAddress)) + .log(' Address: ', bold(validator.address)) + .log(' Public key: ', bold(validator.publicKey)) + .log(' State: ', bold(Object.keys(validator.state as object)[0])) + .log(' Stake: ', bold(validator.stake)) + .log(' Voting power: ', bold(validator.votingPower)) + .log(' Priority: ', bold(validator.proposerPriority)) + .log(' Commission: ', bold(validator.commission.commissionRate)) + .log(' Max change: ', bold(validator.commission.maxCommissionChangePerEpoch), 'per epoch') + .log('Email: ', bold(validator.metadata?.email||'')) + .log('Website: ', bold(validator.metadata?.website||'')) + .log('Discord: ', bold(validator.metadata?.discordHandle||'')) + .log('Avatar: ', bold(validator.metadata?.avatar||'')) + .log('Description: ', bold(validator.metadata?.description||'')) + } + + printVoteProposal (proposal: { + id: unknown + vote: unknown + voter: unknown + delegations: unknown + }) { + return this.log(bold(' Decoded VoteProposal:')) + .log(' Proposal ID:', bold(proposal.id)) + .log(' Vote: ', bold(JSON.stringify(proposal.vote))) + .log(' Voter: ', bold(JSON.stringify(proposal.voter))) + .log(' Delegations:', bold(JSON.stringify(proposal.delegations))) + } + +} diff --git a/packages/namada/namada-decode.ts b/packages/namada/NamadaDecode.ts similarity index 89% rename from packages/namada/namada-decode.ts rename to packages/namada/NamadaDecode.ts index e98ffabf54..e6ef2f92ba 100644 --- a/packages/namada/namada-decode.ts +++ b/packages/namada/NamadaDecode.ts @@ -1,4 +1,3 @@ -import * as TX from './namada-tx' import init, { Decode } from './pkg/fadroma_namada.js' export async function initDecoder (decoder: string|URL|Uint8Array): Promise { diff --git a/packages/namada/namada-epoch.ts b/packages/namada/NamadaEpoch.ts similarity index 50% rename from packages/namada/namada-epoch.ts rename to packages/namada/NamadaEpoch.ts index 7aeb743e6e..caf7878773 100644 --- a/packages/namada/namada-epoch.ts +++ b/packages/namada/NamadaEpoch.ts @@ -1,8 +1,8 @@ import { decode, u64 } from '@hackbg/borshest' -type Connection = { abciQuery: (path: string) => Promise } - -export async function getCurrentEpoch (connection: Connection) { +export async function fetchCurrentEpoch (connection: { + abciQuery: (path: string) => Promise +}) { const binary = await connection.abciQuery("/shell/epoch") return decode(u64, binary) } diff --git a/packages/namada/namada-gov.ts b/packages/namada/NamadaGov.ts similarity index 94% rename from packages/namada/namada-gov.ts rename to packages/namada/NamadaGov.ts index ae455c8c90..ae2a94b4f4 100644 --- a/packages/namada/namada-gov.ts +++ b/packages/namada/NamadaGov.ts @@ -2,6 +2,16 @@ import { assign } from '@hackbg/fadroma' import type { Address } from '@hackbg/fadroma' import { decode, u64 } from '@hackbg/borshest' +export async function fetchProposalCount (connection: Connection) { + const binary = await connection.abciQuery(`/shell/value/#${INTERNAL_ADDRESS}/counter`) + return decode(u64, binary) as bigint +} + +export async function fetchGovernanceParameters (connection: Connection) { + const binary = await connection.abciQuery(`/vp/governance/parameters`) + return new GovernanceParameters(connection.decode.gov_parameters(binary)) +} + class GovernanceParameters { minProposalFund!: bigint maxProposalCodeSize!: bigint @@ -21,6 +31,28 @@ class GovernanceParameters { } } +export async function fetchProposalInfo (connection: Connection, id: number) { + const proposal = await connection.abciQuery(`/vp/governance/proposal/${id}`) + if (proposal[0] === 0) { + return null + } + const [ votes, result ] = await Promise.all([ + connection.abciQuery(`/vp/governance/proposal/${id}/votes`), + connection.abciQuery(`/vp/governance/stored_proposal_result/${id}`), + ]) + return { + proposal: new GovernanceProposal( + connection.decode.gov_proposal(proposal.slice(1)) + ), + votes: connection.decode.gov_votes(votes).map( + vote=>new GovernanceVote(vote) + ), + result: (result[0] === 0) ? null : new GovernanceProposalResult( + connection.decode.gov_result(result.slice(1)) + ) + } +} + class GovernanceProposal { id!: string content!: Map @@ -110,35 +142,3 @@ type Connection = { gov_result (binary: Uint8Array): Partial } } - -export async function getGovernanceParameters (connection: Connection) { - const binary = await connection.abciQuery(`/vp/governance/parameters`) - return new GovernanceParameters(connection.decode.gov_parameters(binary)) -} - -export async function getProposalCount (connection: Connection) { - const binary = await connection.abciQuery(`/shell/value/#${INTERNAL_ADDRESS}/counter`) - return decode(u64, binary) as bigint -} - -export async function getProposalInfo (connection: Connection, id: number) { - const proposal = await connection.abciQuery(`/vp/governance/proposal/${id}`) - if (proposal[0] === 0) { - return null - } - const [ votes, result ] = await Promise.all([ - connection.abciQuery(`/vp/governance/proposal/${id}/votes`), - connection.abciQuery(`/vp/governance/stored_proposal_result/${id}`), - ]) - return { - proposal: new GovernanceProposal( - connection.decode.gov_proposal(proposal.slice(1)) - ), - votes: connection.decode.gov_votes(votes).map( - vote=>new GovernanceVote(vote) - ), - result: (result[0] === 0) ? null : new GovernanceProposalResult( - connection.decode.gov_result(result.slice(1)) - ) - } -} diff --git a/packages/namada/NamadaIdentity.ts b/packages/namada/NamadaIdentity.ts new file mode 100644 index 0000000000..a51b84024b --- /dev/null +++ b/packages/namada/NamadaIdentity.ts @@ -0,0 +1,15 @@ +import { MnemonicIdentity } from '@fadroma/cw' + +export const coinType = 118 +export const bech32Prefix = 'tnam' +export const hdAccountIndex = 0 + +class NamadaMnemonicIdentity extends MnemonicIdentity { + constructor (properties?: { mnemonic?: string } & Partial) { + super({ coinType, bech32Prefix, hdAccountIndex, ...properties||{} }) + } +} + +export { + NamadaMnemonicIdentity as Mnemonic, +} diff --git a/packages/namada/namada-pgf.ts b/packages/namada/NamadaPGF.ts similarity index 83% rename from packages/namada/namada-pgf.ts rename to packages/namada/NamadaPGF.ts index 66e6d5fe13..d1fec81529 100644 --- a/packages/namada/namada-pgf.ts +++ b/packages/namada/NamadaPGF.ts @@ -30,16 +30,16 @@ type Connection = { } } -export async function getPGFParameters (connection: Connection) { +export async function fetchPGFParameters (connection: Connection) { const binary = await connection.abciQuery(`/vp/pgf/parameters`) return new PGFParameters(connection.decode.pgf_parameters(binary)) } -export async function getPGFStewards (connection: Connection) { +export async function fetchPGFStewards (connection: Connection) { throw new Error("not implemented") } -export async function getPGFFundings (connection: Connection) { +export async function fetchPGFFundings (connection: Connection) { throw new Error("not implemented") } diff --git a/packages/namada/namada-pos.ts b/packages/namada/NamadaPoS.ts similarity index 91% rename from packages/namada/namada-pos.ts rename to packages/namada/NamadaPoS.ts index 8b23497153..c64e0008b1 100644 --- a/packages/namada/namada-pos.ts +++ b/packages/namada/NamadaPoS.ts @@ -2,8 +2,10 @@ import type { Address } from '@hackbg/fadroma' import { Console, assign, base16, optionallyParallel} from '@hackbg/fadroma' 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' +import type { + Chain as Namada, + Connection as NamadaConnection +} from './Namada' class NamadaPoSParameters { maxProposalPeriod!: bigint @@ -177,17 +179,17 @@ export { NamadaCommissionPair as CommissionPair, } -export async function getStakingParameters (connection: NamadaConnection) { +export async function fetchStakingParameters (connection: NamadaConnection) { const binary = await connection.abciQuery("/vp/pos/pos_params") return new NamadaPoSParameters(connection.decode.pos_parameters(binary)) } -export async function getTotalStaked (connection: NamadaConnection) { +export async function fetchTotalStaked (connection: NamadaConnection) { const binary = await connection.abciQuery("/vp/pos/total_stake") return decode(u64, binary) } -export async function getValidators ( +export async function fetchValidators ( chain: Namada, options: Partial[1]> & { addresses?: string[], @@ -200,7 +202,7 @@ export async function getValidators ( const connection = chain.getConnection() if (options.allStates) { let { addresses } = options - addresses ??= await getValidatorAddresses(connection) + addresses ??= await fetchValidatorAddresses(connection) if (options.pagination && (options.pagination as Array).length !== 0) { if (options.pagination.length !== 2) { throw new Error("pagination format: [page, per_page]") @@ -238,17 +240,17 @@ export async function getValidators ( } } -export async function getValidatorAddresses (connection: NamadaConnection): Promise { +export async function fetchValidatorAddresses (connection: NamadaConnection): Promise { const binary = await connection.abciQuery("/vp/pos/validator/addresses") return connection.decode.addresses(binary) } -export async function getValidatorsConsensus (connection: NamadaConnection) { +export async function fetchValidatorsConsensus (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: NamadaConnection) { +export async function fetchValidatorsBelowCapacity (connection: NamadaConnection) { const binary = await connection.abciQuery("/vp/pos/validator_set/below_capacity") return connection.decode.pos_validator_set(binary).sort(byBondedStake) } @@ -260,7 +262,7 @@ const byBondedStake = ( : (a.bondedStake < b.bondedStake) ? 1 : 0 -export async function getValidator (chain: Namada, namadaAddress: Address) { +export async function fetchValidator (chain: Namada, namadaAddress: Address) { return await new NamadaValidator({ chain, address: '', @@ -268,17 +270,17 @@ export async function getValidator (chain: Namada, namadaAddress: Address) { }).fetchDetails() } -export async function getValidatorStake (connection: NamadaConnection, address: Address) { +export async function fetchValidatorStake (connection: NamadaConnection, address: Address) { const totalStake = await connection.abciQuery(`/vp/pos/validator/stake/${address}`) return decode(u256, totalStake) } -export async function getDelegations (connection: NamadaConnection, address: Address) { +export async function fetchDelegations (connection: NamadaConnection, address: Address) { const binary = await connection.abciQuery(`/vp/pos/delegations/${address}`) return connection.decode.addresses(binary) } -export async function getDelegationsAt ( +export async function fetchDelegationsAt ( connection: NamadaConnection, address: Address, epoch?: number ): Promise> { let query = `/vp/pos/delegations_at/${address}` diff --git a/packages/namada/NamadaTransaction.ts b/packages/namada/NamadaTransaction.ts new file mode 100644 index 0000000000..1057ddbf1f --- /dev/null +++ b/packages/namada/NamadaTransaction.ts @@ -0,0 +1,68 @@ +import { assign } from '@hackbg/fadroma' +import { Block } from '@fadroma/cw' +import { Decode } from './NamadaDecode' +import type { Chain as Namada } from './Namada' + +export default class NamadaTransaction { + + constructor (properties: Partial = {}) { + assign(this, properties, [ + 'id', + 'height', + 'chainId', + 'expiration', + 'timestamp', + 'feeToken', + 'feeAmountPerGasUnit', + 'multiplier', + 'gasLimitMultiplier', + 'txType', + 'sections', + 'content', + 'batch', + 'atomic' + ]) + } + + id!: string + height?: number + chainId!: string + expiration!: string|null + timestamp!: string + feeToken?: string + feeAmountPerGasUnit?: string + multiplier?: BigInt + gasLimitMultiplier?: BigInt + atomic!: boolean + txType!: 'Raw'|'Wrapper'|'Decrypted'|'Protocol' + sections!: object[] + content?: object + batch!: Array<{ + hash: string, + codeHash: string, + dataHash: string, + memoHash: string + }> + + static fromDecoded ({ id, sections, type, ...header }: { + id: string, + type: 'Raw'|'Wrapper'|'Decrypted'|'Protocol', + sections: object[] + }) { + return new this({ + ...header, + id, + txType: type, + sections + }) + } +} + +export class NamadaUndecodedTransaction extends NamadaTransaction { + data!: unknown + error!: Error + constructor (properties: Partial = {}) { + super() + assign(this, properties, [ "data", "error" ]) + } +} diff --git a/packages/namada/namada-connection.ts b/packages/namada/namada-connection.ts deleted file mode 100644 index 6603716ff5..0000000000 --- a/packages/namada/namada-connection.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { ChainId } from '@hackbg/fadroma' -import * as CW from '@fadroma/cw' -import { - getTotalStaked, - getStakingParameters, - getValidators, - getValidatorsConsensus, - getValidatorsBelowCapacity, - getValidatorAddresses, - getValidator, - getValidatorStake, - getDelegations, - getDelegationsAt, -} from './namada-pos' -import { - getGovernanceParameters, - getProposalCount, - getProposalInfo -} from './namada-gov' -import { - getCurrentEpoch -} from "./namada-epoch"; -import { - getPGFParameters, - getPGFStewards, - getPGFFundings, - isPGFSteward -} from "./namada-pgf" -import * as TX from './namada-tx' -import { - Decode, - initDecoder, -} from './namada-decode' - -export class Namada extends CW.Chain { - - decode = Decode - - /** Connect to Namada over one or more endpoints. */ - static async connect ( - properties: Parameters[0] & { - chainId?: ChainId - decoder?: string|URL|Uint8Array - } - ): Promise { - if (properties?.decoder) { - await initDecoder(properties.decoder) - } else { - new CW.Console('Namada').warn( - "You didn't provide the 'decoder' property; trying to decode Namada objects will fail." - ) - } - properties ??= {} as any - properties.bech32Prefix ??= "tnam" - return await super.connect(properties || {}) as Namada - } - - /** Connect to Namada using `testnetChainId` and `testnetURLs`. */ - static testnet (properties: Parameters[0]) { - return this.connect({ - chainId: properties?.chainId || Namada.testnetChainId, - urls: (properties as any)?.url - ? [(properties as any).url] - : ((properties as any)?.urls || [...Namada.testnetURLs]), - }) - } - - /** Default chain ID of testnet. */ - static testnetChainId = 'shielded-expedition.88f17d1d14' - - /** Default RPC endpoints for testnet. */ - static testnetURLs = new Set([ - 'https://namada-testnet-rpc.itrocket.net', - 'https://namada-rpc.stake-machine.com', - 'https://namadarpc.songfi.xyz', - 'https://rpc.testnet.one', - ]) - - static get Connection () { - return NamadaConnection - } - - getConnection (): NamadaConnection { - return this.connections[0] as NamadaConnection - } - - authenticate (...args: unknown[]): never { - throw new Error('Transacting on Namada is currently not supported.') - } - - getPGFParameters () { - return getPGFParameters(this.getConnection()) - } - - getPGFStewards () { - return getPGFStewards(this.getConnection()) - } - - getPGFFundings () { - return getPGFFundings(this.getConnection()) - } - - isPGFSteward (address: string) { - return isPGFSteward(this.getConnection()) - } - - getStakingParameters () { - return getStakingParameters(this.getConnection()) - } - - getValidatorAddresses () { - return getValidatorAddresses(this.getConnection()) - } - - getValidator (address: string) { - return getValidator(this, address) - } - - getValidators (options?: { - details?: boolean, - pagination?: [number, number] - allStates?: boolean, - addresses?: string[], - parallel?: boolean, - parallelDetails?: boolean, - }) { - return getValidators(this, options) - } - - getValidatorsConsensus () { - return getValidatorsConsensus(this.getConnection()) - } - - getValidatorsBelowCapacity () { - return getValidatorsBelowCapacity(this.getConnection()) - } - - getDelegations (address: string) { - return getDelegations(this.getConnection(), address) - } - - getDelegationsAt (address: string, epoch?: number) { - return getDelegationsAt(this.getConnection(), address, epoch) - } - - getGovernanceParameters () { - return getGovernanceParameters(this.getConnection()) - } - - getProposalCount () { - return getProposalCount(this.getConnection()) - } - - getProposalInfo (id: number) { - return getProposalInfo(this.getConnection(), id) - } - - getCurrentEpoch () { - return getCurrentEpoch(this.getConnection()) - } - - getTotalStaked () { - return getTotalStaked(this.getConnection()) - } - - getValidatorStake (address: string) { - return getValidatorStake(this.getConnection(), address) - } -} - -export class NamadaConnection extends CW.Connection { - get chain (): Namada { - return super.chain as unknown as Namada - } - get decode () { - return this.chain.decode - } - override async fetchBlockImpl ( - parameter?: ({ height: number }|{ hash: string }) & { raw?: boolean } - ): Promise { - if (!this.url) { - throw new CW.Error("Can't fetch block: missing connection URL") - } - if (!parameter) { - parameter = {} as any - } - if ('height' in parameter!) { - return TX.Block.fetchByHeight(this, parameter) - } else if ('hash' in parameter!) { - return TX.Block.fetchByHash(this, parameter) - } else { - throw new Error('Pass { height } or { hash }') - } - } -} diff --git a/packages/namada/namada-console.ts b/packages/namada/namada-console.ts deleted file mode 100644 index ce8a5fb261..0000000000 --- a/packages/namada/namada-console.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { Console, bold } from '@hackbg/fadroma' -import type { Transaction } from './namada-tx-base' -import type { VoteProposal } from './namada-gov-tx' -import type Section from './namada-tx-section' -import * as Sections from './namada-tx-section' -import type { Validator } from './namada-pos' - -export class NamadaConsole extends Console { - - printTx (tx: Partial = {}, indent = 0) { - this.log('-', bold(`${tx.txType} transaction:`)) - .log(' Chain ID: ', bold(tx.chainId)) - .log(' Timestamp: ', bold(tx.timestamp)) - .log(' Expiration:', bold(tx.expiration)) - .log(' Code hash: ', bold(tx.codeHash)) - .log(' Data hash: ', bold(tx.dataHash)) - .log(' Memo hash: ', bold(tx.memoHash)) - .log(' Sections: ', bold(tx.sections?.length)) - } - - printTxSections (sections: Array> = [], indent = 0) { - console.log(bold(' Sections: ')) - for (const section of sections) { - this.printTxSection(section) - } - return true - } - - printTxSection (section: Partial
= {}, indent = 0) { - switch (true) { - case (section instanceof Sections.Data): - return this.printDataSection(section) - case (section instanceof Sections.ExtraData): - return this.printExtraDataSection(section) - case (section instanceof Sections.Code): - return this.printCodeSection(section) - case (section instanceof Sections.Signature): - return this.printSignatureSection(section) - case (section instanceof Sections.Ciphertext): - return this.printCiphertextSection(section) - case (section instanceof Sections.MaspTx): - return this.printMaspTxSection(section) - case (section instanceof Sections.MaspBuilder): - return this.printMaspBuilderSection(section) - default: - return this.printUnknownSection(section as any) - } - } - - printUnknownSection ( - _: Partial = {}, - indent = 0 - ) { - return this.warn(' Section: Unknown') - } - - printDataSection ( - { salt, data }: Sections.Data, - indent = 0 - ) { - return this - .log(' Section: Data') - .log(' Salt:', salt) - .log(' Data:', data) - } - - printExtraDataSection ( - { salt, code, tag }: Sections.ExtraData, - indent = 0 - ) { - return this - .log(' Section: Extra data') - .log(' Salt:', salt) - .log(' Code:', code) - .log(' Tag: ', tag) - } - - printCodeSection ( - { salt, code, tag }: Sections.Code, - indent = 0 - ) { - return this - .log(' Section: Code') - .log(' Salt:', salt) - .log(' Code:', code) - .log(' Tag: ', tag) - } - - printSignatureSection ( - { targets, signer, signatures }: Partial = {}, - indent = 0 - ) { - this - .log(' Section: Signature') - .log(' Targets: ') - for (const target of targets||[]) { - this - .log(' -', target) - } - if (typeof signer === 'string') { - this - .log(' Signer: ', signer) - } else { - this - .log(' Signer: ') - for (const sign of signer||[]) { - this - .log(' -', sign) - } - } - this - .log(' Signatures:') - for (const [key, value] of Object.entries(signatures||{})) { - this - .log(' -', key, value) - } - return this - } - - printCiphertextSection ( - _: Partial = {}, - indent = 0 - ) { - console.log(' Section: Ciphertext') - } - - printMaspTxSection ( - { - txid, lockTime, expiryHeight, transparentBundle, saplingBundle - }: Partial = {}, - indent = 0 - ) { - this - .log(' MASP TX section') - .log(' TX ID: ', txid) - .log(' Lock time:', lockTime) - .log(' Expiry: ', expiryHeight) - if (transparentBundle) { - this.log(' Transparent bundle:') - this.log(' vIn:') - for (const tx of transparentBundle?.vin||[]) { - this - .log(' - Asset type:', tx.assetType) - .log(' Value: ', tx.value) - .log(' Address: ', tx.address) - } - this.log(' vOut:') - for (const tx of transparentBundle?.vout||[]) { - this - .log(' - Asset type:', tx.assetType) - .log(' Value: ', tx.value) - .log(' Address: ', tx.address) - } - } - if (saplingBundle) { - this - .log(' Sapling bundle:') - .log(' Shielded spends:') - for (const tx of saplingBundle?.shieldedSpends||[]) { - this - .log(' - CV: ', tx.cv) - .log(' Anchor: ', tx.anchor) - .log(' Nullifier:', tx.nullifier) - .log(' RK: ', tx.rk) - .log(' ZKProof: ', tx.zkProof) - } - this.log(' Shielded converts:') - for (const tx of saplingBundle?.shieldedConverts||[]) { - this - .log(' - CV: ', tx.cv) - .log(' Anchor: ', tx.anchor) - .log(' ZKProof:', tx.zkProof) - } - this.log(' Shielded outputs:') - for (const tx of saplingBundle?.shieldedOutputs||[]) { - this - .log(' - CV: ', tx.cv) - .log(' CMU: ', tx.cmu) - .log(' Epheremeral key:', tx.ephemeralKey) - .log(' Enc. ciphertext:', tx.encCiphertext) - .log(' Out. ciphertext:', tx.outCiphertext) - .log(' ZKProof: ', tx.zkProof) - } - } - return this - } - - printMaspBuilderSection ( - _: Partial = {}, - indent = 0 - ) { - return this.warn(' Section: MaspBuilder') - } - - printValidator (validator: Validator) { - return this - .log('Validator: ', bold(validator.namadaAddress)) - .log(' Address: ', bold(validator.address)) - .log(' Public key: ', bold(validator.publicKey)) - .log(' State: ', bold(Object.keys(validator.state as object)[0])) - .log(' Stake: ', bold(validator.stake)) - .log(' Voting power: ', bold(validator.votingPower)) - .log(' Priority: ', bold(validator.proposerPriority)) - .log(' Commission: ', bold(validator.commission.commissionRate)) - .log(' Max change: ', bold(validator.commission.maxCommissionChangePerEpoch), 'per epoch') - .log('Email: ', bold(validator.metadata?.email||'')) - .log('Website: ', bold(validator.metadata?.website||'')) - .log('Discord: ', bold(validator.metadata?.discordHandle||'')) - .log('Avatar: ', bold(validator.metadata?.avatar||'')) - .log('Description: ', bold(validator.metadata?.description||'')) - } - - printVoteProposal (proposal: VoteProposal) { - return this.log(bold(' Decoded VoteProposal:')) - .log(' Proposal ID:', bold(proposal.id)) - .log(' Vote: ', bold(JSON.stringify(proposal.vote))) - .log(' Voter: ', bold(JSON.stringify(proposal.voter))) - .log(' Delegations:', bold(JSON.stringify(proposal.delegations))) - } - -} diff --git a/packages/namada/namada-gov-tx.ts b/packages/namada/namada-gov-tx.ts deleted file mode 100644 index b8cde5b648..0000000000 --- a/packages/namada/namada-gov-tx.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { assign } from '@hackbg/fadroma' - -export class InitProposal { - static noun = 'Proposal Init' - id!: bigint - content!: string - author!: string - type!: unknown - votingStartEpoch!: bigint - votingEndEpoch!: bigint - graceEpoch!: bigint - constructor (properties: Partial = {}) { - assign(this, properties, [ - "id", - "content", - "author", - "type", - "votingStartEpoch", - "votingEndEpoch", - "graceEpoch", - ]) - } -} - -export class VoteProposal { - static noun = 'Proposal Vote' - id!: bigint - vote!: unknown - voter!: unknown - delegations!: unknown[] - constructor (properties: Partial = {}) { - assign(this, properties, [ - "id", - "vote", - "voter", - "delegations" - ]) - } -} diff --git a/packages/namada/namada-identity.ts b/packages/namada/namada-identity.ts deleted file mode 100644 index 375fe5f7e0..0000000000 --- a/packages/namada/namada-identity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as CW from '@fadroma/cw' - -export class NamadaMnemonicIdentity extends CW.MnemonicIdentity { - constructor (properties?: { mnemonic?: string } & Partial) { - super({ ...defaults, ...properties||{} }) - } -} - -const defaults = { - coinType: 118, - bech32Prefix: 'tnam', - hdAccountIndex: 0, -} diff --git a/packages/namada/namada-pgf-tx.ts b/packages/namada/namada-pgf-tx.ts deleted file mode 100644 index 3cd97d6714..0000000000 --- a/packages/namada/namada-pgf-tx.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { assign } from '@hackbg/fadroma' - -export class UpdateStewardCommission { - static noun = 'Steward Commission Update' - steward!: string - commission!: Record - constructor (properties: Partial = {}) { - assign(this, properties, [ - "steward", - "commission" - ]) - } -} - -export class ResignSteward { - static noun = 'Steward Resignation' - steward!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - "steward", - ]) - } -} diff --git a/packages/namada/namada-pos-tx.ts b/packages/namada/namada-pos-tx.ts deleted file mode 100644 index 594b2d01fe..0000000000 --- a/packages/namada/namada-pos-tx.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { assign } from '@hackbg/fadroma' - -export class BecomeValidator { - static noun = 'Become Validator' - address!: string - consensusKey!: string - ethColdKey!: string - ethHotKey!: string - protocolKey!: string - commissionRate!: bigint - maxCommissionRateChange!: bigint - email!: string - description!: string - website!: string - discordHandle!: string - avatar!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'address', - 'consensusKey', - 'ethColdKey', - 'ethHotKey', - 'protocolKey', - 'commissionRate', - 'maxCommissionRateChange', - 'email', - 'description', - 'website', - 'discordHandle', - 'avatar', - ]) - } -} - -export class Bond { - static noun = 'Bonding' - validator!: string - amount!: bigint - source!: null|string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'amount', - 'source', - ]) - } -} - -export class ClaimRewards { - static noun = 'Rewards Claim' - validator!: string - source!: null|string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'source' - ]) - } -} - -export class ConsensusKeyChange { - static noun = 'Validator Consensus Key Change' - validator!: string - consensusKey!: unknown - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'consensusKey' - ]) - } -} - -export class CommissionChange { - static noun = 'Validator Commission Change' - validator!: string - newRate!: bigint - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'newRate' - ]) - } -} - -export class MetaDataChange { - static noun = 'Validator Metadata Change' - validator!: string - email!: null|string - description!: null|string - website!: null|string - discordHandle!: null|string - avatar!: null|string - commissionRate!: null|string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'email', - 'description', - 'website', - 'discordHandle', - 'avatar', - 'commissionRate' - ]) - } -} - -export class Redelegation { - static noun = 'Redelegation' - srcValidator!: string - destValidator!: string - owner!: string - amount!: bigint - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'srcValidator', - 'destValidator', - 'owner', - 'amount' - ]) - } -} - -export class Unbond { - static noun = 'Unbonding' - validator!: string - amount!: bigint - source!: null|string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'amount', - 'source', - ]) - } -} - -export class Withdraw { - static noun = 'Withdrawal' - validator!: string - source!: null|string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'validator', - 'source', - ]) - } -} - -export class DeactivateValidator { - static noun = 'Validator Deactivation' - address!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'address' - ]) - } -} - -export class ReactivateValidator { - static noun = 'Validator Reactivation' - address!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'address' - ]) - } -} - -export class UnjailValidator { - static noun = 'Validator Unjail' - address!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'address' - ]) - } -} diff --git a/packages/namada/namada-tx-content.ts b/packages/namada/namada-tx-content.ts deleted file mode 100644 index 1cb50ed2ec..0000000000 --- a/packages/namada/namada-tx-content.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** Index of inner transaction content. */ - -import { assign } from '@hackbg/fadroma' -import { - InitProposal, - VoteProposal -} from './namada-gov-tx' -import { - UpdateStewardCommission, - ResignSteward -} from './namada-pgf-tx' -import { - BecomeValidator, - Bond, - ClaimRewards, - ConsensusKeyChange, - CommissionChange, - MetaDataChange, - Redelegation, - Unbond, - Withdraw, - DeactivateValidator, - ReactivateValidator, - UnjailValidator -} from './namada-pos-tx' - -export { - InitProposal, - VoteProposal, - - UpdateStewardCommission, - ResignSteward, - - BecomeValidator, - Bond, - ClaimRewards, - ConsensusKeyChange, - CommissionChange, - MetaDataChange, - Redelegation, - Unbond, - Withdraw, - DeactivateValidator, - ReactivateValidator, - UnjailValidator -} - -export class InitAccount { - static noun = 'Account Init' - publicKeys!: string[] - vpCodeHash!: string - threshold!: bigint - constructor (properties: Partial = {}) { - assign(this, properties, [ - "publicKeys", - "vpCodeHash", - "threshold" - ]) - } -} - -export class UpdateAccount { - static noun = 'Account Update' - address!: string - publicKeys!: string[] - vpCodeHash!: string - threshold!: bigint - constructor (properties: Partial = {}) { - assign(this, properties, [ - "address", - "publicKeys", - "vpCodeHash", - "threshold" - ]) - } -} - -export class RevealPK { - static noun = 'PK Reveal' - pk!: string - constructor (properties: Partial = {}) { - assign(this, properties, [ - "pk" - ]) - } -} - -export class Transfer { - static noun = 'Transfer' - source!: string - target!: string - token!: string - amount!: bigint - key!: string - shielded!: unknown - constructor (properties: Partial = {}) { - assign(this, properties, [ - "source", - "target", - "token", - "amount", - "key", - "shielded" - ]) - } -} - -export class IBC { - static noun = 'IBC' -} - -export class BridgePool {} - -/** Mapping of target WASM to transaction kind. */ -export default { - 'tx_become_validator.wasm': BecomeValidator, - 'tx_bond.wasm': Bond, - 'tx_change_consensus_key.wasm': ConsensusKeyChange, - 'tx_change_validator_commission.wasm': CommissionChange, - 'tx_change_validator_metadata.wasm': MetaDataChange, - 'tx_claim_rewards.wasm': ClaimRewards, - 'tx_deactivate_validator.wasm': DeactivateValidator, - 'tx_init_account.wasm': InitAccount, - 'tx_init_proposal.wasm': InitProposal, - 'tx_redelegate.wasm': Redelegation, - 'tx_reactivate_validator.wasm': ReactivateValidator, - 'tx_resign_steward.wasm': ResignSteward, - 'tx_reveal_pk.wasm': RevealPK, - 'tx_transfer.wasm': Transfer, - 'tx_unbond.wasm': Unbond, - 'tx_unjail_validator.wasm': UnjailValidator, - 'tx_update_account.wasm': UpdateAccount, - 'tx_update_steward_commission.wasm': UpdateStewardCommission, - 'tx_vote_proposal.wasm': VoteProposal, - 'tx_withdraw.wasm': Withdraw, - 'tx_ibc.wasm': IBC, -} diff --git a/packages/namada/namada-tx-section.ts b/packages/namada/namada-tx-section.ts deleted file mode 100644 index f86bded4f5..0000000000 --- a/packages/namada/namada-tx-section.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { assign } from '@hackbg/fadroma' -import type { Fields } from '@hackbg/borshest' - -class Section { - static noun = 'Section' - type!: null - |'Data' - |'ExtraData' - |'Code' - |'Signature' - |'Ciphertext' - |'MaspTx' - |'MaspBuilder' - |'Header' - constructor (properties: Partial
= {}) { - assign(this, properties, [ - "type" - ]) - } -} - -class UnknownSection extends Section { - static noun = 'Unknown Section' - type = null - data!: unknown - constructor (data: unknown) { - super() - this.data = data - } -} - -class DataSection extends Section { - static noun = 'Data' - type = 'Data' as 'Data' - salt!: string - data!: string - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ 'salt', 'data' ]) - } -} - -class ExtraDataSection extends Section { - static noun = 'Extra Data' - type = 'ExtraData' as 'ExtraData' - salt!: string - code!: string - tag!: string - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ 'salt', 'code', 'tag' ]) - } -} - -class CodeSection extends Section { - static noun = 'Code' - type = 'Code' as 'Code' - salt!: string - code!: string - tag!: string - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ 'salt', 'code', 'tag' ]) - } -} - -class SignatureSection extends Section { - static noun = 'Signature' - type = 'Signature' as 'Signature' - targets!: string[] - signer!: string|string[] - signatures!: string[] - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ 'targets', 'signer', 'signatures' ]) - } -} - -class CiphertextSection extends Section { - static noun = 'Ciphertext' - type = 'Ciphertext' as 'Ciphertext' -} - -class MaspTxSection extends Section { - static noun = 'MASP Transaction' - type = 'MaspTx' as 'MaspTx' - txid!: string - lockTime!: string - expiryHeight!: string|null - transparentBundle!: null|{ - vin: Array<{ - assetType: string, - value: bigint, - address: string - }> - vout: Array<{ - assetType: string, - value: bigint, - address: string - }> - } - saplingBundle!: null|{ - shieldedSpends: Array<{ - cv: string - anchor: string - nullifier: string - rk: string - zkProof: string - }> - shieldedConverts: Array<{ - cv: string - anchor: string - zkProof: string - }> - shieldedOutputs: Array<{ - cv: string, - cmu: string, - ephemeralKey: string, - encCiphertext: string - outCiphertext: string - zkProof: string - }> - valueBalance: Record - } - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ - 'txid', - 'lockTime', - 'expiryHeight', - 'transparentBundle', - 'saplingBundle' - ]) - } -} - -class MaspBuilderSection extends Section { - static noun = 'MASP Builder' - type = 'MaspBuilder' as 'MaspBuilder' - target!: string - assetTypes!: Array<{ - token: string, - denom: number, - position: number, - epoch?: number - }> - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ - 'target', - 'assetTypes' - ]) - } -} - -class HeaderSection extends Section { - static noun = 'Header' - type = 'Header' as 'Header' - chainId!: string - expiration!: string|null - timestamp!: string - codeHash!: string - dataHash!: string - memoHash!: string - txType!: 'Raw'|'Wrapper'|'Decrypted'|'Protocol' - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ - 'chainId', - 'expiration', - 'timestamp', - 'codeHash', - 'dataHash', - 'memoHash', - 'txType' - ]) - } -} - -export default Section -export { - UnknownSection as Unknown, - DataSection as Data, - CodeSection as Code, - ExtraDataSection as ExtraData, - SignatureSection as Signature, - CiphertextSection as Ciphertext, - MaspTxSection as MaspTx, - MaspBuilderSection as MaspBuilder, - HeaderSection as Header -} diff --git a/packages/namada/namada-tx.ts b/packages/namada/namada-tx.ts deleted file mode 100644 index b940c6f99a..0000000000 --- a/packages/namada/namada-tx.ts +++ /dev/null @@ -1,323 +0,0 @@ -export { default as Section } from './namada-tx-section' -export * as Sections from './namada-tx-section' -export { default as wasmToContent } from './namada-tx-content' -export * as Contents from './namada-tx-content' - -import { assign } from '@hackbg/fadroma' -import { Block } from '@fadroma/cw' -import { Decode } from './namada-decode' -import type { Namada } from './namada-connection' -import type Section from './namada-tx-section' -import * as Sections from './namada-tx-section' - -class NamadaBlock extends Block { - - constructor ({ - header, responses, ...properties - }: ConstructorParameters[0] - & Pick - ) { - super(properties) - this.#chain = properties.chain - this.#responses = responses - this.header = header - } - - #chain: Namada - get chain (): Namada { - return this.#chain - } - - #responses?: { - block: { url: string, response: string } - results: { url: string, response: string } - } - get responses () { - return this.#responses - } - - /** Block header. */ - header: { - version: object - chainId: string - height: bigint - time: string - lastBlockId: string - lastCommitHash: string - dataHash: string - validatorsHash: string - nextValidatorsHash: string - consensusHash: string - appHash: string - lastResultsHash: string - evidenceHash: string - proposerAddress: string - } - - /** Transaction in block. */ - declare transactions: NamadaTransaction[] - - /** Responses from block API endpoints. */ - - static async fetchByHeight ( - { url, decode = Decode, chain }: { - url: string|URL, decode?: typeof Decode, chain?: Namada - }, - { height, raw }: { - height: number|string|bigint, - raw?: boolean, - } - ): Promise { - if (!url) { - throw new Error("Can't fetch block: missing connection URL") - } - // Fetch block and results as undecoded JSON - const blockUrl = `${url}/block?height=${height}` - const resultsUrl = `${url}/block_results?height=${height}` - const [block, results] = await Promise.all([ - fetch(blockUrl).then(response=>response.text()), - fetch(resultsUrl).then(response=>response.text()), - ]) - return this.fromResponses({ - block: { url: blockUrl, response: block, }, - results: { url: resultsUrl, response: results, }, - }, { chain, decode, height }) - } - - static async fetchByHash ( - _1: { url: string|URL, decode?: typeof Decode, chain?: Namada }, - _2: { hash: string, raw?: boolean }, - ): Promise { - throw new Error('NamadaBlock.fetchByHash: not implemented') - } - - static fromResponses ( - responses: NonNullable, - { decode = Decode, chain, height }: { - decode?: typeof Decode - chain?: Namada, - height?: string|number|bigint - }, - ): NamadaBlock { - const { id, header, txs } = decode.block( - responses.block.response, - responses.results.response - ) as { - id: string, - txs: Partial[] - header: NamadaBlock["header"] - } - - return new NamadaBlock({ - id, - header, - - chain: chain!, - height: Number(header.height), - timestamp: header.time, - - transactions: txs.map((tx, i)=>{ - try { - return NamadaTransaction.fromDecoded({ - height, - ...tx as any - }) - } catch (error) { - console.error(error) - console.warn(`Failed to decode transaction #${i} in block ${height}, see above for details.`) - return new NamadaUndecodedTransaction({ - error: error as any, - data: tx as any, - }) - } - }), - - responses - }) - - } - -} - -class NamadaTransaction { - - constructor (properties: Partial = {}) { - assign(this, properties, [ - 'id', - 'height', - 'chainId', - 'expiration', - 'timestamp', - 'feeToken', - 'feeAmountPerGasUnit', - 'multiplier', - 'gasLimitMultiplier', - 'txType', - 'sections', - 'content', - 'batch', - 'atomic' - ]) - } - - id!: string - height?: number - chainId!: string - expiration!: string|null - timestamp!: string - feeToken?: string - feeAmountPerGasUnit?: string - multiplier?: BigInt - gasLimitMultiplier?: BigInt - atomic!: boolean - txType!: 'Raw'|'Wrapper'|'Decrypted'|'Protocol' - sections!: Section[] - content?: object - batch!: Array<{ - hash: string, - codeHash: string, - dataHash: string, - memoHash: string - }> - - static fromDecoded ({ id, sections, type, ...header }: { - id: string, - type: 'Raw'|'Wrapper'|'Decrypted'|'Protocol', - sections: Array< - | Partial - | Partial - | Partial - | Partial - | Partial - | Partial - | Partial - | Partial - | Partial - > - }) { - return new this({ - ...header, - id, - txType: type, - sections: sections.map(section=>{ - switch (section.type) { - case 'Data': - return new Sections.Data(section) - case 'ExtraData': - return new Sections.ExtraData(section) - case 'Code': - return new Sections.Code(section) - case 'Signature': - return new Sections.Signature(section) - case 'Ciphertext': - return new Sections.Ciphertext() - case 'MaspBuilder': - return new Sections.MaspBuilder(section) - case 'Header': - return new Sections.Header(section) - case 'MaspTx': - return new Sections.MaspTx(section) - default: - return new Sections.Unknown(section) - } - }) - }) - } -} - -class NamadaUndecodedTransaction extends NamadaTransaction { - data!: unknown - error!: Error - constructor (properties: Partial = {}) { - super() - assign(this, properties, [ "data", "error" ]) - } -} - -class NamadaRawTransaction extends NamadaTransaction { - txType = 'Raw' as 'Raw' - //constructor (header: object, details: object, sections: object[]) { - //super(header, sections) - //this.txType = 'Raw' - //} -} - -class NamadaWrapperTransaction extends NamadaTransaction { - txType = 'Wrapper' as 'Wrapper' - //declare fee: { - //token: string - //amountPerGasUnit: { - //amount: bigint, - //denomination: number - //}, - //} - //declare pk: string - //declare epoch: bigint - //declare gasLimit: bigint - //declare unshieldSectionHash: string|null - //constructor (header: object, details: object, sections: object[]) { - //super(header, sections) - //assignCamelCase(this, details, wrapperTransactionFields.map(x=>x[0] as string)) - //this.txType = 'Wrapper' - //} - -//export const wrapperTransactionFields: Fields = [ - //["fee", struct( - //["amountPerGasUnit", struct( - //["amount", u256], - //["denomination", u8], - //)], - //["token", addr], - //)], - //["pk", pubkey], - //["epoch", u64], - //["gasLimit", u64], - //["unshieldSectionHash", option(hashSchema)], -//] -} - -class NamadaDecryptedTransaction extends NamadaTransaction { - txType = 'Decrypted' as 'Decrypted' - //undecryptable: boolean -} - -class NamadaProtocolTransaction extends NamadaTransaction { - txType = 'Protocol' as 'Protocol' - //pk: string - //tx: |'EthereumEvents' - //|'BridgePool' - //|'ValidatorSetUpdate' - //|'EthEventsVext' - //|'BridgePoolVext' - //|'ValSetUpdateVext' - //constructor (header: object, details: object, sections: object[]) { - //super(header, sections) - //assignCamelCase(this, details, protocolTransactionFields.map(x=>x[0] as string)) - //this.txType = 'Protocol' - //} - -//export const protocolTransactionFields: Fields = [ - //["pk", pubkey], - //["tx", variants( - //['EthereumEvents', unit], - //['BridgePool', unit], - //['ValidatorSetUpdate', unit], - //['EthEventsVext', unit], - //['BridgePoolVext', unit], - //['ValSetUpdateVext', unit], - //)], -//] -} - -export { - NamadaBlock as Block, - NamadaTransaction as Transaction, -} - -export const Transactions = { - Undecoded: NamadaUndecodedTransaction, - Raw: NamadaRawTransaction, - Wrapper: NamadaWrapperTransaction, - Decrypted: NamadaDecryptedTransaction, - Protocol: NamadaProtocolTransaction, -} diff --git a/packages/namada/namada.test.ts b/packages/namada/namada.test.ts index 848f1f015d..f59a43090c 100644 --- a/packages/namada/namada.test.ts +++ b/packages/namada/namada.test.ts @@ -1,19 +1,18 @@ -import Namada, { initDecoder } from './namada' -import { NamadaConsole } from './namada-console' +import * as Namada from './Namada' import init, { Decode } from './pkg/fadroma_namada.js' import { readFileSync } from 'node:fs' -const console = new NamadaConsole('test') +const console = new Namada.Console('test') export default async function main () { - await initDecoder(readFileSync('./pkg/fadroma_namada_bg.wasm')) + await Namada.initDecoder(readFileSync('./pkg/fadroma_namada_bg.wasm')) const namada = await Namada.connect({ url: 'https://namada-testnet-rpc.itrocket.net' }) - console.log(await namada.getDelegationsAt( + console.log(await namada.fetchDelegationsAt( 'tnam1qpr2uzf9pgrd6sucp34wq5gss5rm2un5lszcwzqc' )) - const test = await namada.getValidators({ + const test = await namada.fetchValidators({ details: true, parallel: false, parallelDetails: true, diff --git a/packages/namada/namada.ts b/packages/namada/namada.ts deleted file mode 100644 index 1e2ace4cd5..0000000000 --- a/packages/namada/namada.ts +++ /dev/null @@ -1,39 +0,0 @@ -export { - Namada as default, - NamadaConnection as Connection -} from './namada-connection' -export { - NamadaMnemonicIdentity as MnemonicIdentity, -} from './namada-identity' -export { - initDecoder, - Decode -} from './namada-decode' -export { - Transaction -} from './namada-tx' -export * as TX from './namada-tx' -export { - NamadaConsole as Console -} from './namada-console' - -import { Namada } from './namada-connection' - -export function connect (...args: Parameters) { - return Namada.connect(...args) -} - -export function testnet (...args: Parameters) { - return Namada.testnet(...args) -} - -export function mainnet (...args: never) { - throw new Error( - 'Connection details for Namada mainnet are not built into Fadroma yet. ' + - 'You can pass them to Namada.connect function if you have them.' - ) -} - -export const testnetChainId = Namada.testnetChainId - -export const testnetURLs = Namada.testnetURLs diff --git a/packages/namada/package.json b/packages/namada/package.json index b522bd1e72..0a6d6e1945 100644 --- a/packages/namada/package.json +++ b/packages/namada/package.json @@ -1,7 +1,7 @@ { "name": "@fadroma/namada", "type": "module", - "main": "namada.ts", + "main": "Namada.ts", "files": [ "*.ts", "!target", "pkg/*" ], "version": "1.0.0-rc.27", "license": "AGPL-3.0-only", diff --git a/src/Agent.ts b/src/Agent.ts index a65e735972..90921dbc63 100644 --- a/src/Agent.ts +++ b/src/Agent.ts @@ -64,7 +64,7 @@ export abstract class Agent extends Logged { contract: CodeId|Partial, options: Partial & { initMsg: Into, - initSend?: Token.Native[] + initSend?: Token.ICoin[] } ): Promise