diff --git a/packages/internal/src/action-types.js b/packages/internal/src/action-types.js index 18d4ad2b8a9..87e6af2e1e2 100644 --- a/packages/internal/src/action-types.js +++ b/packages/internal/src/action-types.js @@ -1,5 +1,7 @@ // @jessie-check +import { keyMirror } from './keyMirror.js'; + /** * Types of messages used for communication between a cosmos-sdk blockchain node * and its paired swingset VM, especially for the ABCI lifecycle. See: @@ -14,13 +16,13 @@ * * @enum {(typeof SwingsetMessageType)[keyof typeof SwingsetMessageType]} */ -export const SwingsetMessageType = /** @type {const} */ ({ - AG_COSMOS_INIT: 'AG_COSMOS_INIT', // used to synchronize at process launch - BEGIN_BLOCK: 'BEGIN_BLOCK', - END_BLOCK: 'END_BLOCK', - COMMIT_BLOCK: 'COMMIT_BLOCK', - AFTER_COMMIT_BLOCK: 'AFTER_COMMIT_BLOCK', - SWING_STORE_EXPORT: 'SWING_STORE_EXPORT', // used to synchronize data export +export const SwingsetMessageType = keyMirror({ + AG_COSMOS_INIT: null, // used to synchronize at process launch + BEGIN_BLOCK: null, + END_BLOCK: null, + COMMIT_BLOCK: null, + AFTER_COMMIT_BLOCK: null, + SWING_STORE_EXPORT: null, // used to synchronize data export }); harden(SwingsetMessageType); @@ -47,17 +49,17 @@ export const { * * @enum {(typeof QueuedActionType)[keyof typeof QueuedActionType]} */ -export const QueuedActionType = /** @type {const} */ ({ - CORE_EVAL: 'CORE_EVAL', - DELIVER_INBOUND: 'DELIVER_INBOUND', - IBC_EVENT: 'IBC_EVENT', - INSTALL_BUNDLE: 'INSTALL_BUNDLE', - PLEASE_PROVISION: 'PLEASE_PROVISION', - VBANK_BALANCE_UPDATE: 'VBANK_BALANCE_UPDATE', - WALLET_ACTION: 'WALLET_ACTION', - WALLET_SPEND_ACTION: 'WALLET_SPEND_ACTION', - VTRANSFER_IBC_EVENT: 'VTRANSFER_IBC_EVENT', - KERNEL_UPGRADE_EVENTS: 'KERNEL_UPGRADE_EVENTS', +export const QueuedActionType = keyMirror({ + CORE_EVAL: null, + DELIVER_INBOUND: null, + IBC_EVENT: null, + INSTALL_BUNDLE: null, + PLEASE_PROVISION: null, + VBANK_BALANCE_UPDATE: null, + WALLET_ACTION: null, + WALLET_SPEND_ACTION: null, + VTRANSFER_IBC_EVENT: null, + KERNEL_UPGRADE_EVENTS: null, }); harden(QueuedActionType); diff --git a/packages/internal/src/config.js b/packages/internal/src/config.js index 2a05de87f21..f753619af81 100644 --- a/packages/internal/src/config.js +++ b/packages/internal/src/config.js @@ -1,6 +1,8 @@ // @ts-check // @jessie-check +import { keyMirror } from './keyMirror.js'; + /** * @file * @@ -38,8 +40,8 @@ export const CosmosInitKeyToBridgeId = { }; harden(CosmosInitKeyToBridgeId); -export const WalletName = /** @type {const} */ ({ - depositFacet: 'depositFacet', +export const WalletName = keyMirror({ + depositFacet: null, }); harden(WalletName); diff --git a/packages/internal/src/index.js b/packages/internal/src/index.js index 8c1269028be..4c4ea3e0ebf 100644 --- a/packages/internal/src/index.js +++ b/packages/internal/src/index.js @@ -15,6 +15,7 @@ export * from './config.js'; export * from './debug.js'; export * from './errors.js'; export * from './js-utils.js'; +export * from './keyMirror.js'; export { pureDataMarshaller } from './marshal.js'; export * from './method-tools.js'; export * from './metrics.js'; diff --git a/packages/internal/src/keyMirror.js b/packages/internal/src/keyMirror.js new file mode 100644 index 00000000000..0816184503e --- /dev/null +++ b/packages/internal/src/keyMirror.js @@ -0,0 +1,45 @@ +// @ts-check +// @jessie-check + +const { freeze, keys } = Object; + +/** + * @template {Record} Init + * @typedef {{ readonly [K in keyof Init]: K }} KeyMirrorResult + */ + +/** + * Mirror the keys of an object to string values that match the key names. + * + * The provided record must only map property names to either `null` or the + * property name itself. The returned object has identical keys whose values are + * the string form of the key. + * + * @template {Record} Init + * @param {Init & { readonly [K in keyof Init]: Init[K] extends null ? null : K }} + * record + * @returns {KeyMirrorResult} + */ +export const keyMirror = record => { + if (record === null || typeof record !== 'object') { + throw TypeError('keyMirror expects a record of string keys.'); + } + + /** @type {Record} */ + const mirrored = {}; + for (const key of keys(record)) { + const typedKey = /** @type {keyof Init & string} */ (key); + const value = record[typedKey]; + const stringValue = /** @type {string | null} */ (value); + if (stringValue !== null && stringValue !== typedKey) { + throw TypeError( + `Value for key "${typedKey}" must be null or the key string; got ${String(value)}.`, + ); + } + mirrored[typedKey] = typedKey; + } + + return /** @type {KeyMirrorResult} */ (freeze(mirrored)); +}; + +freeze(keyMirror); diff --git a/packages/orchestration/src/chain-info.js b/packages/orchestration/src/chain-info.js index 495b4bdabd5..34119b6d69a 100644 --- a/packages/orchestration/src/chain-info.js +++ b/packages/orchestration/src/chain-info.js @@ -7,6 +7,7 @@ import { E } from '@endo/far'; import { M, mustMatch } from '@endo/patterns'; +import { keyMirror } from '@agoric/internal'; import cctpChainInfo from './cctp-chain-info.js'; import { withChainCapabilities } from './chain-capabilities.js'; import { HubName, normalizeConnectionInfo } from './exos/chain-hub.js'; @@ -25,10 +26,10 @@ import { ChainInfoShape, CosmosAssetInfoShape } from './typeGuards.js'; * @enum {(typeof KnownNamespace)[keyof typeof KnownNamespace]} * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/c599f7601d0ce83e6dd9f350c6c21d158d56fd6d/CAIPs/caip-2.md} */ -export const KnownNamespace = /** @type {const} */ ({ - cosmos: 'cosmos', - eip155: 'eip155', - solana: 'solana', +export const KnownNamespace = keyMirror({ + cosmos: null, + eip155: null, + solana: null, }); harden(KnownNamespace); diff --git a/packages/portfolio-api/src/constants.js b/packages/portfolio-api/src/constants.js index f9413c93685..5111abbae5a 100644 --- a/packages/portfolio-api/src/constants.js +++ b/packages/portfolio-api/src/constants.js @@ -2,40 +2,44 @@ /// +import { keyMirror } from '@agoric/internal'; + /** * Yield protocols for Proof of Concept. * * @enum {(typeof YieldProtocol)[keyof typeof YieldProtocol]} */ -export const YieldProtocol = /** @type {const} */ ({ - Aave: 'Aave', - Compound: 'Compound', - USDN: 'USDN', - Beefy: 'Beefy', +export const YieldProtocol = keyMirror({ + Aave: null, + Compound: null, + USDN: null, + Beefy: null, }); harden(YieldProtocol); /** * @enum {(typeof AxelarChain)[keyof typeof AxelarChain]} */ -export const AxelarChain = /** @type {const} */ ({ - Arbitrum: 'Arbitrum', - Avalanche: 'Avalanche', - Base: 'Base', - Ethereum: 'Ethereum', - Optimism: 'Optimism', +export const AxelarChain = keyMirror({ + Arbitrum: null, + Avalanche: null, + Base: null, + Ethereum: null, + Optimism: null, }); harden(AxelarChain); /** * @enum {(typeof SupportedChain)[keyof typeof SupportedChain]} */ -export const SupportedChain = /** @type {const} */ ({ +export const SupportedChain = { ...AxelarChain, - agoric: 'agoric', - noble: 'noble', - // XXX: check privateArgs for chainInfo for all of these -}); + ...keyMirror({ + agoric: null, + noble: null, + // XXX: check privateArgs for chainInfo for all of these + }), +}; harden(SupportedChain); /** diff --git a/packages/portfolio-contract/src/resolver/constants.js b/packages/portfolio-contract/src/resolver/constants.js index 35165518c05..3707fe6f853 100644 --- a/packages/portfolio-contract/src/resolver/constants.js +++ b/packages/portfolio-contract/src/resolver/constants.js @@ -2,6 +2,8 @@ /// +import { keyMirror } from '@agoric/internal'; + /** * Tx statuses for published transactions. Exhaustive state machine flows: * - pending -> success (when cross-chain operation completes successfully) @@ -21,11 +23,11 @@ harden(TxStatus); * * @enum {(typeof TxType)[keyof typeof TxType]} */ -export const TxType = /** @type {const} */ ({ - CCTP_TO_EVM: 'CCTP_TO_EVM', - GMP: 'GMP', - CCTP_TO_AGORIC: 'CCTP_TO_AGORIC', +export const TxType = keyMirror({ + CCTP_TO_EVM: null, + GMP: null, + CCTP_TO_AGORIC: null, /** @deprecated - only supports 20 byte addresses */ - CCTP_TO_NOBLE: 'CCTP_TO_NOBLE', + CCTP_TO_NOBLE: null, }); harden(TxType); diff --git a/packages/vats/src/walletFlags.js b/packages/vats/src/walletFlags.js index d0f2778565e..8db70aae3b2 100644 --- a/packages/vats/src/walletFlags.js +++ b/packages/vats/src/walletFlags.js @@ -1,11 +1,13 @@ // XXX domain of @agoric/cosmic-proto +import { keyMirror } from '@agoric/internal'; + /** * non-exhaustive list of powerFlags REMOTE_WALLET is currently a default. * * See also MsgProvision in golang/cosmos/proto/agoric/swingset/msgs.proto */ -export const PowerFlags = /** @type {const} */ ({ - SMART_WALLET: 'SMART_WALLET', +export const PowerFlags = keyMirror({ + SMART_WALLET: null, /** The ag-solo wallet is remote. */ - REMOTE_WALLET: 'REMOTE_WALLET', + REMOTE_WALLET: null, });