-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: examples/auto-stake-it.contract.js
- creates an example contract that user .monitorTransfers to react to an incoming IBC transfer. when the transfer is received, it's sent to an ICA then delegated. Both accounts are put in a PortfolioHolder kit, which combines ContinuingOfferResults into a single record - includes logic to ignore outgoing transfers, uknown denoms, and unkown sourceChannels - does not include logic to look for a specific value in the transfer memo field, but this could be added - refs: #9042
- Loading branch information
1 parent
c582a50
commit 62b254f
Showing
4 changed files
with
612 additions
and
0 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
packages/builders/scripts/testing/start-auto-stake-it.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/** | ||
* @file A proposal to start the auto-stake-it contract. | ||
* | ||
* AutoStakeIt allows users to to create an auto-forwarding address that | ||
* transfers and stakes tokens on a remote chain when received. | ||
*/ | ||
import { makeTracer } from '@agoric/internal'; | ||
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; | ||
import { E } from '@endo/far'; | ||
|
||
/** | ||
* @import {AutoStakeItSF} from '@agoric/orchestration/src/examples/auto-stake-it.contract.js'; | ||
*/ | ||
|
||
const contractName = 'autoAutoStakeIt'; | ||
const trace = makeTracer(contractName, true); | ||
|
||
/** | ||
* @param {BootstrapPowers} powers | ||
*/ | ||
export const startAutoStakeIt = async ({ | ||
consume: { | ||
agoricNames, | ||
board, | ||
chainStorage, | ||
chainTimerService, | ||
cosmosInterchainService, | ||
localchain, | ||
startUpgradable, | ||
}, | ||
installation: { | ||
// @ts-expect-error not a WellKnownName | ||
consume: { [contractName]: installation }, | ||
}, | ||
instance: { | ||
// @ts-expect-error not a WellKnownName | ||
produce: { [contractName]: produceInstance }, | ||
}, | ||
}) => { | ||
trace(`start ${contractName}`); | ||
await null; | ||
|
||
const storageNode = await makeStorageNodeChild(chainStorage, contractName); | ||
const marshaller = await E(board).getPublishingMarshaller(); | ||
|
||
/** @type {StartUpgradableOpts<AutoStakeItSF>} */ | ||
const startOpts = { | ||
label: 'autoAutoStakeIt', | ||
installation, | ||
terms: undefined, | ||
privateArgs: { | ||
agoricNames: await agoricNames, | ||
orchestrationService: await cosmosInterchainService, | ||
localchain: await localchain, | ||
storageNode, | ||
marshaller, | ||
timerService: await chainTimerService, | ||
}, | ||
}; | ||
|
||
const { instance } = await E(startUpgradable)(startOpts); | ||
produceInstance.resolve(instance); | ||
}; | ||
harden(startAutoStakeIt); | ||
|
||
export const getManifestForContract = ( | ||
{ restoreRef }, | ||
{ installKeys, ...options }, | ||
) => { | ||
return { | ||
manifest: { | ||
[startAutoStakeIt.name]: { | ||
consume: { | ||
agoricNames: true, | ||
board: true, | ||
chainStorage: true, | ||
chainTimerService: true, | ||
cosmosInterchainService: true, | ||
localchain: true, | ||
startUpgradable: true, | ||
}, | ||
installation: { | ||
consume: { [contractName]: true }, | ||
}, | ||
instance: { | ||
produce: { [contractName]: true }, | ||
}, | ||
}, | ||
}, | ||
installations: { | ||
[contractName]: restoreRef(installKeys[contractName]), | ||
}, | ||
options, | ||
}; | ||
}; | ||
|
||
/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ | ||
export const defaultProposalBuilder = async ({ publishRef, install }) => { | ||
return harden({ | ||
// Somewhat unorthodox, source the exports from this builder module | ||
sourceSpec: '@agoric/builders/scripts/testing/start-auto-stake-it.js', | ||
getManifestCall: [ | ||
'getManifestForContract', | ||
{ | ||
installKeys: { | ||
autoAutoStakeIt: publishRef( | ||
install( | ||
'@agoric/orchestration/src/examples/auto-stake-it.contract.js', | ||
), | ||
), | ||
}, | ||
}, | ||
], | ||
}); | ||
}; | ||
|
||
export default async (homeP, endowments) => { | ||
// import dynamically so the module can work in CoreEval environment | ||
const dspModule = await import('@agoric/deploy-script-support'); | ||
const { makeHelpers } = dspModule; | ||
const { writeCoreEval } = await makeHelpers(homeP, endowments); | ||
await writeCoreEval(startAutoStakeIt.name, defaultProposalBuilder); | ||
}; |
157 changes: 157 additions & 0 deletions
157
packages/orchestration/src/examples/auto-stake-it-tap-kit.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { M, mustMatch } from '@endo/patterns'; | ||
import { E } from '@endo/far'; | ||
import { VowShape } from '@agoric/vow'; | ||
import { makeTracer } from '@agoric/internal'; | ||
import { atob } from '@endo/base64'; | ||
import { ChainAddressShape } from '../typeGuards.js'; | ||
|
||
const trace = makeTracer('AutoStakeItTap'); | ||
|
||
/** | ||
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats'; | ||
* @import {VowTools} from '@agoric/vow'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import {TargetApp} from '@agoric/vats/src/bridge-target.js'; | ||
* @import {ChainAddress, CosmosValidatorAddress, Denom, OrchestrationAccount, StakingAccountActions} from '@agoric/orchestration'; | ||
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js'; | ||
*/ | ||
|
||
/** | ||
* @typedef {{ | ||
* stakingAccount: ERef<OrchestrationAccount<any> & StakingAccountActions>; | ||
* localAccount: ERef<OrchestrationAccount<{ chainId: 'agoric' }>>; | ||
* validator: CosmosValidatorAddress; | ||
* localChainAddress: ChainAddress; | ||
* remoteChainAddress: ChainAddress; | ||
* sourceChannel: IBCChannelID; | ||
* remoteDenom: Denom; | ||
* localDenom: Denom; | ||
* }} StakingTapState | ||
*/ | ||
|
||
const StakingTapStateShape = { | ||
stakingAccount: M.remotable('CosmosOrchestrationAccount'), | ||
localAccount: M.remotable('LocalOrchestrationAccount'), | ||
validator: ChainAddressShape, | ||
localChainAddress: ChainAddressShape, | ||
remoteChainAddress: ChainAddressShape, | ||
sourceChannel: M.string(), | ||
remoteDenom: M.string(), | ||
localDenom: M.string(), | ||
}; | ||
harden(StakingTapStateShape); | ||
|
||
/** | ||
* @param {Zone} zone | ||
* @param {VowTools} vowTools | ||
*/ | ||
const prepareMakeStakingTapKit = (zone, { watch }) => { | ||
return zone.exoClassKit( | ||
'StakingTap', | ||
{ | ||
tap: M.interface('AutoStakeItTap', { | ||
receiveUpcall: M.call(M.record()).returns( | ||
M.or(VowShape, M.undefined()), | ||
), | ||
}), | ||
transferWatcher: M.interface('TransferWatcher', { | ||
onFulfilled: M.call(M.undefined()) | ||
.optional(M.bigint()) | ||
.returns(VowShape), | ||
}), | ||
}, | ||
/** @param {StakingTapState} initialState */ | ||
initialState => { | ||
mustMatch(initialState, StakingTapStateShape); | ||
return /** @type {StakingTapState} */ (harden(initialState)); | ||
}, | ||
{ | ||
tap: { | ||
/** | ||
* Transfers from localAccount to stakingAccount, then delegates from | ||
* the stakingAccount to `validator` if the expected token (remoteDenom) | ||
* is received. | ||
* | ||
* @param {VTransferIBCEvent} event | ||
*/ | ||
receiveUpcall(event) { | ||
trace('receiveUpcall', event); | ||
|
||
// ignore packets from unknown channels | ||
if (event.packet.source_channel !== this.state.sourceChannel) { | ||
return; | ||
} | ||
|
||
const tx = /** @type {FungibleTokenPacketData} */ ( | ||
JSON.parse(atob(event.packet.data)) | ||
); | ||
trace('receiveUpcall packet data', tx); | ||
|
||
const { remoteDenom, localChainAddress } = this.state; | ||
// ignore outgoing transfers | ||
if (tx.receiver !== localChainAddress.value) { | ||
return; | ||
} | ||
// only interested in transfers of `remoteDenom` | ||
if (tx.denom !== remoteDenom) { | ||
return; | ||
} | ||
|
||
const { localAccount, localDenom, remoteChainAddress } = this.state; | ||
return watch( | ||
E(localAccount).transfer( | ||
{ | ||
denom: localDenom, | ||
value: BigInt(tx.amount), | ||
}, | ||
remoteChainAddress, | ||
), | ||
this.facets.transferWatcher, | ||
BigInt(tx.amount), | ||
); | ||
}, | ||
}, | ||
transferWatcher: { | ||
/** | ||
* @param {void} _result | ||
* @param {bigint} value the qty of uatom to delegate | ||
*/ | ||
onFulfilled(_result, value) { | ||
const { stakingAccount, validator, remoteDenom } = this.state; | ||
return watch( | ||
E(stakingAccount).delegate(validator, { | ||
denom: remoteDenom, | ||
value, | ||
}), | ||
); | ||
}, | ||
}, | ||
}, | ||
); | ||
}; | ||
|
||
/** @typedef {ReturnType<typeof prepareMakeStakingTapKit>} MakeStakingTapKit */ | ||
/** @typedef {ReturnType<MakeStakingTapKit>} StakingTapKit */ | ||
|
||
/** | ||
* Provides a {@link TargetApp} that reacts to an incoming IBC transfer by: | ||
* | ||
* 1. transferring the funds to the staking account specified at initialization | ||
* 2. delegating the funds to the validator specified at initialization | ||
* | ||
* XXX consider a facet with a method for changing the validator | ||
* | ||
* XXX consider logic for multiple stakingAccounts + denoms | ||
* | ||
* @param {Zone} zone | ||
* @param {VowTools} vowTools | ||
* @returns {( | ||
* ...args: Parameters<ReturnType<typeof prepareMakeStakingTapKit>> | ||
* ) => StakingTapKit['tap']} | ||
*/ | ||
export const prepareMakeStakingTap = (zone, vowTools) => { | ||
const makeKit = prepareMakeStakingTapKit(zone, vowTools); | ||
return (...args) => makeKit(...args).tap; | ||
}; | ||
/** @typedef {ReturnType<typeof prepareMakeStakingTap>} MakeStakingTap */ | ||
/** @typedef {StakingTapKit['tap']} StakingTap */ |
Oops, something went wrong.