diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index 120d51fae9a9..00c03a10dbde 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -180,6 +180,7 @@ const ChainHubI = M.interface('ChainHub', { registerAsset: M.call(M.string(), DenomDetailShape).returns(), getAsset: M.call(M.string()).returns(M.or(DenomDetailShape, M.undefined())), getDenom: M.call(BrandShape).returns(M.or(M.string(), M.undefined())), + getChainInfoFromAddress: M.call(M.string()).returns(CosmosChainInfoShape), }); /** @@ -216,6 +217,11 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { keyShape: BrandShape, valueShape: M.string(), }); + /** @type {MapStore} */ + const bech32PrefixToChainName = zone.mapStore('bech32PrefixToChainName', { + keyShape: M.string(), + valueShape: M.string(), + }); const lookupChainInfo = vowTools.retryable( zone, @@ -230,6 +236,9 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { // TODO consider makeAtomicProvider for vows if (!chainInfos.has(chainName)) { chainInfos.init(chainName, chainInfo); + if (chainInfo.bech32Prefix) { + bech32PrefixToChainName.init(chainInfo.bech32Prefix, chainName); + } } return chainInfo; } catch (e) { @@ -316,6 +325,9 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { */ registerChain(name, chainInfo) { chainInfos.init(name, chainInfo); + if (chainInfo.bech32Prefix) { + bech32PrefixToChainName.init(chainInfo.bech32Prefix, name); + } }, /** * @template {string} K @@ -425,6 +437,20 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { } return undefined; }, + /** + * @param {string} address bech32 address + * @returns {CosmosChainInfo} chainId + */ + getChainInfoFromAddress(address) { + const [prefix, rest] = address.split('1'); + (prefix && rest) || + Fail`Address contains invalid bech32 separator: ${q(address)}`; + if (bech32PrefixToChainName.has(prefix)) { + const chainName = bech32PrefixToChainName.get(prefix); + return chainInfos.get(chainName); + } + throw makeError(`Chain info not found for bech32Prefix: ${prefix}`); + }, }); return chainHub; diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md index 14c7fa11ebb8..cb7e77f4a24d 100644 --- a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md @@ -34,6 +34,9 @@ Generated by [AVA](https://avajs.dev). chainHub: { ChainHub_kindHandle: 'Alleged: kind', ChainHub_singleton: 'Alleged: ChainHub', + bech32PrefixToChainName: { + agoric: 'agoric', + }, brandDenom: {}, chainInfos: { agoric: { diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap index 92840da3e5e1..019aa9a9d1af 100644 Binary files a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap and b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap differ diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md index 8c787b284440..bbf2ad9192b8 100644 --- a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md @@ -30,6 +30,10 @@ Generated by [AVA](https://avajs.dev). chainHub: { ChainHub_kindHandle: 'Alleged: kind', ChainHub_singleton: 'Alleged: ChainHub', + bech32PrefixToChainName: { + agoric: 'agoric', + cosmos: 'cosmoshub', + }, brandDenom: { 'Alleged: BLD brand': 'ubld', }, diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap index 694625ae9256..01a323d924f1 100644 Binary files a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap and b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap differ diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md index ab5fcc7056e3..fb15c9d00d8e 100644 --- a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md @@ -30,6 +30,11 @@ Generated by [AVA](https://avajs.dev). chainHub: { ChainHub_kindHandle: 'Alleged: kind', ChainHub_singleton: 'Alleged: ChainHub', + bech32PrefixToChainName: { + agoric: 'agoric', + osmo: 'osmosis', + stride: 'stride', + }, brandDenom: {}, chainInfos: { agoric: { diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap index 7da1546a8627..34fcdb2becaf 100644 Binary files a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap and b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap differ diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index 81b22718c46a..9ab8e551eaa3 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -159,3 +159,33 @@ test.serial('toward asset info in agoricNames (#9572)', async t => { }); } }); + +test.serial('getChainInfoFromAddress', async t => { + const { chainHub, nameAdmin, vt } = setup(); + // use fetched chain info + await registerKnownChains(nameAdmin); + + // call getChainInfo so ChainHub performs agoricNames lookup that populates its local cache + await vt.asPromise(chainHub.getChainInfo('osmosis')); + + const MOCK_ICA_ADDRESS = + 'osmo1ht7u569vpuryp6utadsydcne9ckeh2v8dkd38v5hptjl3u2ewppqc6kzgd'; + t.like(chainHub.getChainInfoFromAddress(MOCK_ICA_ADDRESS), { + chainId: 'osmosis-1', + bech32Prefix: 'osmo', + }); + + t.throws( + () => + chainHub.getChainInfoFromAddress( + MOCK_ICA_ADDRESS.replace('osmo1', 'foo1'), + ), + { + message: 'Chain info not found for bech32Prefix: foo', + }, + ); + + t.throws(() => chainHub.getChainInfoFromAddress('notbech32'), { + message: 'Address contains invalid bech32 separator: "notbech32"', + }); +});