Skip to content

Commit

Permalink
feat(smart-wallet): contract for wallet factory
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Jul 5, 2022
1 parent b510905 commit d807b03
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 15 deletions.
3 changes: 3 additions & 0 deletions packages/vats/decentral-core-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
"singleWallet": {
"sourceSpec": "@agoric/wallet/contract/src/singleWallet.js"
},
"walletFactory": {
"sourceSpec": "@agoric/wallet/contract/src/walletFactory.js"
},
"zoe": {
"sourceSpec": "@agoric/vats/src/vat-zoe.js"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/vats/decentral-demo-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
"singleWallet": {
"sourceSpec": "@agoric/wallet/contract/src/singleWallet.js"
},
"walletFactory": {
"sourceSpec": "@agoric/wallet/contract/src/walletFactory.js"
},
"zoe": {
"sourceSpec": "@agoric/vats/src/vat-zoe.js"
}
Expand Down
4 changes: 4 additions & 0 deletions packages/vats/scripts/build-bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const sourceToBundle = [
`@agoric/wallet/contract/src/singleWallet.js`,
`../bundles/bundle-singleWallet.js`,
],
[
`@agoric/wallet/contract/src/walletFactory.js`,
`../bundles/bundle-walletFactory.js`,
],
];

createBundles(sourceToBundle, dirname);
25 changes: 16 additions & 9 deletions packages/vats/src/core/basic-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,26 +236,33 @@ export const makeClientBanks = async ({
zoe,
},
installation: {
consume: { singleWallet },
consume: { walletFactory },
},
}) => {
const STORAGE_PATH = 'wallet';

const storageNode = await getChildNode(chainStorage, STORAGE_PATH);
const marshaller = E(board).getPublishingMarshaller();
const { creatorFacet } = await E(zoe).startInstance(
walletFactory,
{},
{ agoricNames, namesByAddress, board },
{ storageNode, marshaller },
);
return E(client).assignBundle([
address => {
const bank = E(bankManager).getBankForAddress(address);
/** @type {ERef<MyAddressNameAdmin>} */
const myAddressNameAdmin = E(namesByAddressAdmin).lookupAdmin(address);
const smartWallet = E(zoe).startInstance(
singleWallet,
{},
{ agoricNames, bank, namesByAddress, myAddressNameAdmin, board },
{ storageNode, marshaller },

const myWallet = E(creatorFacet).provideSmartWallet(
address,
bank,
myAddressNameAdmin,
);

// sets these values in REPL home by way of registerWallet
return { bank, smartWallet };
return { bank, myWallet };
},
]);
};
Expand All @@ -267,13 +274,13 @@ export const installBootContracts = async ({
devices: { vatAdmin },
consume: { zoe },
installation: {
produce: { centralSupply, mintHolder, singleWallet },
produce: { centralSupply, mintHolder, walletFactory },
},
}) => {
for (const [name, producer] of Object.entries({
centralSupply,
mintHolder,
singleWallet,
walletFactory,
})) {
const bundleCap = D(vatAdmin).getNamedBundleCap(name);
const bundle = D(bundleCap).getBundle();
Expand Down
4 changes: 2 additions & 2 deletions packages/vats/src/core/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const SHARED_CHAIN_BOOTSTRAP_MANIFEST = harden({
chainStorage: true,
zoe: 'zoe',
},
installation: { consume: { singleWallet: 'zoe' } },
installation: { consume: { walletFactory: 'zoe' } },
home: { produce: { bank: 'bank' } },
},
installBootContracts: {
Expand All @@ -124,7 +124,7 @@ const SHARED_CHAIN_BOOTSTRAP_MANIFEST = harden({
produce: {
centralSupply: 'zoe',
mintHolder: 'zoe',
singleWallet: 'zoe',
walletFactory: 'zoe',
},
},
},
Expand Down
3 changes: 2 additions & 1 deletion packages/vats/src/core/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
* issuer: |
* 'RUN' | 'BLD' | 'Attestation' | 'AUSD',
* installation: |
* 'centralSupply' | 'mintHolder' | 'singleWallet' |
* 'centralSupply' | 'mintHolder' | 'singleWallet' | 'walletFactory' |
* 'feeDistributor' |
* 'contractGovernor' | 'committee' | 'noActionElectorate' | 'binaryVoteCounter' |
* 'amm' | 'VaultFactory' | 'liquidate' | 'runStake' |
Expand Down Expand Up @@ -171,6 +171,7 @@
* produce: Record<WellKnownName['installation'], Producer<Installation>>,
* consume: Record<WellKnownName['installation'], Promise<Installation<unknown>>> & {
* singleWallet: Promise<Installation<import('@agoric/smart-wallet/src/singleWallet.js').start>>,
* walletFactory: Promise<Installation<import('@agoric/smart-wallet/src/walletFactory.js').start>>,
* },
* },
* instance:{
Expand Down
1 change: 1 addition & 0 deletions packages/vats/src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const agoricNamesReserved = harden({
centralSupply: 'central supply',
mintHolder: 'mint holder',
singleWallet: 'single smart wallet',
walletFactory: 'multitenant smart wallet',
contractGovernor: 'contract governor',
committee: 'committee electorate',
noActionElectorate: 'no action electorate',
Expand Down
3 changes: 3 additions & 0 deletions packages/vats/test/devices.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import bundleCentralSupply from '../bundles/bundle-centralSupply.js';
import bundleMintHolder from '../bundles/bundle-mintHolder.js';
import bundleSingleWallet from '../bundles/bundle-singleWallet.js';
import bundleWalletFactory from '../bundles/bundle-walletFactory.js';

export const devices = {
vatAdmin: {
Expand All @@ -13,6 +14,8 @@ export const devices = {
return bundleMintHolder;
case 'singleWallet':
return bundleSingleWallet;
case 'walletFactory':
return bundleWalletFactory;
default:
throw new Error(`unknown bundle ${name}`);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/wallet/api/src/lib-wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const cmp = (a, b) => {
return -1;
};

// TODO rename to makeWalletKit
/**
* @typedef {object} MakeWalletParams
* @property {ERef<ZoeService>} zoe
Expand All @@ -75,6 +76,8 @@ export function makeWallet({
inboxStateChangeHandler = noActionStateChangeHandler,
dateNow = undefined,
}) {
assert(myAddressNameAdmin, 'missing myAddressNameAdmin');

let lastId = 0;

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet/api/src/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export function buildRootObject(vatPowers) {
localTimerService,
localTimerPollInterval,
}) {
assert(myAddressNameAdmin, 'missing myAddressNameAdmin');

/** @type {ERef<() => number> | undefined} */
let dateNowP;
if (timerDevice) {
Expand Down
7 changes: 4 additions & 3 deletions packages/wallet/contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
"@endo/captp": "^2.0.9"
},
"dependencies": {
"@agoric/wallet-backend": "0.12.1",
"@agoric/deploy-script-support": "^0.9.0",
"@agoric/zoe": "^0.24.0",
"@agoric/notifier": "^0.4.0",
"@agoric/vat-data": "^0.3.1",
"@agoric/wallet-backend": "0.12.1",
"@agoric/zoe": "^0.24.0",
"@endo/far": "^0.2.5"
},
"keywords": [],
Expand All @@ -46,4 +47,4 @@
"publishConfig": {
"access": "public"
}
}
}
116 changes: 116 additions & 0 deletions packages/wallet/contract/src/walletFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// @ts-check
/**
* @file Wallet Factory
*
* Contract to make smart wallets.
*/
import '@agoric/deploy-script-support/exported.js';
import '@agoric/wallet-backend/src/types.js'; // TODO avoid ambient types

import {
makeStoredSubscription,
makeSubscriptionKit,
observeIteration,
} from '@agoric/notifier';
import spawn from '@agoric/wallet-backend/src/wallet.js';
import '@agoric/zoe/exported.js';
import { E, Far } from '@endo/far';
import { makeScalarBigMapStore } from '@agoric/vat-data';

const { assign, entries, keys, fromEntries } = Object;

/**
* @typedef {{
* agoricNames: NameHub,
* board: Board,
* namesByAddress: NameHub,
* }} SmartWalletContractTerms
*/

/**
*
* @param {ZCF<SmartWalletContractTerms>} zcf
* @param {{
* storageNode?: ERef<StorageNode>,
* marshaller?: ERef<Marshaller>,
* }} privateArgs
*/
export const start = async (zcf, privateArgs) => {
const { agoricNames, namesByAddress, board } = zcf.getTerms();
assert(board, 'missing board');
assert(namesByAddress, 'missing namesByAddress');
assert(agoricNames, 'missing agoricNames');
const zoe = zcf.getZoeService();
const { storageNode, marshaller } = privateArgs;

/** @type {MapStore<string, unknown>} */
const walletsByAddress = makeScalarBigMapStore('walletsByAddress');

/**
*
* @param {string} address
* @param {ERef<import('@agoric/vats/src/vat-bank').Bank>} bank
* @param {ERef<MyAddressNameAdmin>} myAddressNameAdmin
*/
const provideSmartWallet = async (address, bank, myAddressNameAdmin) => {
assert.typeof(address, 'string', 'invalid address');
assert(bank, 'missing bank');
assert(myAddressNameAdmin, 'missing myAddressNameAdmin');

const walletVat = spawn({
agoricNames,
namesByAddress,
// ??? why do we make this instead of passing the address itself?
myAddressNameAdmin,
zoe,
board,
localTimerService: undefined,
publicFacetName: 'smartWallet',
});

const wallet = await E(walletVat).getWallet(bank);
const admin = E(wallet).getAdminFacet();

/** @type {Record<string, ERef<Notifier<unknown>>>} */
const notifierParts = {
contacts: E(admin).getContactsNotifier(),
dapps: E(admin).getDappsNotifier(),
issuers: E(admin).getIssuersNotifier(),
offers: E(admin).getOffersNotifier(),
payments: E(admin).getPaymentsNotifier(),
purses: E(admin).getPursesNotifier(),
};
const mutableState = fromEntries(keys(notifierParts).map(key => [key, []]));
const { subscription, publication } = makeSubscriptionKit();
publication.updateState({ ...mutableState });
entries(notifierParts).forEach(([key, notifier]) => {
void observeIteration(notifier, {
updateState: value =>
publication.updateState({
...assign(mutableState, { [key]: value }),
}),
});
});

const myWalletStorageNode =
storageNode && E(storageNode).getChildNode(address);
const storedSubscription = makeStoredSubscription(
subscription,
myWalletStorageNode,
marshaller,
);

const smartWallet = Far('SmartWallet', {
...wallet,
getSubscription: () => storedSubscription,
});
walletsByAddress.init(address, smartWallet);
return smartWallet;
};

return {
creatorFacet: Far('walletFactoryCreator', {
provideSmartWallet,
}),
};
};
2 changes: 2 additions & 0 deletions packages/wallet/contract/src/walletHolder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const makeWalletHolder = () => {};
harden(makeWalletHolder);
3 changes: 3 additions & 0 deletions packages/wallet/contract/test/devices.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import bundleCentralSupply from '@agoric/vats/bundles/bundle-centralSupply.js';
import bundleMintHolder from '@agoric/vats/bundles/bundle-mintHolder.js';
import bundleSingleWallet from '@agoric/vats/bundles/bundle-singleWallet.js';
import bundleWalletFactory from '@agoric/vats/bundles/bundle-walletFactory.js';

export const devices = {
vatAdmin: {
Expand All @@ -13,6 +14,8 @@ export const devices = {
return bundleMintHolder;
case 'singleWallet':
return bundleSingleWallet;
case 'walletFactory':
return bundleWalletFactory;
default:
throw new Error(`unknown bundle ${name}`);
}
Expand Down
Loading

0 comments on commit d807b03

Please sign in to comment.