Skip to content

Commit

Permalink
feat: clients, separation of concerns
Browse files Browse the repository at this point in the history
I figured out that we actually have 5 clients:
- base client – knows only the `apiUrl`
- public client - knows the `apiUrl` and `CosmWasmClient`
- wallet client - knows the `apiUrl` and `SigningCosmWasmClient`
- account-public client - knows it's accountId, `apiUrl` and `CosmWasmClient`
- account-wallet client - knows it's accountId, `apiUrl` and `SigningCosmWasmClient`

The commit is still WIP, yet mostly it's just the functionality of the abstract
account that is left to be implemented.

Following folders were created to manage the codebase effectively:
- `actions` – contains all actions that clients are able to perform
|  - `public` – contains all public client actions
|  - `wallet` – contains all wallet client actions
|  - `account-public` – contains all account-public client actions
|  - `account-wallet` – contains all account-wallet client actions
|  ... - base client actions
|  Some of them are grouped by module groups, such as `ans`, `account-factory`, `manager` etc.
- `utils` – contains all utility data-transformation, formatting functions
– `clients` – contains the clients implementations
|  - `decorators` – contains the decorators to inject the client data and bind those to clients
- `types` – contains types

With such a structure, we're clearly separating the business logic from the utilitary logic that
mostly concerns different data transormations.

The focus here is also to keep the functions with the most minimal API as possible, so that hardly
re-uses `ts-codegen` generated types whenever possible.

The API might not look user-friendly at the moment, but later on we will provide aliases for more
user-friendly function names.
  • Loading branch information
dalechyn committed Jan 16, 2024
1 parent 172a270 commit eff9b5c
Show file tree
Hide file tree
Showing 123 changed files with 1,211 additions and 1,286 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"typescript.preferences.importModuleSpecifier": "shortest",
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"source.organizeImports.biome": true
"source.organizeImports.biome": "explicit"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { request } from 'graphql-request'
import { gql } from '../../codegen/gql'

export async function getAccountFactoryAddressFromApi(
apiUrl: string,
chainId: string,
) {
const deploymentData = await request(
apiUrl,
gql(/* GraphQL */ `
query AccountFactoryDeployment($chain: ID!) {
version
deployment(chain: $chain) {
accountFactory
}
chainInfo(chain: $chain) {
rpcUrl
}
}
`),
{
chain: chainId,
},
)

return deploymentData.deployment.accountFactory
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { getVersionControlQueryClientFromApi } from 'src/actions/public/version-control/get-version-control-query-client-from-api'
import { VersionControlTypes } from 'src/codegen/abstract'
import { accountIdToParameter } from 'src/utils/account-id'

export async function getAccountBaseAddressesFromApi(
accountId: VersionControlTypes.AccountId,
cosmWasmClient: CosmWasmClient,
apiUrl: string,
) {
const versionControlQueryClient = await getVersionControlQueryClientFromApi(
cosmWasmClient,
apiUrl,
)
const { account_base: accountBase } =
await versionControlQueryClient.accountBase({
accountId: accountIdToParameter(accountId),
})
return {
managerAddress: accountBase.manager,
proxyAddress: accountBase.proxy,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { ManagerQueryClient, VersionControlTypes } from 'src/codegen/abstract'
import { getAccountBaseAddressesFromApi } from '../get-account-base-addresses-from-api'

export async function getManagerQueryClientFromApi(
accountId: VersionControlTypes.AccountId,
cosmWasmClient: CosmWasmClient,
apiUrl: string,
) {
const { managerAddress } = await getAccountBaseAddressesFromApi(
accountId,
cosmWasmClient,
apiUrl,
)

return new ManagerQueryClient(cosmWasmClient, managerAddress)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { ProxyQueryClient, VersionControlTypes } from 'src/codegen/abstract'
import { getAccountBaseAddressesFromApi } from '../get-account-base-addresses-from-api'

export async function getProxyQueryClientFromApi(
accountId: VersionControlTypes.AccountId,
cosmWasmClient: CosmWasmClient,
apiUrl: string,
) {
const { proxyAddress } = await getAccountBaseAddressesFromApi(
accountId,
cosmWasmClient,
apiUrl,
)

return new ProxyQueryClient(cosmWasmClient, proxyAddress)
}
27 changes: 27 additions & 0 deletions packages/core/src/actions/ans/get-ans-host-address-from-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { request } from 'graphql-request'
import { gql } from '../../codegen/gql'

export async function getAnsHostAddressFromApi(
apiUrl: string,
chainId: string,
) {
const deploymentData = await request(
apiUrl,
gql(/* GraphQL */ `
query AnsHostDeployment($chain: ID!) {
version
deployment(chain: $chain) {
ansHost
}
chainInfo(chain: $chain) {
rpcUrl
}
}
`),
{
chain: chainId,
},
)

return deploymentData.deployment.ansHost
}
43 changes: 43 additions & 0 deletions packages/core/src/actions/get-accounts-of-owner-from-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import request from 'graphql-request'
import { gql } from '../codegen/gql'
import { accountIdApiFormatToAccountId } from '../utils/account-id/account-id-api-format-to-account-id'

export async function getAccountsOfOwnerFromApi(
apiUrl: string,
owner: string,
chains: string[],
) {
const result = await request(
apiUrl,
gql(/* GraphQL */ `
query Accounts($chains: [ID!]!, $page: Page, $filter: AccountFilter) {
accounts(chains: $chains, page: $page, filter: $filter) {
id
chain
accountId {
chainName
sequence
trace
}
namespace
owner
info {
name
description
link
governance {
governanceType
}
}
proxy
manager
modules {
id
}
}
}
`),
{ filter: { owner }, chains },
)
return result.accounts.map((r) => accountIdApiFormatToAccountId(r.accountId))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import {
VersionControlQueryClient,
VersionControlTypes,
} from '../../../codegen/abstract'
import { versionControlModuleToAddress } from '../../../utils/version-control/version-control-module-to-address'

const ACCOUNT_FACTORY_MODULE_NAME = 'account-factory'

export async function getAccountFactoryAddress(
cosmWasmClient: CosmWasmClient,
versionControlAddress: string,
version?: string,
) {
const registryClient = new VersionControlQueryClient(
cosmWasmClient,
versionControlAddress,
)

const [accountFactoryAddress] = await registryClient
.modules({
infos: [
{
name: ACCOUNT_FACTORY_MODULE_NAME,
namespace: 'abstract',
version: version ? { version } : 'latest',
} satisfies VersionControlTypes.ModuleInfo,
],
})
.then(({ modules }) =>
modules.map(({ module }) => versionControlModuleToAddress(module)),
)

if (!accountFactoryAddress) {
throw new Error(
`Could not fetch factoryAddress for version ${version} from registry ${versionControlAddress}`,
)
}

return accountFactoryAddress
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import { AccountFactoryQueryClient } from '../../../codegen/abstract'
import { getAccountFactoryAddressFromApi } from '../../account-factory/get-account-factory-address-from-api'

export async function getAccountFactoryQueryClientFromApi(
cosmWasmClient: CosmWasmClient,
apiUrl: string,
) {
const chainId = await cosmWasmClient.getChainId()
const factoryAddress = await getAccountFactoryAddressFromApi(apiUrl, chainId)

return new AccountFactoryQueryClient(cosmWasmClient, factoryAddress)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import { AccountFactoryQueryClient } from '../../../codegen/abstract'
import { getAccountFactoryAddress } from './get-account-factory-address'

export async function getAccountFactoryQueryClient(
cosmWasmClient: CosmWasmClient,
versionControlAddress: string,
version?: string,
) {
const factoryAddress = await getAccountFactoryAddress(
cosmWasmClient,
versionControlAddress,
version,
)

return new AccountFactoryQueryClient(cosmWasmClient, factoryAddress)
}
41 changes: 41 additions & 0 deletions packages/core/src/actions/public/ans/get-ans-host-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import {
VersionControlQueryClient,
VersionControlTypes,
} from '../../../codegen/abstract'
import { versionControlModuleToAddress } from '../../../utils/version-control/version-control-module-to-address'
const ANS_HOST_MODULE_NAME = 'ans-host'

export async function getAnsHostAddress(
cosmWasmClient: CosmWasmClient,
versionControlAddress: string,
version?: string,
) {
const registryClient = new VersionControlQueryClient(
cosmWasmClient,
versionControlAddress,
)

const [ansHostAddress] = await registryClient
.modules({
infos: [
{
name: ANS_HOST_MODULE_NAME,
namespace: 'abstract',
version: version ? { version } : 'latest',
} satisfies VersionControlTypes.ModuleInfo,
],
})
.then(({ modules }) =>
modules.map(({ module }) => versionControlModuleToAddress(module)),
)

if (!ansHostAddress) {
throw new Error(
`Could not get ansHostAddress for version ${version} from registry ${versionControlAddress}`,
)
}

return ansHostAddress
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import { AnsHostQueryClient } from '../../../codegen/abstract'
import { getAnsHostAddressFromApi } from '../../ans/get-ans-host-address-from-api'

export async function getAnsHostQueryClientFromApi(
cosmWasmClient: CosmWasmClient,
apiUrl: string,
) {
const chainId = await cosmWasmClient.getChainId()

const ansHostAddress = await getAnsHostAddressFromApi(apiUrl, chainId)

return new AnsHostQueryClient(cosmWasmClient, ansHostAddress)
}
18 changes: 18 additions & 0 deletions packages/core/src/actions/public/ans/get-ans-host-query-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'

import { AnsHostQueryClient } from '../../../codegen/abstract'
import { getAnsHostAddress } from './get-ans-host-address'

export async function getAnsHostQueryClient(
cosmWasmClient: CosmWasmClient,
versionControlAddress: string,
version?: string,
) {
const ansHostAddress = await getAnsHostAddress(
cosmWasmClient,
versionControlAddress,
version,
)

return new AnsHostQueryClient(cosmWasmClient, ansHostAddress)
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { ACCOUNT_ID_CHAIN_DELIMITER, AccountId } from '../../utils/account-id'
import { chainIdToName } from '../../utils/chain-registry'
import { findAbstractAttribute } from '../../utils/events'

export function parseCreateAccountExecuteResult(
result: ExecuteResult,
chainId: string,
) {
const seq = Number.parseInt(
findAbstractAttribute(result, 'account_sequence').value,
)
const trace = findAbstractAttribute(result, 'trace').value
const accountId = {
chainName: chainIdToName(chainId),
seq,
trace:
trace === 'local' || trace === undefined
? 'local'
: { remote: trace.split(ACCOUNT_ID_CHAIN_DELIMITER) },
} satisfies AccountId

const managerAddress = findAbstractAttribute(result, 'manager_address').value
const proxyAddress = findAbstractAttribute(result, 'proxy_address').value
return { accountId, managerAddress, proxyAddress }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@ import { rawQuery } from '@abstract-money/cosmwasm-utils'
import { type CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { match } from 'ts-pattern'
import { VersionControlTypes } from '../../../codegen/abstract'
import { type ModuleData } from '../../../utils/Module.types'
import { getModuleIdWithVersion } from '../module-id'
import { getAddressFromVcModule } from './get-address-from-vc-module'
import { getCodeIdFromVcModule } from './get-code-id-from-vc-module'
import { getTypeFromVcModule } from './get-type-from-vc-module'
import { type ModuleData } from '../../../utils/generics/Module.types'
import { formatModuleIdWithVersion } from '../../../utils/version-control/module-id/format-module-id-with-version'
import { versionControlModuleToAddress } from '../../../utils/version-control/version-control-module-to-address'
import { versionControlModuleToCodeId } from '../../../utils/version-control/version-control-module-to-code-id'
import { versionControlModuleToType } from '../../../utils/version-control/version-control-module-to-type'

export async function getModuleData<
export async function getVersionControlModuleData<
const TVcModule extends VersionControlTypes.Module = VersionControlTypes.Module,
>(client: CosmWasmClient, module: TVcModule): Promise<ModuleData | null> {
// Retrieve the first instantiation of the module
const moduleType = getTypeFromVcModule(module)
const moduleType = versionControlModuleToType(module)
const firstInstantiation = await match(moduleType)
.with('adapter', async () => {
return Promise.resolve(getAddressFromVcModule(module))
return Promise.resolve(versionControlModuleToAddress(module))
})
// TODO: not all standalones will have module_data
.with('standalone', 'app', async () => {
// retrieve the first instantiation
const instantiations = await client.getContracts(
getCodeIdFromVcModule(module),
versionControlModuleToCodeId(module),
)
const firstInstantiation = instantiations[0]
if (!firstInstantiation) {
console.log(
`Could not find first instantiation of ${getModuleIdWithVersion(
`Could not find first instantiation of ${formatModuleIdWithVersion(
module.info.namespace,
module.info.name,
module.info.version,
Expand Down
Loading

0 comments on commit eff9b5c

Please sign in to comment.