diff --git a/packages/builders/scripts/fast-usdc/init-fast-usdc.js b/packages/builders/scripts/fast-usdc/init-fast-usdc.js index fade01cea83..624fe675d0b 100644 --- a/packages/builders/scripts/fast-usdc/init-fast-usdc.js +++ b/packages/builders/scripts/fast-usdc/init-fast-usdc.js @@ -1,10 +1,8 @@ // @ts-check import { makeHelpers } from '@agoric/deploy-script-support'; import { AmountMath } from '@agoric/ertp'; -import { - FastUSDCConfigShape, - getManifestForFastUSDC, -} from '@agoric/fast-usdc/src/fast-usdc.start.js'; +import { getManifestForFastUSDC } from '@agoric/fast-usdc/src/fast-usdc.start.js'; +import { FastUSDCConfigShape } from '@agoric/fast-usdc/src/fast-usdc.contract.meta.js'; import { toExternalConfig } from '@agoric/fast-usdc/src/utils/config-marshal.js'; import { configurations } from '@agoric/fast-usdc/src/utils/deploy-config.js'; import { diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index ff9f08c5000..81c568b9dae 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -2,10 +2,6 @@ import { AssetKind } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { observeIteration, subscribeEach } from '@agoric/notifier'; import { - CosmosChainInfoShape, - DenomDetailShape, - DenomShape, - OrchestrationPowersShape, registerChainsAndAssets, withOrchestration, } from '@agoric/orchestration'; @@ -14,14 +10,13 @@ import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js' import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { Fail } from '@endo/errors'; import { E } from '@endo/far'; -import { M } from '@endo/patterns'; import { prepareAdvancer } from './exos/advancer.js'; import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js'; import { prepareSettler } from './exos/settler.js'; import { prepareStatusManager } from './exos/status-manager.js'; import { prepareTransactionFeedKit } from './exos/transaction-feed.js'; +import { meta } from './fast-usdc.contract.meta.js'; import * as flows from './fast-usdc.flows.js'; -import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js'; import { defineInertInvitation } from './utils/zoe.js'; const trace = makeTracer('FastUsdc'); @@ -48,20 +43,7 @@ const ADDRESSES_BAGGAGE_KEY = 'addresses'; * }} FastUsdcTerms */ -/** @type {ContractMeta} */ -export const meta = { - // @ts-expect-error TypedPattern not recognized as record - customTermsShape: FastUSDCTermsShape, - privateArgsShape: { - // @ts-expect-error TypedPattern not recognized as record - ...OrchestrationPowersShape, - assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), - chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), - feeConfig: FeeConfigShape, - marshaller: M.remotable(), - poolMetricsNode: M.remotable(), - }, -}; +export { meta }; harden(meta); /** diff --git a/packages/fast-usdc/src/fast-usdc.contract.meta.js b/packages/fast-usdc/src/fast-usdc.contract.meta.js new file mode 100644 index 00000000000..7c3ca891586 --- /dev/null +++ b/packages/fast-usdc/src/fast-usdc.contract.meta.js @@ -0,0 +1,74 @@ +/** ContractMeta, Permit for Fast USDC */ +import { + CosmosChainInfoShape, + DenomDetailShape, + DenomShape, + OrchestrationPowersShape, +} from '@agoric/orchestration'; +import { M } from '@endo/patterns'; +import { + FastUSDCTermsShape, + FeeConfigShape, + FeedPolicyShape, +} from './type-guards.js'; +import { adminPermit, orchPermit } from './orch.start.js'; + +/** + * @import {StartParams} from '@agoric/zoe/src/zoeService/utils' + * @import {BootstrapManifest, BootstrapManifestPermit} from '@agoric/vats/src/core/lib-boot.js'; + * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js' + * @import {TypedPattern, Remote} from '@agoric/internal' + * @import {Marshaller} from '@agoric/internal/src/lib-chainStorage.js' + * @import {OrchestrationPowers} from '@agoric/orchestration'; + * @import {FastUsdcSF} from './fast-usdc.contract.js'; + * @import {FeedPolicy, FastUSDCConfig} from './types.js' + */ + +/** + * @import {LegibleCapData} from './utils/config-marshal.js' + * @import {CorePowersG} from './orch.start.js'; + */ + +/** @type {TypedPattern} */ +export const FastUSDCConfigShape = M.splitRecord({ + terms: FastUSDCTermsShape, + oracles: M.recordOf(M.string(), M.string()), + feeConfig: FeeConfigShape, + feedPolicy: FeedPolicyShape, + chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), + assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), +}); + +/** @satisfies {ContractMeta} */ +export const meta = /** @type {const} */ ({ + name: 'fastUsdc', + abbr: 'FUSD', // for tracer(s) + // @ts-expect-error TypedPattern not recognized as record + customTermsShape: FastUSDCTermsShape, + privateArgsShape: { + // @ts-expect-error TypedPattern not recognized as record + ...OrchestrationPowersShape, + assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), + chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), + feeConfig: FeeConfigShape, + marshaller: M.remotable(), + poolMetricsNode: M.remotable(), + }, + deployConfigShape: FastUSDCConfigShape, + /** @type {Record['creatorFacet']>} */ + adminRoles: { + oracles: 'makeOperatorInvitation', + }, +}); +harden(meta); + +/** @satisfies {BootstrapManifestPermit} */ +export const permit = /** @type {const} */ ({ + produce: { [`${meta.name}Kit`]: true }, + consume: { ...orchPermit, ...adminPermit }, + instance: { produce: { [meta.name]: true } }, + installation: { consume: { [meta.name]: true } }, + issuer: { produce: { FastLP: 'PoolShares' }, consume: { USDC: true } }, + brand: { produce: { FastLP: 'PoolShares' } }, +}); +harden(permit); diff --git a/packages/fast-usdc/src/fast-usdc.start.js b/packages/fast-usdc/src/fast-usdc.start.js index 2815569d2ed..c8bae7482c6 100644 --- a/packages/fast-usdc/src/fast-usdc.start.js +++ b/packages/fast-usdc/src/fast-usdc.start.js @@ -1,294 +1,82 @@ -import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal'; -import { - CosmosChainInfoShape, - DenomDetailShape, - DenomShape, -} from '@agoric/orchestration'; -import { Fail } from '@endo/errors'; +/** ContractMeta, Permit for Fast USDC */ import { E } from '@endo/far'; -import { makeMarshal } from '@endo/marshal'; -import { M } from '@endo/patterns'; -import { - FastUSDCTermsShape, - FeeConfigShape, - FeedPolicyShape, -} from './type-guards.js'; -import { fromExternalConfig } from './utils/config-marshal.js'; +import { makeTracer } from '@agoric/internal/src/debug.js'; +import { meta, permit } from './fast-usdc.contract.meta.js'; +import { makeGetManifest, startOrchContract } from './orch.start.js'; /** - * @import {DepositFacet} from '@agoric/ertp/src/types.js' - * @import {TypedPattern} from '@agoric/internal' - * @import {Instance, StartParams} from '@agoric/zoe/src/zoeService/utils' - * @import {Board} from '@agoric/vats' - * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js' - * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js' + * @import {Marshaller} from '@agoric/internal/src/lib-chainStorage.js' + * @import {OrchestrationPowers} from '@agoric/orchestration'; + * + * XXX these 2 could/should be down in the platform somewhere * @import {LegibleCapData} from './utils/config-marshal.js' - * @import {FastUsdcSF} from './fast-usdc.contract.js' - * @import {FeedPolicy, FastUSDCConfig} from './types.js' - */ - -const trace = makeTracer('FUSD-Start', true); - -const contractName = 'fastUsdc'; - -/** @type {TypedPattern} */ -export const FastUSDCConfigShape = M.splitRecord({ - terms: FastUSDCTermsShape, - oracles: M.recordOf(M.string(), M.string()), - feeConfig: FeeConfigShape, - feedPolicy: FeedPolicyShape, - chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), - assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), -}); - -/** - * XXX Shouldn't the bridge or board vat handle this? + * @import {CorePowersG} from './orch.start.js'; * - * @param {string} path - * @param {{ - * chainStorage: ERef; - * board: ERef; - * }} io - */ -const makePublishingStorageKit = async (path, { chainStorage, board }) => { - const storageNode = await E(chainStorage).makeChildNode(path); - - const marshaller = await E(board).getPublishingMarshaller(); - return { storageNode, marshaller }; -}; - -const BOARD_AUX = 'boardAux'; -const marshalData = makeMarshal(_val => Fail`data only`); -/** - * @param {Brand} brand - * @param {Pick} powers + * @import {FastUsdcSF} from './fast-usdc.contract.js'; + * @import {FastUSDCConfig} from './types.js' */ -const publishDisplayInfo = async (brand, { board, chainStorage }) => { - // chainStorage type includes undefined, which doesn't apply here. - // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247 - const boardAux = E(chainStorage).makeChildNode(BOARD_AUX); - const [id, displayInfo, allegedName] = await Promise.all([ - E(board).getId(brand), - E(brand).getDisplayInfo(), - E(brand).getAllegedName(), - ]); - const node = E(boardAux).makeChildNode(id); - const aux = marshalData.toCapData(harden({ allegedName, displayInfo })); - await E(node).setValue(JSON.stringify(aux)); -}; -const FEED_POLICY = 'feedPolicy'; const POOL_METRICS = 'poolMetrics'; +const FEED_POLICY = 'feedPolicy'; -/** - * @param {ERef} node - * @param {FeedPolicy} policy - */ -const publishFeedPolicy = async (node, policy) => { - const feedPolicy = E(node).makeChildNode(FEED_POLICY); - await E(feedPolicy).setValue(JSON.stringify(policy)); -}; - -/** - * @typedef { PromiseSpaceOf<{ - * fastUsdcKit: FastUSDCKit - * }> & { - * installation: PromiseSpaceOf<{ fastUsdc: Installation }>; - * instance: PromiseSpaceOf<{ fastUsdc: Instance }>; - * issuer: PromiseSpaceOf<{ FastLP: Issuer }>; - * brand: PromiseSpaceOf<{ FastLP: Brand }>; - * }} FastUSDCCorePowers - * - * @typedef {StartedInstanceKitWithLabel & { - * privateArgs: StartParams['privateArgs']; - * }} FastUSDCKit - */ +const trace = makeTracer(`FUSDC-Start`, true); /** - * @throws if oracle smart wallets are not yet provisioned * - * @param {BootstrapPowers & FastUSDCCorePowers } powers - * @param {{ options: LegibleCapData }} config + * @param {OrchestrationPowers} orchestrationPowers + * @param {Marshaller} marshaller + * @param {FastUSDCConfig} config + * @returns {Promise[1]>} */ -export const startFastUSDC = async ( - { - produce: { fastUsdcKit }, - consume: { - agoricNames, - namesByAddress, - board, - chainStorage, - chainTimerService: timerService, - localchain, - cosmosInterchainService, - startUpgradable, - zoe, - }, - issuer: { - produce: { FastLP: produceShareIssuer }, - }, - brand: { - produce: { FastLP: produceShareBrand }, - }, - installation: { - consume: { fastUsdc }, - }, - instance: { - produce: { fastUsdc: produceInstance }, - }, - }, +export const makePrivateArgs = async ( + orchestrationPowers, + marshaller, config, ) => { - trace('startFastUSDC'); - - await null; - /** @type {Issuer<'nat'>} */ - const USDCissuer = await E(agoricNames).lookup('issuer', 'USDC'); - const brands = harden({ - USDC: await E(USDCissuer).getBrand(), - }); - - const { terms, oracles, feeConfig, feedPolicy, ...net } = fromExternalConfig( - config.options, - brands, - FastUSDCConfigShape, - ); - trace('using terms', terms); + const { feeConfig, chainInfo, assetInfo } = config; trace('using fee config', feeConfig); - trace('look up oracle deposit facets'); - const oracleDepositFacets = await deeplyFulfilledObject( - objectMap(oracles, async address => { - /** @type {DepositFacet} */ - const depositFacet = await E(namesByAddress).lookup( - address, - 'depositFacet', - ); - return depositFacet; - }), - ); - - const { storageNode, marshaller } = await makePublishingStorageKit( - contractName, - { - board, - // @ts-expect-error Promise case is vestigial - chainStorage, - }, - ); + const { storageNode } = orchestrationPowers; const poolMetricsNode = await E(storageNode).makeChildNode(POOL_METRICS); - const privateArgs = await deeplyFulfilledObject( - harden({ - agoricNames, - feeConfig, - localchain, - orchestrationService: cosmosInterchainService, - poolMetricsNode, - storageNode, - timerService, - marshaller, - chainInfo: net.chainInfo, - assetInfo: net.assetInfo, - }), - ); - - const kit = await E(startUpgradable)({ - label: contractName, - installation: fastUsdc, - issuerKeywordRecord: harden({ USDC: USDCissuer }), - terms, - privateArgs, + return harden({ + ...orchestrationPowers, + feeConfig, + poolMetricsNode, + marshaller, + chainInfo, + assetInfo, }); - fastUsdcKit.resolve(harden({ ...kit, privateArgs })); - const { instance, creatorFacet } = kit; - - await publishFeedPolicy(storageNode, feedPolicy); - - const { - issuers: { PoolShares: shareIssuer }, - brands: { PoolShares: shareBrand }, - } = await E(zoe).getTerms(instance); - produceShareIssuer.resolve(shareIssuer); - produceShareBrand.resolve(shareBrand); - await publishDisplayInfo(shareBrand, { board, chainStorage }); - - await Promise.all( - Object.entries(oracleDepositFacets).map(async ([name, depositFacet]) => { - const address = oracles[name]; - trace('making invitation for', name, address); - const toWatch = await E(creatorFacet).makeOperatorInvitation(address); +}; +harden(makePrivateArgs); - const amt = await E(depositFacet).receive(toWatch); - trace('sent', amt, 'to', name); - }), +/** + * @param {BootstrapPowers & CorePowersG<'fastUsdc', FastUsdcSF, typeof permit>} permitted + * @param {{ options: LegibleCapData }} configStruct + */ +export const startFastUSDC = async (permitted, configStruct) => { + const { config, kit } = await startOrchContract( + meta, + permit, + makePrivateArgs, + permitted, + configStruct, ); - produceInstance.reset(); - produceInstance.resolve(instance); + const { storageNode } = kit.privateArgs; + const { feedPolicy } = config; + const policyNode = E(storageNode).makeChildNode(FEED_POLICY); + await E(policyNode).setValue(JSON.stringify(feedPolicy)); - const addresses = await E(kit.creatorFacet).publishAddresses(); + const { creatorFacet } = kit; + const addresses = await E(creatorFacet).publishAddresses(); trace('contract orch account addresses', addresses); - if (!net.noNoble) { - const addr = await E(kit.creatorFacet).connectToNoble(); + if (!config.noNoble) { + const addr = await E(creatorFacet).connectToNoble(); trace('noble intermediate recipient', addr); } - trace('startFastUSDC done', instance); }; -harden(startFastUSDC); - -/** - * @param {{ - * restoreRef: (b: ERef) => Promise; - * }} utils - * @param {{ - * installKeys: { fastUsdc: ERef }; - * options: LegibleCapData; - * }} param1 - */ -export const getManifestForFastUSDC = ( - { restoreRef }, - { installKeys, options }, -) => { - return { - /** @type {BootstrapManifest} */ - manifest: { - [startFastUSDC.name]: { - produce: { - fastUsdcKit: true, - }, - consume: { - chainStorage: true, - chainTimerService: true, - localchain: true, - cosmosInterchainService: true, - // limited distribution durin MN2: contract installation - startUpgradable: true, - zoe: true, // only getTerms() is needed. XXX should be split? - - // widely shared: name services - agoricNames: true, - namesByAddress: true, - board: true, - }, - issuer: { - produce: { FastLP: true }, // UNTIL #10432 - }, - brand: { - produce: { FastLP: true }, // UNTIL #10432 - }, - instance: { - produce: { fastUsdc: true }, - }, - installation: { - consume: { fastUsdc: true }, - }, - }, - }, - installations: { - fastUsdc: restoreRef(installKeys.fastUsdc), - }, - options, - }; -}; +// XXX hm... we need to preserve the function name. +export const getManifestForFastUSDC = (u, d) => + makeGetManifest(startFastUSDC, permit, meta.name)(u, d); diff --git a/packages/fast-usdc/src/orch.start.js b/packages/fast-usdc/src/orch.start.js new file mode 100644 index 00000000000..6711c772c38 --- /dev/null +++ b/packages/fast-usdc/src/orch.start.js @@ -0,0 +1,349 @@ +import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal'; +import { Fail } from '@endo/errors'; +import { E } from '@endo/far'; +import { makeMarshal } from '@endo/marshal'; +import { M, mustMatch } from '@endo/patterns'; +import { fromExternalConfig } from './utils/config-marshal.js'; + +/** + * @import {CopyRecord} from '@endo/pass-style'; + * @import {TypedPattern} from '@agoric/internal'; + * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js' + * @import {OrchestrationPowers} from '@agoric/orchestration'; + * @import {ContractStartFunction, Instance, StartedInstanceKit} from '@agoric/zoe/src/zoeService/utils' + * @import {DepositFacet} from '@agoric/ertp/src/types.js' + * @import {Board, NameHub} from '@agoric/vats' + * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js' + * @import {BootstrapManifestPermit} from '@agoric/vats/src/core/lib-boot.js'; + * @import {LegibleCapData} from './utils/config-marshal.js' + */ + +const { entries, fromEntries, keys, values } = Object; // XXX move up + +const trace = makeTracer(`ORCH-Start`, true); + +/** + * XXX Shouldn't the bridge or board vat handle this? + * + * @param {string} path + * @param {{ + * chainStorage: ERef; + * board: ERef; + * }} io + */ +const makePublishingStorageKit = async (path, { chainStorage, board }) => { + const storageNode = await E(chainStorage).makeChildNode(path); + + const marshaller = await E(board).getPublishingMarshaller(); + return { storageNode, marshaller }; +}; + +const BOARD_AUX = 'boardAux'; +const marshalData = makeMarshal(_val => Fail`data only`); +/** + * @param {Brand} brand + * @param {Pick} powers + */ +const publishDisplayInfo = async (brand, { board, chainStorage }) => { + // chainStorage type includes undefined, which doesn't apply here. + // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247 + const boardAux = E(chainStorage).makeChildNode(BOARD_AUX); + const [id, displayInfo, allegedName] = await Promise.all([ + E(board).getId(brand), + E(brand).getDisplayInfo(), + E(brand).getAllegedName(), + ]); + const node = E(boardAux).makeChildNode(id); + const aux = marshalData.toCapData(harden({ allegedName, displayInfo })); + await E(node).setValue(JSON.stringify(aux)); +}; + +/** + * @param {string} role + * @param {BootstrapPowers['consume']['namesByAddress']} namesByAddress + * @param {Record} nameToAddress + */ +const makeAdminRole = (role, namesByAddress, nameToAddress) => { + const lookup = async () => { + trace('look up deposit facets for', role); + return deeplyFulfilledObject( + objectMap(nameToAddress, async address => { + /** @type {DepositFacet} */ + const depositFacet = await E(namesByAddress).lookup( + address, + 'depositFacet', + ); + return depositFacet; + }), + ); + }; + const lookupP = lookup(); + + return harden({ + lookup: () => lookupP, + /** @param {(addr: string) => Promise} makeInvitation */ + send: async makeInvitation => { + const oracleDepositFacets = await lookupP; + await Promise.all( + entries(oracleDepositFacets).map(async ([name, depositFacet]) => { + const address = nameToAddress[name]; + trace('making invitation for', role, name, address); + const toWatch = await makeInvitation(address); + + const amt = await E(depositFacet).receive(toWatch); + trace('sent', amt, 'to', role, name); + }), + ); + }, + }); +}; + +/** + * @template {PermitG} P + * @param {BootstrapPowers['consume']['agoricNames']} agoricNames + * @param {P} permitG + */ +export const permittedIssuers = async (agoricNames, permitG) => { + const permittedKeys = keys(permitG?.issuer?.consume || {}); + const agoricIssuers = await E(E(agoricNames).lookup('issuer')).entries(); + /** @type {Record} */ + // @ts-expect-error by construction + const issuerKeywordRecord = fromEntries( + agoricIssuers.filter(([n, _v]) => permittedKeys.includes(n)), + ); + return issuerKeywordRecord; +}; + +/** + * @template {string} CN contract name + * @template {ContractStartFunction} SF typeof start + * @template {CopyRecord} CFG + * @typedef {ContractMeta & {name: CN} & { + * adminRoles?: Record['creatorFacet']>, + * deployConfigShape?: TypedPattern, + * }} ContractMetaG + */ + +/** + * @template {ContractStartFunction} SF typeof start + * @typedef {StartedInstanceKit & { + * label: string, + * privateArgs: Parameters[1]; + * }} UpgradeKit + * + */ + +/** + * @typedef {BootstrapManifest & + * { issuer: BootstrapManifest} & + * { brand: BootstrapManifest } + * } PermitG generic permit constraints + */ + +/** + * @template {string} CN contract name + * @template {ContractStartFn} SF typeof start + * @template {PermitG} P permit + * @typedef { PromiseSpaceOf>> & { + * installation: PromiseSpaceOf>>; + * instance: PromiseSpaceOf>>; + * issuer: PromiseSpaceOf>; + * brand: PromiseSpaceOf>; + * }} CorePowersG + */ + +/** + * @template {ContractStartFn} SF typeof start + * @template {CopyRecord} CFG + * @typedef {(op: OrchestrationPowers, m: Marshaller, cfg: CFG) => Parameters[1]} MakePrivateArgs + */ + +/** + * @throws if admin role smart wallets are not yet provisioned + * + * @template {string} CN contract name + * @template {ContractStartFn} SF typeof start + * @template {CopyRecord} CFG + * @template {PermitG} P permit + * @param {ContractMetaG} metaG generic metadata + * @param {P} permitG + * @param {MakePrivateArgs} makePrivateArgs + * @param {CorePowersG & BootstrapPowers} powers + * @param {{ options: LegibleCapData }} configStruct + */ +export const startOrchContract = async ( + metaG, + permitG, + makePrivateArgs, + { + produce, + consume, + issuer: { produce: produceIssuer }, + brand: { produce: produceBrand }, + installation: { + consume: { [metaG.name]: installation }, + }, + instance: { + produce: { [metaG.name]: produceInstance }, + }, + }, + configStruct, +) => { + trace('startOrchContract'); + + const { agoricNames } = consume; + const issuerKeywordRecord = await permittedIssuers(agoricNames, permitG); + + /** @type {Promise} */ + const brandHub = E(agoricNames).lookup('brand'); + const xVatEntries = await E(brandHub).entries(); + const config = fromExternalConfig( + configStruct.options, + fromEntries(xVatEntries), + metaG.deployConfigShape, + ); + const { terms } = config; + trace('using terms', terms); + + const adminRoles = objectMap(metaG?.adminRoles || {}, (_method, role) => { + const nameToAddress = config[role]; + mustMatch(nameToAddress, M.recordOf(M.string(), M.string())); + return makeAdminRole( + /** @type {string} */ (role), // XXX tsc gets confused? + consume.namesByAddress, + nameToAddress, + ); + }); + + await Promise.all(values(adminRoles).map(r => r.lookup())); + + /** @type {{ chainStorage: Promise}} */ + // @ts-expect-error Promise case is vestigial + const { chainStorage, board } = consume; + const { storageNode, marshaller } = await makePublishingStorageKit( + metaG.name, + { board, chainStorage }, + ); + + const { + chainTimerService: timerService, + localchain, + cosmosInterchainService, + } = consume; + const orchestrationPowers = await deeplyFulfilledObject( + harden({ + localchain, + orchestrationService: cosmosInterchainService, + storageNode, + timerService, + agoricNames, + }), + ); + const privateArgs = await makePrivateArgs( + orchestrationPowers, + marshaller, + config, + ); + + const { startUpgradable, zoe } = consume; + const kit = await E(startUpgradable)({ + label: metaG.name, + installation, + issuerKeywordRecord, + terms, + privateArgs, + }); + const { instance, creatorFacet } = kit; + /** @type {UpgradeKit} */ + const fullKit = harden({ ...kit, privateArgs }); + // @ts-expect-error XXX tsc gets confused? + produce[`${metaG.name}Kit`].resolve(fullKit); + + const newIssuerNames = keys(permitG?.issuer?.produce || {}).filter( + n => permitG?.brand?.produce?.[n], + ); + if (newIssuerNames.length > 0) { + const nameToKeyword = permitG.issuer.produce; + const { issuers, brands } = await E(zoe).getTerms(instance); + for (const name of newIssuerNames) { + const keyword = nameToKeyword[name]; + keyword in issuers || + Fail`${name} not in contract issuers: ${keys(issuers)}`; + console.log('new well-known Issuer, Brand:', name, 'from', keyword); + produceIssuer[name].reset(); + produceIssuer[name].resolve(issuers[keyword]); + produceBrand[name].reset(); + produceBrand[name].resolve(brands[keyword]); + await publishDisplayInfo(brands[keyword], { board, chainStorage }); + } + } + + for (const [role, method] of entries(metaG.adminRoles || {})) { + await adminRoles[role].send(addr => E(creatorFacet)[method](addr)); + } + + produceInstance.reset(); + produceInstance.resolve(instance); + + trace('startOrchContract done', instance); + return { config, kit: fullKit }; +}; +harden(startOrchContract); + +/** @satisfies {BootstrapManifestPermit} */ +export const orchPermit = /** @type {const} */ ({ + localchain: true, + cosmosInterchainService: true, + chainStorage: true, + chainTimerService: true, + agoricNames: true, + + // for publishing Brands and other remote object references + board: true, + + // limited distribution durin MN2: contract installation + startUpgradable: true, + zoe: true, // only getTerms() is needed. XXX should be split? +}); +harden(orchPermit); + +/** + * to find deposit facets for admin invitations + * + * @satisfies {BootstrapManifestPermit} + */ +export const adminPermit = /** @type {const} */ ({ + namesByAddress: true, +}); +harden(adminPermit); + +/** + * @template {CopyRecord} CFG + * @template {PermitG} P permit + * + * @param {Function} startFn + * @param {P} permit + * @param {string} contractName + */ +export const makeGetManifest = (startFn, permit, contractName) => { + /** + * @param {{ + * restoreRef: (b: ERef) => Promise; + * }} utils + * @param {{ + * installKeys: { fastUsdc: ERef }; + * options: LegibleCapData; + * }} data + */ + const getManifestForFastOrch = ({ restoreRef }, { installKeys, options }) => { + return { + /** @satisfies {BootstrapManifest} */ + manifest: { [startFn.name]: permit }, + installations: { [contractName]: restoreRef(installKeys[contractName]) }, + options, + }; + }; + harden(getManifestForFastOrch); + + return getManifestForFastOrch; +};