diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js new file mode 100644 index 00000000000..b91eb454d4c --- /dev/null +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -0,0 +1,138 @@ +/** + * @file This contract demonstrates the continuing invitation pattern with async + * flows. + * + * The primary offer result is a power for invitation makers that can perform + * actions with an ICA account. + */ +import { AmountShape } from '@agoric/ertp'; +import { VowShape } from '@agoric/vow'; +import { M } from '@endo/patterns'; +import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js'; +import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js'; +import { withOrchestration } from '../utils/start-helper.js'; +import * as flows from './staking-combinations.flows.js'; + +/** + * @import {GuestInterface} from '@agoric/async-flow'; + * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; + * @import {ContinuingOfferResult} from '@agoric/smart-wallet/src/types.js'; + * @import {TimerService} from '@agoric/time'; + * @import {LocalChain} from '@agoric/vats/src/localchain.js'; + * @import {NameHub} from '@agoric/vats'; + * @import {Vow} from '@agoric/vow'; + * @import {Remote} from '@agoric/internal'; + * @import {Zone} from '@agoric/zone'; + * @import {CosmosInterchainService} from '../exos/cosmos-interchain-service.js'; + * @import {OrchestrationTools} from '../utils/start-helper.js'; + * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; + */ + +const emptyOfferShape = harden({ + // Nothing to give; the funds are deposited offline + give: {}, + want: {}, // UNTIL https://github.com/Agoric/agoric-sdk/issues/2230 + exit: M.any(), +}); + +/** + * Orchestration contract to be wrapped by withOrchestration for Zoe. + * + * @param {ZCF} zcf + * @param {{ + * agoricNames: Remote; + * localchain: Remote; + * orchestrationService: Remote; + * storageNode: Remote; + * marshaller: Marshaller; + * timerService: Remote; + * }} privateArgs + * @param {Zone} zone + * @param {OrchestrationTools} tools + */ +const contract = async ( + zcf, + privateArgs, + zone, + { orchestrateAll, vowTools }, +) => { + const ExtraInvitationMakerInterface = M.interface('', { + DepositAndDelegate: M.call(M.array()).returns(VowShape), + UndelegateAndTransfer: M.call(M.array()).returns(VowShape), + }); + /** @type {any} XXX async membrane */ + const makeExtraInvitationMaker = zone.exoClass( + 'ContinuingInvitationExampleInvitationMakers', + ExtraInvitationMakerInterface, + /** @param {GuestInterface} account */ + account => { + return { account }; + }, + { + DepositAndDelegate() { + const { account } = this.state; + + const invP = zcf.makeInvitation( + (seat, validatorAddr, amountArg) => + // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this + orchFns.depositAndDelegate(account, seat, validatorAddr, amountArg), + 'Deposit and delegate', + undefined, + { + give: { + Stake: AmountShape, + }, + }, + ); + + return vowTools.watch(invP); + }, + /** + * @param {Omit[]} delegations + */ + UndelegateAndTransfer(delegations) { + const { account } = this.state; + + const invP = zcf.makeInvitation( + // eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this + () => orchFns.undelegateAndTransfer(account, delegations), + 'Undelegate and transfer', + undefined, + emptyOfferShape, + ); + + return vowTools.watch(invP); + }, + }, + ); + + /** @type {any} XXX async membrane */ + const makeCombineInvitationMakers = prepareCombineInvitationMakers( + zone, + CosmosOrchestrationInvitationMakersInterface, + ExtraInvitationMakerInterface, + ); + + const orchFns = orchestrateAll(flows, { + makeCombineInvitationMakers, + makeExtraInvitationMaker, + flows, + zcf, + }); + + const publicFacet = zone.exo('publicFacet', undefined, { + makeAccount() { + return zcf.makeInvitation( + orchFns.makeAccount, + 'Make an ICA account', + undefined, + emptyOfferShape, + ); + }, + }); + + return harden({ publicFacet }); +}; + +export const start = withOrchestration(contract); +harden(start); diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js new file mode 100644 index 00000000000..9361bbf5176 --- /dev/null +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -0,0 +1,83 @@ +/** + * @import {GuestInterface} from '@agoric/async-flow'; + * @import {Orchestrator, OrchestrationFlow, OrchestrationAccount, OrchestrationAccountI, StakingAccountActions, AmountArg, CosmosValidatorAddress} from '../types.js' + * @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js'; + * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; + * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; + * @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js'; + */ + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {{ + * makeCombineInvitationMakers: MakeCombineInvitationMakers; + * makeExtraInvitationMaker: (account: any) => InvitationMakers; + * }} ctx + * @param {ZCFSeat} _seat + * @param {{ chainName: string }} offerArgs + * @returns {Promise} + */ +export const makeAccount = async (orch, ctx, _seat, { chainName }) => { + const chain = await orch.getChain(chainName); + const account = await chain.makeAccount(); + + const extraMakers = ctx.makeExtraInvitationMaker(account); + + /** @type {ContinuingOfferResult} */ + const result = await account.asContinuingOffer(); + + return { + ...result, + invitationMakers: ctx.makeCombineInvitationMakers( + extraMakers, + result.invitationMakers, + ), + }; +}; +harden(makeAccount); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {GuestInterface} account + * @param {ZCFSeat} seat + * @param {CosmosValidatorAddress} validator + * @param {AmountArg} amount + * @returns {Promise} + */ +export const depositAndDelegate = async ( + orch, + ctx, + account, + seat, + validator, + amount, +) => { + console.log('depositAndDelegate', account, seat, validator, amount); + // TODO deposit the amount + await account.delegate(validator, amount); + return 'guest depositAndDelegate complete'; +}; +harden(depositAndDelegate); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {GuestInterface} account + * @param {Omit[]} delegations + * @returns {Promise} + */ +export const undelegateAndTransfer = async ( + orch, + ctx, + account, + delegations, +) => { + await account.undelegate(delegations); + // TODO transfer something + return 'guest undelegateAndTransfer complete'; +}; +harden(undelegateAndTransfer); diff --git a/packages/orchestration/src/exos/combine-invitation-makers.js b/packages/orchestration/src/exos/combine-invitation-makers.js new file mode 100644 index 00000000000..e7b647e3ba6 --- /dev/null +++ b/packages/orchestration/src/exos/combine-invitation-makers.js @@ -0,0 +1,53 @@ +import { M } from '@endo/patterns'; +import { + prepareGuardedAttenuator, + makeSyncMethodCallback, +} from '@agoric/internal/src/callback.js'; +import { getMethodNames } from '@agoric/internal'; + +/** + * @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js'; + * @import {Zone} from '@agoric/zone'; + */ + +// TODO use a helper from Endo https://github.com/endojs/endo/issues/2448 +/** + * Takes two or more InvitationMaker exos and combines them into a new one. + * + * @param {Zone} zone + * @param {import('@endo/patterns').InterfaceGuard[]} interfaceGuards + */ +export const prepareCombineInvitationMakers = (zone, ...interfaceGuards) => { + const methodGuards = interfaceGuards.map(ig => ig.payload.methodGuards); + const CombinedInterfaceGuard = M.interface( + 'CombinedInvitationMakers interface', + Object.assign({}, ...methodGuards), + ); + + const mixin = prepareGuardedAttenuator(zone, CombinedInterfaceGuard, { + tag: 'CombinedInvitationMakers', + }); + + /** + * @template {InvitationMakers[]} IM + * @param {IM} invitationMakers + * @returns {IM[number]} + */ + const combineInvitationMakers = (...invitationMakers) => { + const overrides = {}; + for (const invMakers of invitationMakers) { + // remove '__getInterfaceGuard__', '__getMethodNames__' + const names = getMethodNames(invMakers).filter(n => !n.startsWith('__')); + for (const key of names) { + overrides[key] = makeSyncMethodCallback(invMakers, key); + } + } + return mixin({ + overrides, + }); + }; + + return combineInvitationMakers; +}; + +/** @typedef {ReturnType} MakeCombineInvitationMakers */ diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index ecfa545b048..d3bb08e7af7 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -111,6 +111,27 @@ const PUBLIC_TOPICS = { account: ['Staking Account holder status', M.any()], }; +export const CosmosOrchestrationInvitationMakersInterface = M.interface( + 'invitationMakers', + { + Delegate: M.call(ChainAddressShape, AmountArgShape).returns(M.promise()), + Redelegate: M.call( + ChainAddressShape, + ChainAddressShape, + AmountArgShape, + ).returns(M.promise()), + WithdrawReward: M.call(ChainAddressShape).returns(M.promise()), + Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()), + DeactivateAccount: M.call().returns(M.promise()), + ReactivateAccount: M.call().returns(M.promise()), + TransferAccount: M.call().returns(M.promise()), + Send: M.call().returns(M.promise()), + SendAll: M.call().returns(M.promise()), + Transfer: M.call().returns(M.promise()), + }, +); +harden(CosmosOrchestrationInvitationMakersInterface); + /** * @param {Zone} zone * @param {object} powers @@ -177,24 +198,7 @@ export const prepareCosmosOrchestrationAccountKit = ( .returns(Vow$(M.record())), }), holder: IcaAccountHolderI, - invitationMakers: M.interface('invitationMakers', { - Delegate: M.call(ChainAddressShape, AmountArgShape).returns( - M.promise(), - ), - Redelegate: M.call( - ChainAddressShape, - ChainAddressShape, - AmountArgShape, - ).returns(M.promise()), - WithdrawReward: M.call(ChainAddressShape).returns(M.promise()), - Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()), - DeactivateAccount: M.call().returns(M.promise()), - ReactivateAccount: M.call().returns(M.promise()), - TransferAccount: M.call().returns(M.promise()), - Send: M.call().returns(M.promise()), - SendAll: M.call().returns(M.promise()), - Transfer: M.call().returns(M.promise()), - }), + invitationMakers: CosmosOrchestrationInvitationMakersInterface, }, /** * @param {object} info diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md new file mode 100644 index 00000000000..88d227b0e7a --- /dev/null +++ b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md @@ -0,0 +1,116 @@ +# Snapshot report for `test/examples/staking-combinations.test.ts` + +The actual snapshot is saved in `staking-combinations.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## start + +> contract baggage after start + + { + 'Durable Publish Kit_kindHandle': 'Alleged: kind', + Recorder_kindHandle: 'Alleged: kind', + asyncFlow: { + AdminAsyncFlow_kindHandle: 'Alleged: kind', + AdminAsyncFlow_singleton: 'Alleged: AdminAsyncFlow', + Bijection_kindHandle: 'Alleged: kind', + FunctionUnwrapper_kindHandle: 'Alleged: kind', + FunctionUnwrapper_singleton: 'Alleged: FunctionUnwrapper', + LogStore_kindHandle: 'Alleged: kind', + StateUnwrapper_kindHandle: 'Alleged: kind', + asyncFuncEagerWakers: [], + asyncFuncFailures: {}, + flowForOutcomeVow: {}, + unwrapMap: 'Alleged: weakMapStore', + }, + contract: { + CombinedInvitationMakers_kindHandle: 'Alleged: kind', + ContinuingInvitationExampleInvitationMakers_kindHandle: 'Alleged: kind', + orchestration: { + depositAndDelegate: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + makeAccount: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + undelegateAndTransfer: { + asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + flows: { + depositAndDelegate_kindHandle: 'Alleged: kind', + depositAndDelegate_singleton: 'Alleged: depositAndDelegate', + makeAccount_kindHandle: 'Alleged: kind', + makeAccount_singleton: 'Alleged: makeAccount', + undelegateAndTransfer_kindHandle: 'Alleged: kind', + undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer', + }, + makeCombineInvitationMakers_kindHandle: 'Alleged: kind', + makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers', + makeExtraInvitationMaker_kindHandle: 'Alleged: kind', + makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker', + }, + }, + }, + }, + publicFacet_kindHandle: 'Alleged: kind', + publicFacet_singleton: 'Alleged: publicFacet', + }, + orchestration: { + 'Cosmos Orchestration Account Holder_kindHandle': 'Alleged: kind', + 'Local Orchestration Account Kit_kindHandle': 'Alleged: kind', + LocalChainFacade_kindHandle: 'Alleged: kind', + Orchestrator_kindHandle: 'Alleged: kind', + RemoteChainFacade_kindHandle: 'Alleged: kind', + chainName: { + osmosis: 'Alleged: RemoteChainFacade public', + }, + ibcTools: { + IBCTransferSenderKit_kindHandle: 'Alleged: kind', + ibcResultWatcher_kindHandle: 'Alleged: kind', + ibcResultWatcher_singleton: 'Alleged: ibcResultWatcher', + }, + packetTools: { + PacketToolsKit_kindHandle: 'Alleged: kind', + }, + }, + vows: { + PromiseWatcher_kindHandle: 'Alleged: kind', + VowInternalsKit_kindHandle: 'Alleged: kind', + WatchUtils_kindHandle: 'Alleged: kind', + }, + zoe: {}, + } diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap new file mode 100644 index 00000000000..c18f9684acb Binary files /dev/null and b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap differ diff --git a/packages/orchestration/test/examples/staking-combinations.test.ts b/packages/orchestration/test/examples/staking-combinations.test.ts new file mode 100644 index 00000000000..58505844d28 --- /dev/null +++ b/packages/orchestration/test/examples/staking-combinations.test.ts @@ -0,0 +1,110 @@ +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { inspectMapStore } from '@agoric/internal/src/testing-utils.js'; +import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { E } from '@endo/far'; +import path from 'path'; +import { protoMsgMocks, UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; +import { commonSetup } from '../supports.js'; + +const dirname = path.dirname(new URL(import.meta.url).pathname); + +const contractFile = `${dirname}/../../src/examples/staking-combinations.contract.js`; +type StartFn = + typeof import('@agoric/orchestration/src/examples/staking-combinations.contract.js').start; + +test('start', async t => { + const { + bootstrap: { timer, vowTools: vt }, + brands: { ist }, + mocks: { ibcBridge }, + commonPrivateArgs, + } = await commonSetup(t); + + let contractBaggage; + const { zoe, bundleAndInstall } = await setUpZoeForTest({ + setJig: ({ baggage }) => { + contractBaggage = baggage; + }, + }); + const installation: Installation = + await bundleAndInstall(contractFile); + + const { publicFacet } = await E(zoe).startInstance( + installation, + { Stable: ist.issuer }, + {}, + commonPrivateArgs, + ); + + const inv = E(publicFacet).makeAccount(); + + t.is( + (await E(zoe).getInvitationDetails(inv)).description, + 'Make an ICA account', + ); + + const userSeat = await E(zoe).offer( + inv, + {}, + {}, + { + chainName: 'osmosis', + }, + ); + + const result = await vt.when(E(userSeat).getOfferResult()); + t.like(result, { + publicSubscribers: { + account: { + description: 'Staking Account holder status', + storagePath: 'mockChainStorageRoot.cosmos1test', + }, + }, + }); + + // Here the account would get funded through Cosmos native operations. + + // Delegate the funds like so, but don't bother executing the offer + // because the balances aren't tracked. + const delegateInv = await E(result.invitationMakers).Delegate( + { value: '10', encoding: 'bech32', chainId: 'osmosis' }, + { + denom: 'osmo', + value: 10n, + }, + ); + t.like(await E(zoe).getInvitationDetails(delegateInv), { + description: 'Delegate', + }); + + // Undelegate the funds using the guest flow + ibcBridge.addMockAck( + // observed in console + 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xnS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMd29MWTI5emJXOXpNWFJsYzNRU0VtTnZjMjF2YzNaaGJHOXdaWEl4ZEdWemRCb01DZ1YxYjNOdGJ4SURNVEF3IiwibWVtbyI6IiJ9', + protoMsgMocks.undelegate.ack, + ); + const undelegateInvVow = await E( + result.invitationMakers, + ).UndelegateAndTransfer([ + { validatorAddress: 'cosmosvaloper1test', shares: '100' }, + ]); + const undelegateInv = await vt.when(undelegateInvVow); + t.like(await E(zoe).getInvitationDetails(undelegateInv), { + description: 'Undelegate and transfer', + }); + + const undelegateUserSeat = await E(zoe).offer(undelegateInv); + + // Wait for the unbonding period + timer.advanceBy(UNBOND_PERIOD_SECONDS * 1000n); + + const undelegateResult = await vt.when( + E(undelegateUserSeat).getOfferResult(), + ); + t.is(undelegateResult, 'guest undelegateAndTransfer complete'); + + // snapshot the resulting contract baggage + const tree = inspectMapStore(contractBaggage); + t.snapshot(tree, 'contract baggage after start'); +}); diff --git a/packages/orchestration/test/network-fakes.ts b/packages/orchestration/test/network-fakes.ts index 8df40c2a310..9d73594f2a3 100644 --- a/packages/orchestration/test/network-fakes.ts +++ b/packages/orchestration/test/network-fakes.ts @@ -1,5 +1,6 @@ import { VowTools } from '@agoric/vow'; import { + base64ToBytes, prepareEchoConnectionKit, prepareLoopbackProtocolHandler, prepareNetworkPowers, @@ -19,11 +20,13 @@ import { prepareCallbacks as prepareIBCCallbacks, prepareIBCProtocol, } from '@agoric/vats/src/ibc.js'; -import { BridgeId } from '@agoric/internal'; +import { BridgeId, makeTracer } from '@agoric/internal'; import { E, Far } from '@endo/far'; import type { Guarded } from '@endo/exo'; import { defaultMockAckMap, errorAcknowledgments } from './ibc-mocks.js'; +const trace = makeTracer('NetworkFakes'); + /** * Mimic IBC Channel version negotation * @@ -152,6 +155,7 @@ export const makeFakeIBCBridge = ( zone: Zone, ): Guarded< ScopedBridgeManagerMethods<'dibc'> & { + addMockAck: (msgData: string, ackData: string) => void; setMockAck: (mockAckMap: Record) => void; setAddressPrefix: (addressPrefix: string) => void; inspectDibcBridge: () => { @@ -209,6 +213,11 @@ export const makeFakeIBCBridge = ( return zone.exo('Fake IBC Bridge Manager', undefined, { getBridgeId: () => BridgeId.DIBC, toBridge: async obj => { + trace( + 'toBridge', + obj, + obj.packet?.data ? base64ToBytes(obj.packet.data) : undefined, + ); if (obj.type === 'IBC_METHOD') { bridgeDowncalls = bridgeDowncalls.concat(obj); switch (obj.method) { @@ -240,10 +249,19 @@ export const makeFakeIBCBridge = ( return undefined; } case 'sendPacket': { + const mockAckMapHasData = obj.packet.data in mockAckMap; + if (!mockAckMapHasData) { + trace( + 'sendPacket acking err bc no mock ack for data:', + obj.packet.data, + base64ToBytes(obj.packet.data), + ); + } const ackEvent = ibcBridgeMocks.acknowledgementPacket(obj, { sequence: ibcSequenceNonce, - acknowledgement: - mockAckMap?.[obj.packet.data] || errorAcknowledgments.error5, + acknowledgement: mockAckMapHasData + ? mockAckMap[obj.packet.data] + : errorAcknowledgments.error5, }); bridgeEvents = bridgeEvents.concat(ackEvent); ibcSequenceNonce += 1; @@ -257,6 +275,7 @@ export const makeFakeIBCBridge = ( return undefined; }, fromBridge: async obj => { + trace('fromBridge', obj); bridgeEvents = bridgeEvents.concat(obj); if (!bridgeHandler) throw Error('no handler!'); return bridgeHandler.fromBridge(obj); @@ -276,14 +295,20 @@ export const makeFakeIBCBridge = ( * @param ackMap */ setMockAck: (ackMap: typeof mockAckMap) => { + trace('setMockAck', ackMap); mockAckMap = ackMap; }, + addMockAck: (msgData: string, ackData: string) => { + trace('addMockAck', msgData, ackData); + mockAckMap[msgData] = ackData; + }, /** * Set a new bech32 prefix for the mocked ICA channel. Defaults to `cosmos`. * * @param newPrefix */ setAddressPrefix: (newPrefix: typeof bech32Prefix) => { + trace('setAddressPrefix', newPrefix); bech32Prefix = newPrefix; }, /**