diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts
index 4bdc1fc7f4b..5deaca784a1 100644
--- a/packages/boot/test/bootstrapTests/orchestration.test.ts
+++ b/packages/boot/test/bootstrapTests/orchestration.test.ts
@@ -2,12 +2,12 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { Fail } from '@agoric/assert';
import { AmountMath } from '@agoric/ertp';
+import { documentStorageSchema } from '@agoric/internal/src/storage-test-utils.js';
import type { CosmosValidatorAddress } from '@agoric/orchestration';
import type { start as startStakeIca } from '@agoric/orchestration/src/examples/stakeIca.contract.js';
import type { Instance } from '@agoric/zoe/src/zoeService/utils.js';
import { M, matches } from '@endo/patterns';
import type { TestFn } from 'ava';
-import { documentStorageSchema } from '@agoric/internal/src/storage-test-utils.js';
import {
makeWalletFactoryContext,
type WalletFactoryTestContext,
@@ -56,13 +56,14 @@ test.serial('config', async t => {
'chainConnection',
'cosmoshub-4_juno-1',
);
+ t.like(connection, {
+ state: 3,
+ transferChannel: { portId: 'transfer', state: 3 },
+ });
- t.like(
+ t.deepEqual(
readLatest(`published.agoricNames.chainConnection.cosmoshub-4_juno-1`),
- {
- state: 3,
- transferChannel: { portId: 'transfer', state: 3 },
- },
+ connection,
);
await documentStorageSchema(t, storage, {
@@ -230,3 +231,35 @@ test.serial('stakeAtom - smart wallet', async t => {
'delegate fails with invalid validator',
);
});
+
+// XXX rely on .serial to be in sequence, and keep this one last
+test.serial('revise chain info', async t => {
+ const {
+ buildProposal,
+ evalProposal,
+ runUtils: { EV },
+ } = t.context;
+
+ const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames');
+
+ await t.throwsAsync(EV(agoricNames).lookup('chain', 'hot'), {
+ message: '"nameKey" not found: "hot"',
+ });
+
+ // Revise chain info in agoricNames with the fixture in this script
+ await evalProposal(
+ buildProposal('@agoric/builders/scripts/testing/append-chain-info.js'),
+ );
+
+ const hotchain = await EV(agoricNames).lookup('chain', 'hot');
+ t.deepEqual(hotchain, { allegedName: 'Hot New Chain', chainId: 'hot-1' });
+
+ const connection = await EV(agoricNames).lookup(
+ 'chainConnection',
+ 'cosmoshub-4_hot-1',
+ );
+ t.like(connection, {
+ id: 'connection-99',
+ client_id: '07-tendermint-3',
+ });
+});
diff --git a/packages/builders/scripts/inter-protocol/add-STARS.js b/packages/builders/scripts/inter-protocol/add-STARS.js
index 48809f22daa..50d2e6dbe9f 100644
--- a/packages/builders/scripts/inter-protocol/add-STARS.js
+++ b/packages/builders/scripts/inter-protocol/add-STARS.js
@@ -3,7 +3,7 @@ import { defaultProposalBuilder as vaultProposalBuilder } from './add-collateral
import { defaultProposalBuilder as oraclesProposalBuilder } from './price-feed-core.js';
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
-export const starsVaultProposalBuilder = async powers => {
+const starsVaultProposalBuilder = async powers => {
return vaultProposalBuilder(powers, {
interchainAssetOptions: {
// Values for the Stargaze token on Osmosis
@@ -18,7 +18,7 @@ export const starsVaultProposalBuilder = async powers => {
};
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
-export const starsOraclesProposalBuilder = async powers => {
+const starsOraclesProposalBuilder = async powers => {
return oraclesProposalBuilder(powers, {
AGORIC_INSTANCE_NAME: `STARS-USD price feed`,
IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'STARS'],
diff --git a/packages/builders/scripts/smart-wallet/build-game1-start.js b/packages/builders/scripts/smart-wallet/build-game1-start.js
index 20ecee5b4d2..aee88216584 100644
--- a/packages/builders/scripts/smart-wallet/build-game1-start.js
+++ b/packages/builders/scripts/smart-wallet/build-game1-start.js
@@ -9,7 +9,7 @@ import { makeHelpers } from '@agoric/deploy-script-support';
import { getManifestForGame1 } from '@agoric/smart-wallet/test/start-game1-proposal.js';
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
-export const game1ProposalBuilder = async ({ publishRef, install }) => {
+const game1ProposalBuilder = async ({ publishRef, install }) => {
return harden({
sourceSpec: '@agoric/smart-wallet/test/start-game1-proposal.js',
getManifestCall: [
diff --git a/packages/builders/scripts/testing/add-LEMONS.js b/packages/builders/scripts/testing/add-LEMONS.js
index f0fce8380f1..a17827f3684 100644
--- a/packages/builders/scripts/testing/add-LEMONS.js
+++ b/packages/builders/scripts/testing/add-LEMONS.js
@@ -4,7 +4,7 @@ import { defaultProposalBuilder as vaultProposalBuilder } from '../inter-protoco
/** @file This is for use in tests in a3p-integration */
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
-export const starsVaultProposalBuilder = async powers => {
+const starsVaultProposalBuilder = async powers => {
return vaultProposalBuilder(powers, {
interchainAssetOptions: {
denom: 'ibc/000C0FFEECAFE000',
diff --git a/packages/builders/scripts/testing/add-OLIVES.js b/packages/builders/scripts/testing/add-OLIVES.js
index 9b82d9242b7..011d70e3e3b 100644
--- a/packages/builders/scripts/testing/add-OLIVES.js
+++ b/packages/builders/scripts/testing/add-OLIVES.js
@@ -4,7 +4,7 @@ import { defaultProposalBuilder as vaultProposalBuilder } from '../inter-protoco
/** @file This is for use in tests in a3p-integration */
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
-export const stars2VaultProposalBuilder = async powers => {
+const stars2VaultProposalBuilder = async powers => {
return vaultProposalBuilder(powers, {
interchainAssetOptions: {
denom: 'ibc/111C0FFEECAFE111',
diff --git a/packages/builders/scripts/testing/append-chain-info.js b/packages/builders/scripts/testing/append-chain-info.js
new file mode 100644
index 00000000000..f141fe49074
--- /dev/null
+++ b/packages/builders/scripts/testing/append-chain-info.js
@@ -0,0 +1,50 @@
+///
+import { makeHelpers } from '@agoric/deploy-script-support';
+
+/** @type {Record} */
+const chainInfo = {
+ hot: {
+ allegedName: 'Hot New Chain',
+ chainId: 'hot-1',
+ connections: {
+ 'cosmoshub-4': {
+ id: 'connection-99',
+ client_id: '07-tendermint-3',
+ counterparty: {
+ client_id: '07-tendermint-2',
+ connection_id: 'connection-1',
+ prefix: {
+ key_prefix: '',
+ },
+ },
+ state: 3 /* IBCConnectionState.STATE_OPEN */,
+ transferChannel: {
+ portId: 'transfer',
+ channelId: 'channel-1',
+ counterPartyChannelId: 'channel-1',
+ counterPartyPortId: 'transfer',
+ ordering: 1 /* Order.ORDER_UNORDERED */,
+ state: 3 /* IBCConnectionState.STATE_OPEN */,
+ version: 'ics20-1',
+ },
+ },
+ },
+ },
+};
+
+/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
+export const defaultProposalBuilder = async () =>
+ harden({
+ sourceSpec: '@agoric/orchestration/src/proposals/revise-chain-info.js',
+ getManifestCall: [
+ 'getManifestForReviseChains',
+ {
+ chainInfo,
+ },
+ ],
+ });
+
+export default async (homeP, endowments) => {
+ const { writeCoreEval } = await makeHelpers(homeP, endowments);
+ await writeCoreEval('revise-chain-info', defaultProposalBuilder);
+};
diff --git a/packages/builders/scripts/testing/tweak-chain-info.js b/packages/builders/scripts/testing/tweak-chain-info.js
new file mode 100644
index 00000000000..922300ea7c0
--- /dev/null
+++ b/packages/builders/scripts/testing/tweak-chain-info.js
@@ -0,0 +1,53 @@
+///
+import { makeHelpers } from '@agoric/deploy-script-support';
+
+/** @type {Record} */
+const chainInfo = {
+ agoric: {
+ chainId: 'agoric-4',
+ },
+ hot: {
+ allegedName: 'Hot New Chain',
+ chainId: 'hot-1',
+ connections: {
+ 'cosmoshub-4': {
+ id: 'connection-99',
+ client_id: '07-tendermint-3',
+ counterparty: {
+ client_id: '07-tendermint-2',
+ connection_id: 'connection-1',
+ prefix: {
+ key_prefix: '',
+ },
+ },
+ state: 3 /* IBCConnectionState.STATE_OPEN */,
+ transferChannel: {
+ portId: 'transfer',
+ channelId: 'channel-1',
+ counterPartyChannelId: 'channel-1',
+ counterPartyPortId: 'transfer',
+ ordering: 1 /* Order.ORDER_UNORDERED */,
+ state: 3 /* IBCConnectionState.STATE_OPEN */,
+ version: 'ics20-1',
+ },
+ },
+ },
+ },
+};
+
+/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
+export const defaultProposalBuilder = async () =>
+ harden({
+ sourceSpec: '@agoric/orchestration/src/proposals/revise-chain-info.js',
+ getManifestCall: [
+ 'getManifestForReviseChains',
+ {
+ chainInfo,
+ },
+ ],
+ });
+
+export default async (homeP, endowments) => {
+ const { writeCoreEval } = await makeHelpers(homeP, endowments);
+ await writeCoreEval('revise-chain-info', defaultProposalBuilder);
+};
diff --git a/packages/orchestration/scripts/fetch-chain-info.ts b/packages/orchestration/scripts/fetch-chain-info.ts
index d294f6d6c29..9634dfc884e 100755
--- a/packages/orchestration/scripts/fetch-chain-info.ts
+++ b/packages/orchestration/scripts/fetch-chain-info.ts
@@ -1,17 +1,9 @@
#!/usr/bin/env tsx
/** @file Fetch canonical chain info to generate the minimum needed for agoricNames */
-import {
- State as IBCChannelState,
- Order,
-} from '@agoric/cosmic-proto/ibc/core/channel/v1/channel.js';
-import { State as IBCConnectionState } from '@agoric/cosmic-proto/ibc/core/connection/v1/connection.js';
-import type { IBCChannelID, IBCConnectionID } from '@agoric/vats';
import { ChainRegistryClient } from '@chain-registry/client';
-import type { IBCInfo } from '@chain-registry/types';
-import assert from 'node:assert';
import fsp from 'node:fs/promises';
import prettier from 'prettier';
-import type { CosmosChainInfo, IBCConnectionInfo } from '../src/cosmos-api.js';
+import { convertChainInfo } from '../src/utils/registry.js';
// XXX script assumes it's run from the package path
// XXX .json would be more apt; UNTIL https://github.com/endojs/endo/issues/2110
@@ -43,96 +35,7 @@ const client = new ChainRegistryClient({
// chain info, assets and ibc data will be downloaded dynamically by invoking fetchUrls method
await client.fetchUrls();
-const chainInfo = {} as Record;
-
-function toConnectionEntry(ibcInfo: IBCInfo, name: string) {
- // IbcInfo encodes the undirected edge as a tuple of (chain_1, chain_2) in alphabetical order
- const fromChain1 = ibcInfo.chain_1.chain_name === name;
- const [from, to] = fromChain1
- ? [ibcInfo.chain_1, ibcInfo.chain_2]
- : [ibcInfo.chain_2, ibcInfo.chain_1];
- assert.equal(from.chain_name, name);
- const transferChannels = ibcInfo.channels.filter(
- c =>
- c.chain_1.port_id === 'transfer' &&
- // @ts-expect-error tags does not specify keys
- c.tags?.preferred,
- );
- if (transferChannels.length === 0) {
- console.warn(
- 'no transfer channel for [',
- from.chain_name,
- to.chain_name,
- ']',
- '(skipping)',
- );
- return [];
- }
- if (transferChannels.length > 1) {
- console.warn(
- 'multiple preferred transfer channels [',
- from.chain_name,
- to.chain_name,
- ']:',
- transferChannels,
- '(choosing first)',
- );
- }
- const [channel] = transferChannels;
- const [channelFrom, channelTo] = fromChain1
- ? [channel.chain_2, channel.chain_1]
- : [channel.chain_1, channel.chain_2];
- const record = {
- id: from.connection_id as IBCConnectionID,
- client_id: from.client_id,
- counterparty: {
- client_id: to.client_id,
- connection_id: to.connection_id as IBCConnectionID,
- prefix: {
- key_prefix: 'FIXME',
- },
- },
- state: IBCConnectionState.STATE_OPEN, // XXX presumably
- transferChannel: {
- channelId: channelFrom.channel_id as IBCChannelID,
- portId: channelFrom.port_id,
- counterPartyChannelId: channelTo.channel_id as IBCChannelID,
- counterPartyPortId: channelTo.port_id,
- // FIXME mapping, our guard expects a numerical enum
- ordering: Order.ORDER_NONE_UNSPECIFIED,
- state: IBCChannelState.STATE_OPEN, // XXX presumably
- version: channel.version,
- },
- } as IBCConnectionInfo;
- const destChainId = chainInfo[to.chain_name].chainId;
- return [destChainId, record] as const;
-}
-
-for (const name of chainNames) {
- console.log('processing info', name);
-
- const chain = client.getChain(name);
- chainInfo[name] = {
- chainId: chain.chain_id,
- stakingTokens: chain.staking?.staking_tokens,
- // UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
- icqEnabled: name === 'osmosis',
- };
-}
-
-// iterate this after chainInfo is filled out
-for (const name of chainNames) {
- console.log('processing connections', name);
-
- const ibcData = client.getChainIbcData(name);
- const connections = Object.fromEntries(
- ibcData
- .map(datum => toConnectionEntry(datum, name))
- // sort alphabetically for consistency
- .sort(([a], [b]) => a?.localeCompare(b)),
- );
- chainInfo[name] = { ...chainInfo[name], connections };
-}
+const chainInfo = await convertChainInfo(client);
const record = JSON.stringify(chainInfo, null, 2);
const src = `/** @file Generated by fetch-chain-info.ts */\nexport default /** @type {const} } */ (${record});`;
diff --git a/packages/orchestration/src/proposals/revise-chain-info.js b/packages/orchestration/src/proposals/revise-chain-info.js
new file mode 100644
index 00000000000..b864bfedfc1
--- /dev/null
+++ b/packages/orchestration/src/proposals/revise-chain-info.js
@@ -0,0 +1,44 @@
+import { makeTracer } from '@agoric/internal';
+import { registerChain } from '../chain-info.js';
+
+const trace = makeTracer('ReviseChainInfo', true);
+
+/** @import {CosmosChainInfo} from '../types.js'; */
+
+/**
+ * This will add news values AND overwrite any existing values. Voters on a
+ * proposal of core-eval must be careful not to overwrite any values operating
+ * in production.
+ *
+ * @param {BootstrapPowers} powers
+ * @param {{ options: { chainInfo: Record } }} opt
+ */
+export const reviseChainInfo = async (
+ { consume: { agoricNamesAdmin } },
+ { options: { chainInfo } },
+) => {
+ trace('init-chainInfo');
+
+ assert(chainInfo, 'chainInfo is required');
+
+ trace(chainInfo);
+
+ // Now register the names
+ for await (const [name, info] of Object.entries(chainInfo)) {
+ await registerChain(agoricNamesAdmin, name, info, trace);
+ }
+};
+harden(reviseChainInfo);
+
+export const getManifestForReviseChains = (_powers, { chainInfo }) => ({
+ manifest: {
+ [reviseChainInfo.name]: {
+ consume: {
+ agoricNamesAdmin: true,
+ },
+ },
+ },
+ options: {
+ chainInfo,
+ },
+});
diff --git a/packages/orchestration/src/utils/registry.js b/packages/orchestration/src/utils/registry.js
new file mode 100644
index 00000000000..c6a4183624e
--- /dev/null
+++ b/packages/orchestration/src/utils/registry.js
@@ -0,0 +1,131 @@
+import {
+ State as IBCChannelState,
+ Order,
+} from '@agoric/cosmic-proto/ibc/core/channel/v1/channel.js';
+import { State as IBCConnectionState } from '@agoric/cosmic-proto/ibc/core/connection/v1/connection.js';
+import assert from 'node:assert';
+
+/**
+ * @import {IBCChannelID, IBCConnectionID} from '@agoric/vats';
+ * @import {Chain, IBCInfo} from '@chain-registry/types';
+ * @import {ChainRegistryClient} from '@chain-registry/client';
+ * @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api.js';
+ */
+
+/**
+ * @param {IBCInfo} ibcInfo
+ * @param {string} name
+ * @param {Record} chainInfo
+ * @returns {[string, IBCConnectionInfo] | []}
+ */
+function toConnectionEntry(ibcInfo, name, chainInfo) {
+ // IbcInfo encodes the undirected edge as a tuple of (chain_1, chain_2) in alphabetical order
+ const fromChain1 = ibcInfo.chain_1.chain_name === name;
+ const [from, to] = fromChain1
+ ? [ibcInfo.chain_1, ibcInfo.chain_2]
+ : [ibcInfo.chain_2, ibcInfo.chain_1];
+ assert.equal(from.chain_name, name);
+ const transferChannels = ibcInfo.channels.filter(
+ c =>
+ c.chain_1.port_id === 'transfer' &&
+ // @ts-expect-error tags does not specify keys
+ c.tags?.preferred,
+ );
+ if (transferChannels.length === 0) {
+ console.warn(
+ 'no transfer channel for [',
+ from.chain_name,
+ to.chain_name,
+ ']',
+ '(skipping)',
+ );
+ return [];
+ }
+ if (transferChannels.length > 1) {
+ console.warn(
+ 'multiple preferred transfer channels [',
+ from.chain_name,
+ to.chain_name,
+ ']:',
+ transferChannels,
+ '(choosing first)',
+ );
+ }
+ const [channel] = transferChannels;
+ const [channelFrom, channelTo] = fromChain1
+ ? [channel.chain_2, channel.chain_1]
+ : [channel.chain_1, channel.chain_2];
+ const record = {
+ id: /** @type {IBCConnectionID} */ (from.connection_id),
+ client_id: from.client_id,
+ counterparty: {
+ client_id: to.client_id,
+ connection_id: /** @type {IBCConnectionID} */ (to.connection_id),
+ prefix: {
+ key_prefix: 'FIXME',
+ },
+ },
+ state: IBCConnectionState.STATE_OPEN, // XXX presumably
+ transferChannel: {
+ channelId: /** @type {IBCChannelID} */ (channelFrom.channel_id),
+ portId: channelFrom.port_id,
+ counterPartyChannelId: /** @type {IBCChannelID} */ (channelTo.channel_id),
+ counterPartyPortId: channelTo.port_id,
+ // FIXME mapping, our guard expects a numerical enum
+ ordering: Order.ORDER_NONE_UNSPECIFIED,
+ state: IBCChannelState.STATE_OPEN, // XXX presumably
+ version: channel.version,
+ },
+ };
+ const destChainId = chainInfo[to.chain_name].chainId;
+ return [destChainId, record];
+}
+
+/**
+ * Converts the given chain info to our local config format
+ *
+ * @param {Pick} registry
+ */
+export const convertChainInfo = async registry => {
+ /** @type {Record} */
+ const chainInfo = {};
+
+ for (const chain of registry.chains) {
+ console.log('processing info', chain.chain_name);
+ chainInfo[chain.chain_name] = {
+ chainId: chain.chain_id,
+ stakingTokens: chain.staking?.staking_tokens,
+ // UNTIL https://github.com/Agoric/agoric-sdk/issues/9326
+ icqEnabled: chain.chain_name === 'osmosis',
+ };
+ }
+
+ // XXX probably easier to keep ibc separate
+ const ibcLookup = {};
+ for (const ibc of registry.ibcData) {
+ ibcLookup[ibc.chain_1.chain_name] ||= [];
+ ibcLookup[ibc.chain_2.chain_name] ||= [];
+
+ ibcLookup[ibc.chain_1.chain_name].push(ibc);
+ ibcLookup[ibc.chain_2.chain_name].push(ibc);
+ }
+
+ const chainNames = registry.chains.map(c => c.chain_name).sort();
+
+ // iterate this after chainInfo is filled out
+ for (const name of chainNames) {
+ console.log('processing connections', name);
+
+ const ibcData = ibcLookup[name];
+ const connections = Object.fromEntries(
+ ibcData
+ .map(datum => toConnectionEntry(datum, name, chainInfo))
+ // sort alphabetically for consistency
+ .sort(([a], [b]) => (a && b ? a.localeCompare(b) : 0)),
+ );
+ chainInfo[name] = { ...chainInfo[name], connections };
+ }
+
+ // return object with insertion in alphabetical order of chain name
+ return Object.fromEntries(chainNames.map(name => [name, chainInfo[name]]));
+};