diff --git a/.changeset/tricky-pianos-design.md b/.changeset/tricky-pianos-design.md new file mode 100644 index 00000000..9f0113ab --- /dev/null +++ b/.changeset/tricky-pianos-design.md @@ -0,0 +1,5 @@ +--- +"@abstract-money/core": minor +--- + +Add interchain account queries diff --git a/packages/core/abstract.config.ts b/packages/core/abstract.config.ts index e129ac6f..22d61371 100644 --- a/packages/core/abstract.config.ts +++ b/packages/core/abstract.config.ts @@ -26,6 +26,10 @@ const contractsConfig = [ name: 'account-factory', version: '0.19', }, + { + name: 'ibc-client', + version: '0.20.0', + }, ] export default defineConfig({ diff --git a/packages/core/src/actions/account/public/get-ibc-client-query-client-from-manager.ts b/packages/core/src/actions/account/public/get-ibc-client-query-client-from-manager.ts new file mode 100644 index 00000000..83c3e756 --- /dev/null +++ b/packages/core/src/actions/account/public/get-ibc-client-query-client-from-manager.ts @@ -0,0 +1,37 @@ +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { + IbcClientQueryClient, + VersionControlTypes, +} from '../../../codegen/abstract' +import { WithArgs } from '../../../types/with-args' +import { getManagerQueryClientFromApi } from './get-manager-query-client-from-api' +import { getModuleAddress } from './get-module-address' + +export type GetModuleAddressParameters = WithArgs<{ + accountId: VersionControlTypes.AccountId + cosmWasmClient: CosmWasmClient + apiUrl: string +}> + +const IBC_CLIENT_MODULE_ID = 'abstract:ibc-client' + +/** + * Retrieve the {@link IbcClientQueryClient} from the manager account. + * @throws if the IBC-client module is not installed + * @param accountId + * @param cosmWasmClient + * @param apiUrl + */ +export async function getIbcClientQueryClientFromManager({ + args: { accountId, cosmWasmClient, apiUrl }, +}: GetModuleAddressParameters) { + const ibcClientAddress = await getModuleAddress({ + args: { accountId, cosmWasmClient, apiUrl, id: IBC_CLIENT_MODULE_ID }, + }) + + if (!ibcClientAddress) { + throw new Error('IBC-client module not installed') + } + + return new IbcClientQueryClient(cosmWasmClient, ibcClientAddress) +} diff --git a/packages/core/src/actions/account/public/get-remote-account-ids.ts b/packages/core/src/actions/account/public/get-remote-account-ids.ts new file mode 100644 index 00000000..3a497862 --- /dev/null +++ b/packages/core/src/actions/account/public/get-remote-account-ids.ts @@ -0,0 +1,45 @@ +import { AccountId, chainIdToName } from '@abstract-money/core' +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { + IbcClientQueryClient, + VersionControlTypes, +} from '../../../codegen/abstract' +import { WithArgs } from '../../../types/with-args' +import { getIbcClientQueryClientFromManager } from './get-ibc-client-query-client-from-manager' +import { + GetRemoteProxiesParameters, + getRemoteAccountProxies, +} from './get-remote-account-proxies' + +export type GetRemoteAccountIdsParameters = GetRemoteProxiesParameters + +/** + * Get the Account's remote Account ids. + * @param accountId + * @param cosmWasmClient + * @param apiUrl + */ +export async function getRemoteAccountIds({ + args: { accountId, cosmWasmClient, apiUrl }, +}: GetRemoteAccountIdsParameters): Promise { + const remoteProxies = await getRemoteAccountProxies({ + args: { accountId, cosmWasmClient, apiUrl }, + }) + + const chainId = await cosmWasmClient.getChainId() + const sourceChainName = chainIdToName(chainId) + + return Object.keys(remoteProxies).map((remoteChainName) => { + // local accounts are now remote accounts, remote accounts are now one hop further + const remoteTrace = + accountId.trace === 'local' + ? [sourceChainName] + : accountId.trace.remote.concat(sourceChainName) + + return { + seq: accountId.seq, + trace: { remote: remoteTrace }, + chainName: remoteChainName, + } satisfies AccountId + }) +} diff --git a/packages/core/src/actions/account/public/get-remote-account-proxies.ts b/packages/core/src/actions/account/public/get-remote-account-proxies.ts new file mode 100644 index 00000000..a30924bb --- /dev/null +++ b/packages/core/src/actions/account/public/get-remote-account-proxies.ts @@ -0,0 +1,49 @@ +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { + IbcClientQueryClient, + VersionControlTypes, +} from '../../../codegen/abstract' +import { WithArgs } from '../../../types/with-args' +import { getIbcClientQueryClientFromManager } from './get-ibc-client-query-client-from-manager' + +export type GetRemoteProxiesParameters = WithArgs< + { + accountId: VersionControlTypes.AccountId + cosmWasmClient: CosmWasmClient + apiUrl: string + } & Omit< + Parameters< + typeof IbcClientQueryClient.prototype.listRemoteProxiesByAccountId + >[0], + 'accountId' + > +> + +type ChainName = string +type MaybeProxyAddress = string | null + +/** + * Get the remote proxies for the given account. + * @param accountId + * @param cosmWasmClient + * @param apiUrl + */ +export async function getRemoteAccountProxies({ + args: { accountId, cosmWasmClient, apiUrl }, +}: GetRemoteProxiesParameters): Promise> { + let ibcClient: IbcClientQueryClient + try { + ibcClient = await getIbcClientQueryClientFromManager({ + args: { accountId, cosmWasmClient, apiUrl }, + }) + } catch (e) { + // IBC client not installed + return {} + } + + const remoteProxies = await ibcClient.listRemoteProxiesByAccountId({ + accountId, + }) + + return Object.fromEntries(remoteProxies.proxies) +} diff --git a/packages/core/src/actions/account/public/get-sub-account-ids.ts b/packages/core/src/actions/account/public/get-sub-account-ids.ts index 82e6ca54..0debc0f7 100644 --- a/packages/core/src/actions/account/public/get-sub-account-ids.ts +++ b/packages/core/src/actions/account/public/get-sub-account-ids.ts @@ -1,3 +1,4 @@ +import { AccountId } from '@abstract-money/core' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { ManagerQueryClient, @@ -18,7 +19,7 @@ export type GetSubAccountIdsParameters = WithArgs< export async function getSubAccountIds({ args: { accountId, cosmWasmClient, apiUrl, ...params }, -}: GetSubAccountIdsParameters) { +}: GetSubAccountIdsParameters): Promise { const chainId = await cosmWasmClient.getChainId() const chainName = chainIdToName(chainId) const sub_accounts = await getSubAccountSequences({ diff --git a/packages/core/src/clients/decorators/account-public.ts b/packages/core/src/clients/decorators/account-public.ts index 5dafe5d0..d290424e 100644 --- a/packages/core/src/clients/decorators/account-public.ts +++ b/packages/core/src/clients/decorators/account-public.ts @@ -8,6 +8,8 @@ import { getModules } from '../../actions/account/public/get-modules' import { getNamespace } from '../../actions/account/public/get-namespace' import { getOwner } from '../../actions/account/public/get-owner' import { getProxyQueryClientFromApi } from '../../actions/account/public/get-proxy-query-client-from-api' +import { getRemoteAccountIds } from '../../actions/account/public/get-remote-account-ids' +import { getRemoteAccountProxies } from '../../actions/account/public/get-remote-account-proxies' import { getSubAccountIds } from '../../actions/account/public/get-sub-account-ids' import { getSubAccountSequences } from '../../actions/account/public/get-sub-account-sequences' import { getTotalValue } from '../../actions/account/public/get-total-value' @@ -61,6 +63,12 @@ export type AccountPublicActions = { getSubAccountSequences( args: CutSpecificArgsFromParameter, ): ReturnType + getRemoteAccountProxies( + args: CutSpecificArgsFromParameter, + ): ReturnType + getRemoteAccountIds( + args: CutSpecificArgsFromParameter, + ): ReturnType getTotalValue( args: CutSpecificArgsFromParameter, ): ReturnType @@ -125,6 +133,16 @@ export function accountPublicActions( args: { ...args, accountId, cosmWasmClient, apiUrl }, ...rest, }), + getRemoteAccountProxies: ({ args, ...rest }) => + getRemoteAccountProxies({ + args: { ...args, accountId, cosmWasmClient, apiUrl }, + ...rest, + }), + getRemoteAccountIds: ({ args, ...rest }) => + getRemoteAccountIds({ + args: { ...args, accountId, cosmWasmClient, apiUrl }, + ...rest, + }), getTotalValue: ({ args, ...rest }) => getTotalValue({ args: { ...args, accountId, cosmWasmClient, apiUrl },