Publishing to chainStorage
Contracts can use notifiers and subscriptions to publish to clients. To publish data visible to vstorage queries, contracts should connect a subscriber to a chainStorage
node.
Deployment Capabilities for Publishing to chainStorage
In Adding Parameter Governance to a Contract, storageNode
and marshaller
are passed to the contract in its privateArgs
so it can publish to chainStorage.
In dapp-agoric-basics, the startSwapContract
uses 2 permitted deployment capabilities, chainStorage
and board
and uses them to make the privateArgs
:
const marshaller = await E(board).getPublishingMarshaller();
const storageNode = await E(chainStorage).makeChildNode(contractName);
A Marshaller
is parameterized by functions for mapping unforgeable object identities to plain data slot references and back. Using the board name service gives consistent slot references across contracts. As discussed in Marshalling Amounts and Instances, this lets off-chain clients use the same @endo/marshal
API.
The chainStorage
node corresponds to the published
key in the vstorage hierarchy. Using E(chainStorage).makeChildNode(contractName)
gives the contract access to write to the published.swaparoo
key and all keys under it.
The swaparoo
contract delegates the rest of publishing governance parameters to the @agoric/governance
package.
Publishing structured data to chainStorage
Let's look at the Inter Protocol assetReserve.js contract to get more of the details. It publishes to published.reserve.metrics data of the form
/**
* @typedef {object} MetricsNotification
* @property {AmountKeywordRecord} allocations
* @property {Amount<'nat'>} shortfallBalance shortfall from liquidation that
* has not yet been compensated.
* @property {Amount<'nat'>} totalFeeMinted total Fee tokens minted to date
* @property {Amount<'nat'>} totalFeeBurned total Fee tokens burned to date
*/
For example:
{
allocations: {
Fee: {
brand: Object @Alleged: IST brand {},
value: 64561373455n,
},
ATOM: {
brand: Object @Alleged: ATOM brand {},
value: 6587020n
},
},
shortfallBalance: {
brand: Object @Alleged: IST brand {},
value: 5747205025n,
},
totalFeeBurned: {
brand: Object @Alleged: IST brand {},
value: n,
},
totalFeeMinted: {
brand: Object @Alleged: IST brand {},
value: 0n,
},
},
The method that writes this data is:
writeMetrics() {
const { state } = this;
const metrics = harden({
allocations: state.collateralSeat.getCurrentAllocation(),
shortfallBalance: state.shortfallBalance,
totalFeeMinted: state.totalFeeMinted,
totalFeeBurned: state.totalFeeBurned,
});
void state.metricsKit.recorder.write(metrics);
},
The metricsKit
is made with a makeRecorderKit
function:
metricsKit: makeRecorderKit(
metricsNode,
/** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<MetricsNotification>} */ (
M.any()
),
),
We "prepare" (in the exo sense) that function for making a RecorderKit
using prepareRecorderKitMakers.
const { makeRecorderKit } = prepareRecorderKitMakers(
baggage,
privateArgs.marshaller,
);
The contract gets baggage
, along with privateArgs
when it starts in the usual way for upgradable contracts:
/**
* Asset Reserve holds onto assets for the Inter Protocol, and ...
*
* @param {{
* ...
* marshaller: ERef<Marshaller>,
* storageNode: ERef<StorageNode>,
* }} privateArgs
* @param {Baggage} baggage
*/
export const prepare = async (zcf, privateArgs, baggage) => {
...
};
The reserve uses its StorageNode
and makes a child to get metricsNode
:
const metricsNode = await E(storageNode).makeChildNode('metrics');
The marshaller
is used to serialize data structures such as MetricsNotification
above.
Deployment Capabilities for the reserve
To start assetReserve
, the setupReserve function again supplies the two relevant privateArgs
, marshaller
and storageNode
:
const STORAGE_PATH = 'reserve';
const storageNode = await E(storageNode).makeChildNode(STORAGE_PATH);
const marshaller = await E(board).getReadonlyMarshaller();
The setupReserve
function gets chainStorage
and board
deployment capabilities passed in:
export const setupReserve = async ({
consume: {
board,
chainStorage,
...
},
...
}) => { ... };