From d415a20ed752ff5db9b74399624b8c6270ed60b2 Mon Sep 17 00:00:00 2001
From: adairrr <32375605+adairrr@users.noreply.github.com>
Date: Tue, 20 Feb 2024 20:38:13 -0800
Subject: [PATCH 1/3] Add module address instantiate 2 helper
---
examples/wagemos-graz-nextjs/package.json | 2 +-
.../src/app/authz-osmosis/page.tsx | 43 ++++++++---
.../wagemos-graz-nextjs/src/app/layout.tsx | 29 ++++---
...et-module-instantiate2-address-from-api.ts | 54 +++++++++++++
...act-module-address-from-version-control.ts | 1 +
...app-module-code-id-from-version-control.ts | 52 +++++++++++++
...le-factory-address-from-version-control.ts | 25 +++++++
.../src/clients/decorators/account-public.ts | 11 +++
.../get-instantiate2-account-address.test.ts | 27 -------
.../get-instantiate2-account-address.ts | 31 --------
.../get-instantiate2-address.test.ts | 48 ++++++++++++
.../get-instantiate2-address.ts | 34 +++++++++
.../core/src/utils/account-factory/index.ts | 2 +-
packages/core/src/utils/encoding.ts | 7 ++
.../module-id/module-id-to-name.ts | 12 ++-
.../module-id/module-id-to-namespace.ts | 11 ++-
.../react/src/hooks/account/public/index.ts | 1 +
...se-module-instantiate2-address-from-api.ts | 75 +++++++++++++++++++
18 files changed, 378 insertions(+), 87 deletions(-)
create mode 100644 packages/core/src/actions/account/public/get-module-instantiate2-address-from-api.ts
create mode 100644 packages/core/src/actions/public/get-app-module-code-id-from-version-control.ts
create mode 100644 packages/core/src/actions/public/get-module-factory-address-from-version-control.ts
delete mode 100644 packages/core/src/utils/account-factory/get-instantiate2-account-address.test.ts
delete mode 100644 packages/core/src/utils/account-factory/get-instantiate2-account-address.ts
create mode 100644 packages/core/src/utils/account-factory/get-instantiate2-address.test.ts
create mode 100644 packages/core/src/utils/account-factory/get-instantiate2-address.ts
create mode 100644 packages/react/src/hooks/account/public/use-module-instantiate2-address-from-api.ts
diff --git a/examples/wagemos-graz-nextjs/package.json b/examples/wagemos-graz-nextjs/package.json
index 6b18800d..ea465368 100644
--- a/examples/wagemos-graz-nextjs/package.json
+++ b/examples/wagemos-graz-nextjs/package.json
@@ -3,7 +3,7 @@
"version": "0.2.14",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "NODE_OPTIONS='--inspect' next dev",
"build": "next build",
"start": "next start",
"typecheck": "tsc --noEmit",
diff --git a/examples/wagemos-graz-nextjs/src/app/authz-osmosis/page.tsx b/examples/wagemos-graz-nextjs/src/app/authz-osmosis/page.tsx
index 524cc103..17724d24 100644
--- a/examples/wagemos-graz-nextjs/src/app/authz-osmosis/page.tsx
+++ b/examples/wagemos-graz-nextjs/src/app/authz-osmosis/page.tsx
@@ -13,30 +13,33 @@ import {
useCreateAccountMonarchy,
useSignAndBroadcast,
} from '@abstract-money/react'
+import { useModuleInstantiate2AddressFromApi } from '@abstract-money/react'
import { useAccount } from 'graz'
-import { useCallback, useEffect, useMemo } from 'react'
+import React, { useCallback, useEffect, useMemo } from 'react'
import { Button } from '../../components/ui/button'
import { WalletButton } from '../_components/wallet-button'
import { prepareInstantiateMsg } from './_utils/prepare-instantiate-msg'
-const GRANTER = 'osmo1jzyqffltm2s5wxmnjyze5hzrpcady0gmpz738n'
-const GRANTEE = 'osmo1ak64euh4tyzetkny6t0y0v5tw47n3y6y0ys3md'
+const GRANTER = 'osmo18k2uq7srsr8lwrae6zr0qahpn29rsp7tswpc4g'
+const CHAIN_NAME = 'osmosis'
+const TEST_SAVINGS_ACCOUNT_ID = 'osmosis-48'
+const SAVINGS_APP_MODULE_ID = 'abstract:carrot-app'
export default function AuthzPage() {
useEffect(() => {
prepareInstantiateMsg()
}, [])
const { mutate: signAndBroadcast } = useSignAndBroadcast({
- args: { chainName: 'osmosis' },
+ args: { chainName: CHAIN_NAME },
})
const { data: account } = useAccount({ chainId: 'osmosis-1' })
const { data: accountFactory, isLoading } =
- useAccountFactoryQueryClientFromApi('osmosis')
+ useAccountFactoryQueryClientFromApi(CHAIN_NAME)
const { mutate: createAccount } = useCreateAccountMonarchy({
- args: { chainName: 'osmosis' },
+ args: { chainName: CHAIN_NAME },
})
const onCreateAccount = useCallback(async () => {
@@ -50,14 +53,32 @@ export default function AuthzPage() {
fee: 'auto',
args: {
name: 'funny-squid',
- accountId: stringToAccountId(`local-${sequence}`, 'osmosis'),
+ accountId: stringToAccountId(`local-${sequence}`, CHAIN_NAME),
owner: account.bech32Address,
},
})
}, [accountFactory])
+ const { data: savingsAppAddress } = useModuleInstantiate2AddressFromApi(
+ {
+ accountId: stringToAccountId(TEST_SAVINGS_ACCOUNT_ID, CHAIN_NAME),
+ moduleId: SAVINGS_APP_MODULE_ID,
+ },
+ {
+ enabled: true,
+ },
+ )
+
+ console.log('calculated savings app address', savingsAppAddress)
+
const onGrantAuthzClick = useMemo(() => {
- if (!account) return undefined
+ if (!account) {
+ console.error('no account')
+ return undefined
+ } else if (!savingsAppAddress) {
+ console.error('no module grantee')
+ return undefined
+ }
return () => {
signAndBroadcast({
@@ -74,18 +95,18 @@ export default function AuthzPage() {
].map((typeUrl) =>
encodeAuthzGrantGenericAuthorizationMsg(
account.bech32Address,
- GRANTEE,
+ savingsAppAddress,
typeUrl,
),
),
encodeAuthzGrantGenericAuthorizationMsg(
account.bech32Address,
- GRANTEE,
+ savingsAppAddress,
BankTransactionTypeUrl.Send,
),
encodeAuthzGrantSendAuthorizationMsg(
account.bech32Address,
- GRANTEE,
+ savingsAppAddress,
{ spendLimit: [{ denom: 'uosmo', amount: '100' }] },
),
],
diff --git a/examples/wagemos-graz-nextjs/src/app/layout.tsx b/examples/wagemos-graz-nextjs/src/app/layout.tsx
index a21d4f2b..96f409d8 100644
--- a/examples/wagemos-graz-nextjs/src/app/layout.tsx
+++ b/examples/wagemos-graz-nextjs/src/app/layout.tsx
@@ -7,6 +7,8 @@ import {
AbstractProvider,
createConfig,
} from '@abstract-money/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { Inter, Poppins } from 'next/font/google'
import { Toaster } from '../components/ui/toaster'
import { cn } from '../utils'
@@ -25,6 +27,8 @@ const abstractConfig = createConfig({
apiUrl: 'https://api.abstract.money/graphql',
})
+const queryClient = new QueryClient()
+
export default function RootLayout({
children,
}: {
@@ -35,17 +39,20 @@ export default function RootLayout({
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {/**/}
+
diff --git a/packages/core/src/actions/account/public/get-module-instantiate2-address-from-api.ts b/packages/core/src/actions/account/public/get-module-instantiate2-address-from-api.ts
new file mode 100644
index 00000000..100fd4a3
--- /dev/null
+++ b/packages/core/src/actions/account/public/get-module-instantiate2-address-from-api.ts
@@ -0,0 +1,54 @@
+import {
+ ModuleId,
+ chainIdToName,
+ getInstantiate2Address,
+ toSha256,
+} from '@abstract-money/core'
+import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
+import { VersionControlTypes } from '../../../codegen/abstract'
+import { WithArgs } from '../../../types/with-args'
+import { getVersionControlAddressFromApi } from '../../get-version-control-address-from-api'
+import { getAbstractModuleAddressFromVersionControl } from '../../public/get-abstract-module-address-from-version-control'
+import { getAppModuleCodeIdFromVersionControl } from '../../public/get-app-module-code-id-from-version-control'
+import { getModuleFactoryAddressFromVersionControl } from '../../public/get-module-factory-address-from-version-control'
+
+export type GetModuleInstantiate2AddressFromApi = WithArgs<{
+ accountId: VersionControlTypes.AccountId
+ moduleId: ModuleId
+ version?: string
+ cosmWasmClient: CosmWasmClient
+ apiUrl: string
+}>
+
+export async function getModuleInstantiate2AddressFromApi({
+ args: { accountId, cosmWasmClient, apiUrl, moduleId, version },
+}: GetModuleInstantiate2AddressFromApi): Promise {
+ const chainId = await cosmWasmClient.getChainId()
+ const chainName = chainIdToName(chainId)
+
+ const versionControlAddress = await getVersionControlAddressFromApi({
+ args: {
+ apiUrl,
+ chainName,
+ },
+ })
+
+ const moduleFactoryAddress = await getModuleFactoryAddressFromVersionControl({
+ args: {
+ cosmWasmClient,
+ versionControlAddress,
+ },
+ })
+
+ const moduleCodeId = await getAppModuleCodeIdFromVersionControl({
+ args: { moduleId, version, cosmWasmClient, versionControlAddress },
+ })
+
+ const moduleCodeDetails = await cosmWasmClient.getCodeDetails(moduleCodeId)
+
+ return await getInstantiate2Address(
+ moduleFactoryAddress,
+ moduleCodeDetails.checksum,
+ { ...accountId, chainName },
+ )
+}
diff --git a/packages/core/src/actions/public/get-abstract-module-address-from-version-control.ts b/packages/core/src/actions/public/get-abstract-module-address-from-version-control.ts
index 16c5e6ad..23712c14 100644
--- a/packages/core/src/actions/public/get-abstract-module-address-from-version-control.ts
+++ b/packages/core/src/actions/public/get-abstract-module-address-from-version-control.ts
@@ -7,6 +7,7 @@ import { getVersionControlQueryClient } from './get-version-control-query-client
export enum CommonModuleNames {
ACCOUNT_FACTORY = 'account-factory',
+ MODULE_FACTORY = 'module-factory',
ANS_HOST = 'ans-host',
}
diff --git a/packages/core/src/actions/public/get-app-module-code-id-from-version-control.ts b/packages/core/src/actions/public/get-app-module-code-id-from-version-control.ts
new file mode 100644
index 00000000..13c1c950
--- /dev/null
+++ b/packages/core/src/actions/public/get-app-module-code-id-from-version-control.ts
@@ -0,0 +1,52 @@
+import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
+
+import {
+ ModuleId,
+ formatModuleIdWithVersion,
+ moduleIdToName,
+ moduleIdToNamespace,
+} from '@abstract-money/core'
+import { VersionControlTypes } from '../../codegen/abstract'
+import { WithArgs } from '../../types/with-args'
+import { versionControlModuleToCodeId } from '../../utils/version-control/version-control-module-to-code-id'
+import { getVersionControlQueryClient } from './get-version-control-query-client'
+
+export type GetAppModuleCodeIdFromVersionControl = WithArgs<{
+ moduleId: `${ModuleId}`
+ cosmWasmClient: CosmWasmClient
+ versionControlAddress: string
+ version?: string
+}>
+
+export async function getAppModuleCodeIdFromVersionControl({
+ args: { moduleId, cosmWasmClient, versionControlAddress, version },
+}: GetAppModuleCodeIdFromVersionControl) {
+ const versionControlQueryClient = getVersionControlQueryClient({
+ args: {
+ cosmWasmClient,
+ versionControlAddress,
+ },
+ })
+
+ const [moduleAddress] = await versionControlQueryClient
+ .modules({
+ infos: [
+ {
+ name: moduleIdToName(moduleId),
+ namespace: moduleIdToNamespace(moduleId),
+ version: version ? { version } : 'latest',
+ } satisfies VersionControlTypes.ModuleInfo,
+ ],
+ })
+ .then(({ modules }) =>
+ modules.map(({ module }) => versionControlModuleToCodeId(module)),
+ )
+
+ if (!moduleAddress) {
+ throw new Error(
+ `Could not fetch code id for app module ${moduleId} version ${version} from registry ${versionControlAddress}`,
+ )
+ }
+
+ return moduleAddress
+}
diff --git a/packages/core/src/actions/public/get-module-factory-address-from-version-control.ts b/packages/core/src/actions/public/get-module-factory-address-from-version-control.ts
new file mode 100644
index 00000000..29e31aea
--- /dev/null
+++ b/packages/core/src/actions/public/get-module-factory-address-from-version-control.ts
@@ -0,0 +1,25 @@
+import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
+
+import { WithArgs } from '../../types/with-args'
+import {
+ CommonModuleNames,
+ getAbstractModuleAddressFromVersionControl,
+} from './get-abstract-module-address-from-version-control'
+
+export type GetModuleFactoryAddressFromVersionControlParameters = WithArgs<{
+ cosmWasmClient: CosmWasmClient
+ versionControlAddress: string
+ version?: string
+}>
+export async function getModuleFactoryAddressFromVersionControl({
+ args: { cosmWasmClient, versionControlAddress, version },
+}: GetModuleFactoryAddressFromVersionControlParameters) {
+ return getAbstractModuleAddressFromVersionControl({
+ args: {
+ moduleName: CommonModuleNames.MODULE_FACTORY,
+ cosmWasmClient,
+ versionControlAddress,
+ version,
+ },
+ })
+}
diff --git a/packages/core/src/clients/decorators/account-public.ts b/packages/core/src/clients/decorators/account-public.ts
index d290424e..1ad1adcc 100644
--- a/packages/core/src/clients/decorators/account-public.ts
+++ b/packages/core/src/clients/decorators/account-public.ts
@@ -4,6 +4,7 @@ import { getAccountBaseAddressesFromApi } from '../../actions/account/public/get
import { getBaseToken } from '../../actions/account/public/get-base-token'
import { getManagerQueryClientFromApi } from '../../actions/account/public/get-manager-query-client-from-api'
import { getModuleAddress } from '../../actions/account/public/get-module-address'
+import { getModuleInstantiate2AddressFromApi } from '../../actions/account/public/get-module-instantiate2-address-from-api'
import { getModules } from '../../actions/account/public/get-modules'
import { getNamespace } from '../../actions/account/public/get-namespace'
import { getOwner } from '../../actions/account/public/get-owner'
@@ -48,6 +49,11 @@ export type AccountPublicActions = {
getModules(
args: CutSpecificArgsFromParameter,
): ReturnType
+ getModuleInstantiate2AddressFromApi(
+ args: CutSpecificArgsFromParameter<
+ typeof getModuleInstantiate2AddressFromApi
+ >,
+ ): ReturnType
getNamespace(
args: CutSpecificArgsFromParameter,
): ReturnType
@@ -108,6 +114,11 @@ export function accountPublicActions(
args: { ...args, accountId, cosmWasmClient, apiUrl },
...rest,
}),
+ getModuleInstantiate2AddressFromApi: ({ args, ...rest }) =>
+ getModuleInstantiate2AddressFromApi({
+ args: { ...args, accountId, cosmWasmClient, apiUrl },
+ ...rest,
+ }),
getNamespace: ({ args, ...rest }) =>
getNamespace({
args: { ...args, accountId, cosmWasmClient, apiUrl },
diff --git a/packages/core/src/utils/account-factory/get-instantiate2-account-address.test.ts b/packages/core/src/utils/account-factory/get-instantiate2-account-address.test.ts
deleted file mode 100644
index 29a14cbf..00000000
--- a/packages/core/src/utils/account-factory/get-instantiate2-account-address.test.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { stringToAccountId } from '../account-id'
-import { getInstantiate2AccountAddress } from './get-instantiate2-account-address' // Update the path to your logic file
-
-describe('getInstantiate2AccountAddress', () => {
- it('returns the correct address', async () => {
- const address = 'juno1jzyqffltm2s5wxmnjyze5hzrpcady0gmltw6ka'
- const accountFactoryAddress =
- 'juno1qtl43hzk7xd9xly9wc4qxnmtjmj0hdtymsnldsl736yh7vnfm3ys7t4a4y'
- const checksum = new Uint8Array([
- 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1,
- 2, 3, 4, 5, 6, 7, 8,
- ])
- const accountId = stringToAccountId('juno-5', 'juno')
-
- const result = await getInstantiate2AccountAddress(
- address,
- accountFactoryAddress,
- checksum,
- accountId,
- )
-
- expect(result).toBe(
- 'juno1m8lz9cru30zugeas5tx9245kn6w4rv92y7t96gvcsl0ktve378tqkv60qs',
- )
- })
-})
diff --git a/packages/core/src/utils/account-factory/get-instantiate2-account-address.ts b/packages/core/src/utils/account-factory/get-instantiate2-account-address.ts
deleted file mode 100644
index 45e8362b..00000000
--- a/packages/core/src/utils/account-factory/get-instantiate2-account-address.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { instantiate2Address } from '@cosmjs/cosmwasm-stargate'
-import { bech32 } from 'bech32'
-import { AccountId, accountIdToString } from '../account-id'
-
-async function getSalt(accountId: AccountId) {
- const encoder = new TextEncoder()
- const hash = await crypto.subtle.digest(
- 'SHA-256',
- encoder.encode(accountIdToString(accountId)),
- )
- return new Uint8Array([
- ...new Uint8Array(hash),
- ...encoder.encode('abstract'),
- ])
-}
-
-export async function getInstantiate2AccountAddress(
- address: string,
- accountFactoryAddress: string,
- checksum: Uint8Array,
- accountId: AccountId,
-) {
- const prefix = bech32.decode(address).prefix
-
- return instantiate2Address(
- checksum,
- accountFactoryAddress,
- await getSalt(accountId),
- prefix,
- )
-}
diff --git a/packages/core/src/utils/account-factory/get-instantiate2-address.test.ts b/packages/core/src/utils/account-factory/get-instantiate2-address.test.ts
new file mode 100644
index 00000000..ae1b8061
--- /dev/null
+++ b/packages/core/src/utils/account-factory/get-instantiate2-address.test.ts
@@ -0,0 +1,48 @@
+import { describe, expect, it } from 'vitest'
+import { stringToAccountId } from '../account-id'
+import { getInstantiate2Address } from './get-instantiate2-address' // Update the path to your logic file
+
+describe('getInstantiate2AccountAddress', () => {
+ it('returns the correct address', async () => {
+ const moduleFactoryAddress =
+ 'juno1qtl43hzk7xd9xly9wc4qxnmtjmj0hdtymsnldsl736yh7vnfm3ys7t4a4y'
+ // TODO: Replace with the actual checksum
+ const checksum = new Uint8Array([
+ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1,
+ 2, 3, 4, 5, 6, 7, 8,
+ ]).toString()
+ const accountId = stringToAccountId('juno-5', 'juno')
+
+ const result = await getInstantiate2Address(
+ moduleFactoryAddress,
+ checksum,
+ accountId,
+ )
+
+ expect(result).toBe(
+ 'juno1m8lz9cru30zugeas5tx9245kn6w4rv92y7t96gvcsl0ktve378tqkv60qs',
+ )
+ })
+})
+
+describe('getInstantiate2AccountAddressCarrot', () => {
+ it('returns the correct address', async () => {
+ const moduleFactoryAddress =
+ 'osmo1n28c9mtw9ehcddpaffewq4sczv4q0y3pfl76ll9v9alxx37rqdyqk89zsv'
+
+ const savingsChecksum =
+ '663B34ECF2287496D6CBD9CF5DFDD5BF23D4240325E688CA9FA809D8ECDCCF12'
+
+ const accountId = stringToAccountId('osmosis-48', 'osmosis')
+
+ const result = await getInstantiate2Address(
+ moduleFactoryAddress,
+ savingsChecksum,
+ accountId,
+ )
+
+ expect(result).toEqual(
+ 'osmo193zezfjwe0ndxyd0rcrw39rlqt2ulu4gvenner2vp8n5402vnhjq4t7pv0',
+ )
+ })
+})
diff --git a/packages/core/src/utils/account-factory/get-instantiate2-address.ts b/packages/core/src/utils/account-factory/get-instantiate2-address.ts
new file mode 100644
index 00000000..a9e8f1b6
--- /dev/null
+++ b/packages/core/src/utils/account-factory/get-instantiate2-address.ts
@@ -0,0 +1,34 @@
+import { instantiate2Address } from '@cosmjs/cosmwasm-stargate'
+import { bech32 } from 'bech32'
+import { AccountId, accountIdToString } from '../account-id'
+import { toSha256 } from '../encoding'
+
+async function getAccountIdSalt(accountId: AccountId) {
+ const sha256 = await toSha256(accountIdToString(accountId))
+ const encoder = new TextEncoder()
+
+ return new Uint8Array([...sha256, ...encoder.encode('abstract')])
+}
+
+/**
+ * Returns the instantiate2 address for the given account factory address, codeIdChecksum and accountId
+ * @param moduleFactoryAddress - the module factory is the creator
+ * @param codeIdChecksum - checksum of the code id of the contract expected to be instantiated
+ * @param accountId
+ */
+export async function getInstantiate2Address(
+ moduleFactoryAddress: string,
+ codeIdChecksum: string,
+ accountId: AccountId,
+) {
+ const prefix = bech32.decode(moduleFactoryAddress).prefix
+
+ const checksumSha256 = await toSha256(codeIdChecksum)
+
+ return instantiate2Address(
+ checksumSha256,
+ moduleFactoryAddress,
+ await getAccountIdSalt(accountId),
+ prefix,
+ )
+}
diff --git a/packages/core/src/utils/account-factory/index.ts b/packages/core/src/utils/account-factory/index.ts
index c4ad51c1..8a291646 100644
--- a/packages/core/src/utils/account-factory/index.ts
+++ b/packages/core/src/utils/account-factory/index.ts
@@ -1,2 +1,2 @@
export * from './parse-create-account-execute-result'
-export * from './get-instantiate2-account-address'
+export * from './get-instantiate2-address'
diff --git a/packages/core/src/utils/encoding.ts b/packages/core/src/utils/encoding.ts
index 19c63c35..ad5ca693 100644
--- a/packages/core/src/utils/encoding.ts
+++ b/packages/core/src/utils/encoding.ts
@@ -10,3 +10,10 @@ export const binaryToJson = (binary: string): string =>
fromUtf8(fromBase64(binary))
export const toUint8Array = (text: string) =>
Uint8Array.from(Array.from(text).map((letter) => letter.charCodeAt(0)))
+
+export const toSha256 = async (text: string): Promise => {
+ const encoder = new TextEncoder()
+ const data = encoder.encode(text)
+ const hash = await crypto.subtle.digest('SHA-256', data)
+ return new Uint8Array(hash)
+}
diff --git a/packages/core/src/utils/version-control/module-id/module-id-to-name.ts b/packages/core/src/utils/version-control/module-id/module-id-to-name.ts
index 0756d8f3..5d07824f 100644
--- a/packages/core/src/utils/version-control/module-id/module-id-to-name.ts
+++ b/packages/core/src/utils/version-control/module-id/module-id-to-name.ts
@@ -1,8 +1,16 @@
import * as s from 'string-ts'
import { MODULE_DELIMITER, type ModuleId } from './types'
+/**
+ * Retrieve the namespace from a module id.
+ * abstract:module => module
+ */
export function moduleIdToName(
id: TModuleId,
-): s.Split[0] {
- return s.split(id, MODULE_DELIMITER)[0]
+): s.Split[1] {
+ const split = s.split(id, MODULE_DELIMITER)
+ if (split.length < 2 || !split[1]) {
+ throw new Error(`Cannot find name for module: ${id}`)
+ }
+ return split[1]
}
diff --git a/packages/core/src/utils/version-control/module-id/module-id-to-namespace.ts b/packages/core/src/utils/version-control/module-id/module-id-to-namespace.ts
index 13e97ba9..410468eb 100644
--- a/packages/core/src/utils/version-control/module-id/module-id-to-namespace.ts
+++ b/packages/core/src/utils/version-control/module-id/module-id-to-namespace.ts
@@ -1,11 +1,16 @@
import * as s from 'string-ts'
import { MODULE_DELIMITER, type ModuleId } from './types'
+/**
+ * Retrieve the namespace from a module id.
+ * abstract:module => abstract
+ */
export function moduleIdToNamespace<
const TModuleId extends ModuleId = ModuleId,
->(id: TModuleId): s.Split[1] {
- const namespace = s.split(id, MODULE_DELIMITER)[0]
- if (!namespace) {
+>(id: TModuleId): s.Split[0] {
+ const split = s.split(id, MODULE_DELIMITER)
+ const namespace = split[0]
+ if (split.length < 2 || !namespace) {
throw new Error(`Cannot find namespace for module: ${id}`)
}
return namespace
diff --git a/packages/react/src/hooks/account/public/index.ts b/packages/react/src/hooks/account/public/index.ts
index 12ec6bb4..9adcdce2 100644
--- a/packages/react/src/hooks/account/public/index.ts
+++ b/packages/react/src/hooks/account/public/index.ts
@@ -1 +1,2 @@
export * from './use-account-base-addresses-from-api'
+export * from './use-module-instantiate2-address-from-api'
diff --git a/packages/react/src/hooks/account/public/use-module-instantiate2-address-from-api.ts b/packages/react/src/hooks/account/public/use-module-instantiate2-address-from-api.ts
new file mode 100644
index 00000000..e251be1c
--- /dev/null
+++ b/packages/react/src/hooks/account/public/use-module-instantiate2-address-from-api.ts
@@ -0,0 +1,75 @@
+import { AccountId, AccountPublicClient } from '@abstract-money/core'
+import { MODULE_DELIMITER } from '@abstract-money/core/.tsup/declaration'
+import {
+ UseQueryOptions,
+ UseQueryResult,
+ useQuery,
+} from '@tanstack/react-query'
+import React from 'react'
+import { useAccountId, useConfig } from '../../../contexts'
+
+type QueryFnData = Awaited<
+ ReturnType
+>
+
+type ModuleId = `${string}${typeof MODULE_DELIMITER}${string}`
+type QueryError = unknown
+type QueryData = QueryFnData
+type QueryKey = readonly [
+ 'moduleInstantiate2AddressFromApi',
+ AccountPublicClient | undefined,
+ ModuleId,
+ string | undefined,
+]
+
+type QueryOptions = Omit<
+ UseQueryOptions,
+ 'queryFn'
+>
+type QueryResult = UseQueryResult
+
+export function useModuleInstantiate2AddressFromApi(
+ args: {
+ accountId: AccountId | undefined
+ moduleId: ModuleId
+ version?: string
+ },
+ options?: QueryOptions,
+) {
+ const { accountId: accountIdParameter, moduleId, version } = args
+
+ const { accountId } = useAccountId({ accountId: accountIdParameter })
+ const config = useConfig()
+ const accountPublicClient = config.useAccountPublicClient({
+ accountId,
+ chainName: accountId?.chainName,
+ })
+ const queryKey = React.useMemo(
+ () =>
+ [
+ 'moduleInstantiate2AddressFromApi',
+ accountPublicClient,
+ moduleId,
+ version,
+ ] as const,
+ [accountPublicClient],
+ )
+
+ const enabled = React.useMemo(
+ () => Boolean(accountPublicClient && options?.enabled),
+ [options?.enabled, accountPublicClient],
+ )
+
+ const queryFn = React.useCallback(() => {
+ if (!accountPublicClient) throw new Error('No client')
+
+ return accountPublicClient.getModuleInstantiate2AddressFromApi({
+ args: {
+ moduleId,
+ version,
+ },
+ })
+ }, [accountPublicClient])
+
+ return useQuery(queryKey, queryFn, { ...options, enabled })
+}
From 2bca17257f8b957f6e2b8e156e7e9cad1b4674ff Mon Sep 17 00:00:00 2001
From: hotwater
Date: Wed, 21 Feb 2024 16:11:29 +0200
Subject: [PATCH 2/3] chore: changesets
---
.changeset/smooth-buses-travel.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changeset/smooth-buses-travel.md
diff --git a/.changeset/smooth-buses-travel.md b/.changeset/smooth-buses-travel.md
new file mode 100644
index 00000000..ed1d8e02
--- /dev/null
+++ b/.changeset/smooth-buses-travel.md
@@ -0,0 +1,7 @@
+---
+"wagemos-graz-nextjs": patch
+"@abstract-money/core": patch
+"@abstract-money/react": patch
+---
+
+Add helpers to predict module address
From aa237922876445dec53d4969c22d9d198ec62b8e Mon Sep 17 00:00:00 2001
From: hotwater
Date: Wed, 21 Feb 2024 17:19:46 +0200
Subject: [PATCH 3/3] nit: drop query client
---
examples/wagemos-graz-nextjs/package.json | 1 -
.../wagemos-graz-nextjs/src/app/layout.tsx | 29 ++++++--------
pnpm-lock.yaml | 39 ++-----------------
3 files changed, 15 insertions(+), 54 deletions(-)
diff --git a/examples/wagemos-graz-nextjs/package.json b/examples/wagemos-graz-nextjs/package.json
index ea465368..24f1df34 100644
--- a/examples/wagemos-graz-nextjs/package.json
+++ b/examples/wagemos-graz-nextjs/package.json
@@ -48,7 +48,6 @@
"devDependencies": {
"@abstract-money/cli": "workspace:*",
"@keplr-wallet/types": "^0.12.44",
- "@tanstack/react-query-devtools": "^5.17.1",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
diff --git a/examples/wagemos-graz-nextjs/src/app/layout.tsx b/examples/wagemos-graz-nextjs/src/app/layout.tsx
index 96f409d8..a21d4f2b 100644
--- a/examples/wagemos-graz-nextjs/src/app/layout.tsx
+++ b/examples/wagemos-graz-nextjs/src/app/layout.tsx
@@ -7,8 +7,6 @@ import {
AbstractProvider,
createConfig,
} from '@abstract-money/react'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { Inter, Poppins } from 'next/font/google'
import { Toaster } from '../components/ui/toaster'
import { cn } from '../utils'
@@ -27,8 +25,6 @@ const abstractConfig = createConfig({
apiUrl: 'https://api.abstract.money/graphql',
})
-const queryClient = new QueryClient()
-
export default function RootLayout({
children,
}: {
@@ -39,20 +35,17 @@ export default function RootLayout({
-
-
-
-
-
-
-
-
- {/**/}
-
+
+
+
+
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 02340466..fdcac1e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
importers:
.:
@@ -331,9 +335,6 @@ importers:
'@keplr-wallet/types':
specifier: ^0.12.44
version: 0.12.44
- '@tanstack/react-query-devtools':
- specifier: ^5.17.1
- version: 5.17.1(@tanstack/react-query@5.17.1)(react@18.2.0)
'@types/node':
specifier: ^20
version: 20.10.0
@@ -7455,25 +7456,6 @@ packages:
/@tanstack/query-core@4.36.1:
resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==}
- /@tanstack/query-core@5.17.1:
- resolution: {integrity: sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==}
- dev: true
-
- /@tanstack/query-devtools@5.17.1:
- resolution: {integrity: sha512-gNdt6PYzYlyjtSAoO8Jt9GIFq5VSLLDV2qq0TCi1t/PGnpAIlIHqNZGYkQTPsy0FyGUTX3pCq4bd7v5z/wzf3A==}
- dev: true
-
- /@tanstack/react-query-devtools@5.17.1(@tanstack/react-query@5.17.1)(react@18.2.0):
- resolution: {integrity: sha512-QWHqdEN2TJpj76r0yzdOJEopmPvdAHOJHAKXaygubRASqCqfcWGkOHGD9pqqHOfTu5eQdV1Csx97EuSjnHMKcA==}
- peerDependencies:
- '@tanstack/react-query': ^5.17.1
- react: ^18.0.0
- dependencies:
- '@tanstack/query-devtools': 5.17.1
- '@tanstack/react-query': 5.17.1(react@18.2.0)
- react: 18.2.0
- dev: true
-
/@tanstack/react-query@4.36.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==}
peerDependencies:
@@ -7491,15 +7473,6 @@ packages:
react-dom: 18.2.0(react@18.2.0)
use-sync-external-store: 1.2.0(react@18.2.0)
- /@tanstack/react-query@5.17.1(react@18.2.0):
- resolution: {integrity: sha512-4JYgX0kU+pvwVQi5eRiHGvBK7WnahEl6lmaxd32ZVSKmByAxLgaewoxBR03cdDNse8lUD2zGOe0sx3M/EGRlmA==}
- peerDependencies:
- react: ^18.0.0
- dependencies:
- '@tanstack/query-core': 5.17.1
- react: 18.2.0
- dev: true
-
/@terra-money/feather.js@1.2.1:
resolution: {integrity: sha512-OyXkWriNwb0lCF45eMmtjdOPEmfGKJxgGSnxpM7VxD0Vqr1qqtlcYyQG9wOHXNQrExvZv+uo922B2ZA4S77HsQ==}
engines: {node: '>=16'}
@@ -17308,7 +17281,3 @@ packages:
immer: 9.0.21
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false