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 6, 2022
1 parent 6218aca commit e0309aa
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 11 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);
23 changes: 15 additions & 8 deletions packages/vats/src/core/basic-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,22 +236,29 @@ 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 smartWallet = E(creatorFacet).provideSmartWallet(
address,
bank,
myAddressNameAdmin,
);

// sets these values in REPL home by way of registerWallet
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
82 changes: 82 additions & 0 deletions packages/wallet/contract/src/smartWallet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @ts-check
import { E, Far } from '@endo/far';
import {
makeStoredSubscription,
makeSubscriptionKit,
observeIteration,
} from '@agoric/notifier';
import spawn from '@agoric/wallet-backend/src/wallet.js';

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

/**
* @param {{
* address: string,
* bank: ERef<import('@agoric/vats/src/vat-bank').Bank>,
* myAddressNameAdmin: ERef<MyAddressNameAdmin>,
* }} unique
* @param {{
* agoricNames: NameHub,
* board: Board
* marshaller?: ERef<Marshaller>,
* namesByAddress: NameHub,
* storageNode?: ERef<StorageNode>,
* zoe: ERef<ZoeService>,
* }} shared
*/
export const makeSmartWallet = async (
{ address, bank, myAddressNameAdmin },
{ agoricNames, board, marshaller, namesByAddress, storageNode, zoe },
) => {
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,
});

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,
);

return Far('SmartWallet', {
...wallet,
getSubscription: () => storedSubscription,
});
};
harden(makeSmartWallet);
/** @typedef {Awaited<ReturnType<typeof makeSmartWallet>>} SmartWallet */
77 changes: 77 additions & 0 deletions packages/wallet/contract/src/walletFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @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 { makeAtomicProvider } from '@agoric/store/src/stores/store-utils';
import { makeScalarBigMapStore } from '@agoric/vat-data';
import '@agoric/zoe/exported.js';
import { Far } from '@endo/far';
import { makeSmartWallet } from './smartWallet';

/**
* @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, import('./smartWallet').SmartWallet>} */
const walletsByAddress = makeScalarBigMapStore('walletsByAddress');
const provider = makeAtomicProvider(walletsByAddress);

const shared = {
agoricNames,
board,
namesByAddress,
storageNode,
marshaller,
zoe,
};

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

/** @type {() => Promise<import('./smartWallet').SmartWallet>} */
const maker = () =>
makeSmartWallet({ address, bank, myAddressNameAdmin }, shared);

return provider.provideAsync(address, maker);
};

return {
creatorFacet: Far('walletFactoryCreator', {
provideSmartWallet,
}),
};
};
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 e0309aa

Please sign in to comment.