Skip to content

Commit 7565626

Browse files
grypezclaude
andauthored
refactor(evm-wallet-experiment): split coordinator-vat into home/away (#939)
Splits the monolithic 3,275-line `coordinator-vat` into four focused components — home coordinator, away coordinator, delegator vat, and redeemer vat — and replaces raw caveat bytes with a discriminated union of decoded semantic grant types. **Architecture before:** ``` coordinator-vat (3,275 lines) — home and away concerns mixed together delegation-vat (211 lines) — limited grant handling delegation-grant.ts — builds grants from raw CaveatSpec arrays ``` **Architecture after:** ``` home-coordinator (2,388 lines) — keyring, signing, delegation building, peer-relay relay delegator-vat (245 lines) — caveat encoding, unsigned grant construction away-coordinator (1,999 lines) — grant reception, typed delegation routing redeemer-vat (57 lines) — away-side grant storage delegation-twin — semantic execution wrapper per grant ``` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Large refactor touching core wallet execution/routing and delegation redemption paths (UserOps, direct tx submission, spend limits), with significant behavioral surface area and risk of regressions across home/away flows. > > **Overview** > Splits the wallet subcluster’s coordinator responsibilities by introducing role-based clustering: `makeWalletClusterConfig` now selects `home-coordinator` vs `away-coordinator` bundles and swaps the auxiliary vat to `delegator` (home) or `redeemer` (away), with updated tests and build bundling. > > Replaces the prior raw `DelegationGrant`/`CaveatSpec`-driven twin flow with **semantic, discriminated grant types** (`TransferNativeGrant`, `TransferFungibleGrant`) and a simplified `METHOD_CATALOG`; removes the `delegation-grant` builder module and rewrites `makeDelegationTwin` to enforce per-grant interface guards plus local spend/limit tracking. > > Adds an `away-coordinator` vat implementing away-side routing, grant receipt/storage integration, and redemption execution paths (incl. ERC-4337/UserOp vs direct 7702 submission) plus new shared `tx-utils` helpers for gas/result validation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 088e26c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d54aa5c commit 7565626

37 files changed

Lines changed: 4561 additions & 9096 deletions

packages/evm-wallet-experiment/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"dist/"
3030
],
3131
"scripts": {
32-
"build": "ocap bundle src/vats/coordinator-vat.ts src/vats/keyring-vat.ts src/vats/provider-vat.ts src/vats/delegation-vat.ts",
32+
"build": "ocap bundle src/vats/home-coordinator.ts src/vats/away-coordinator.ts src/vats/keyring-vat.ts src/vats/provider-vat.ts src/vats/delegator-vat.ts src/vats/redeemer-vat.ts",
3333
"build:docs": "typedoc",
3434
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/evm-wallet-experiment",
3535
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./dist ./.turbo ./logs",

packages/evm-wallet-experiment/src/cluster-config.test.ts

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { describe, it, expect } from 'vitest';
22

33
import { makeWalletClusterConfig } from './cluster-config.ts';
4-
import type { Address } from './types.ts';
54

65
const BUNDLE_BASE_URL = 'http://localhost:3000';
76

87
describe('cluster-config', () => {
98
describe('makeWalletClusterConfig', () => {
10-
it('creates a valid ClusterConfig', () => {
9+
it('defaults to home role', () => {
1110
const config = makeWalletClusterConfig({
1211
bundleBaseUrl: BUNDLE_BASE_URL,
1312
});
@@ -17,7 +16,34 @@ describe('cluster-config', () => {
1716
expect(config.vats).toHaveProperty('coordinator');
1817
expect(config.vats).toHaveProperty('keyring');
1918
expect(config.vats).toHaveProperty('provider');
20-
expect(config.vats).toHaveProperty('delegation');
19+
expect(config.vats).toHaveProperty('delegator');
20+
expect(config.vats).not.toHaveProperty('redeemer');
21+
});
22+
23+
it('uses home-coordinator.bundle for home role', () => {
24+
const config = makeWalletClusterConfig({
25+
bundleBaseUrl: BUNDLE_BASE_URL,
26+
role: 'home',
27+
});
28+
29+
const coordConfig = config.vats.coordinator as { bundleSpec: string };
30+
expect(coordConfig.bundleSpec).toBe(
31+
`${BUNDLE_BASE_URL}/home-coordinator.bundle`,
32+
);
33+
});
34+
35+
it('uses away-coordinator.bundle for away role', () => {
36+
const config = makeWalletClusterConfig({
37+
bundleBaseUrl: BUNDLE_BASE_URL,
38+
role: 'away',
39+
});
40+
41+
const coordConfig = config.vats.coordinator as { bundleSpec: string };
42+
expect(coordConfig.bundleSpec).toBe(
43+
`${BUNDLE_BASE_URL}/away-coordinator.bundle`,
44+
);
45+
expect(config.vats).toHaveProperty('redeemer');
46+
expect(config.vats).not.toHaveProperty('delegator');
2147
});
2248

2349
it('includes OCAP URL services by default', () => {
@@ -40,40 +66,34 @@ describe('cluster-config', () => {
4066
expect(config.services).toStrictEqual(['customService']);
4167
});
4268

43-
it('sets delegation manager address as parameter', () => {
44-
const address = '0xcccccccccccccccccccccccccccccccccccccccc' as Address;
69+
it('has four vats for home role', () => {
4570
const config = makeWalletClusterConfig({
4671
bundleBaseUrl: BUNDLE_BASE_URL,
47-
delegationManagerAddress: address,
72+
role: 'home',
4873
});
74+
const vatNames = Object.keys(config.vats);
4975

50-
expect(config.vats.delegation).toHaveProperty('parameters');
51-
expect(
52-
(config.vats.delegation as { parameters: Record<string, unknown> })
53-
.parameters.delegationManagerAddress,
54-
).toBe(address);
76+
expect(vatNames).toStrictEqual([
77+
'coordinator',
78+
'keyring',
79+
'provider',
80+
'delegator',
81+
]);
5582
});
5683

57-
it('has four vats with bundleSpec', () => {
84+
it('has four vats for away role', () => {
5885
const config = makeWalletClusterConfig({
5986
bundleBaseUrl: BUNDLE_BASE_URL,
87+
role: 'away',
6088
});
6189
const vatNames = Object.keys(config.vats);
6290

6391
expect(vatNames).toStrictEqual([
6492
'coordinator',
6593
'keyring',
6694
'provider',
67-
'delegation',
95+
'redeemer',
6896
]);
69-
70-
for (const vatName of vatNames) {
71-
const vatConfig = config.vats[vatName] as { bundleSpec: string };
72-
expect(vatConfig).toHaveProperty('bundleSpec');
73-
expect(vatConfig.bundleSpec).toBe(
74-
`${BUNDLE_BASE_URL}/${vatName}-vat.bundle`,
75-
);
76-
}
7797
});
7898

7999
it('requests required globals for all vats', () => {
@@ -82,12 +102,11 @@ describe('cluster-config', () => {
82102
});
83103

84104
const baseGlobals = ['TextEncoder', 'TextDecoder'];
85-
for (const vatName of ['keyring', 'provider', 'delegation']) {
105+
for (const vatName of ['keyring', 'provider', 'delegator']) {
86106
const vatConfig = config.vats[vatName] as { globals?: string[] };
87107
expect(vatConfig.globals).toStrictEqual(baseGlobals);
88108
}
89109

90-
// Coordinator additionally needs Date and setTimeout
91110
const coordConfig = config.vats.coordinator as { globals?: string[] };
92111
expect(coordConfig.globals).toStrictEqual([
93112
'TextEncoder',

packages/evm-wallet-experiment/src/cluster-config.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import type { ClusterConfig } from '@metamask/ocap-kernel';
22

3-
import type { Address } from './types.ts';
4-
53
/**
64
* Options for creating a wallet cluster configuration.
75
*/
86
export type WalletClusterConfigOptions = {
97
bundleBaseUrl: string;
10-
delegationManagerAddress?: Address;
11-
chainId?: number;
8+
role?: 'home' | 'away';
129
forceReset?: boolean;
1310
services?: string[];
1411
allowedHosts?: string[];
@@ -25,18 +22,38 @@ export function makeWalletClusterConfig(
2522
): ClusterConfig {
2623
const {
2724
bundleBaseUrl,
28-
delegationManagerAddress,
25+
role = 'home',
2926
services = ['ocapURLIssuerService', 'ocapURLRedemptionService'],
3027
allowedHosts,
3128
} = options;
3229

30+
const coordinatorBundle =
31+
role === 'home'
32+
? `${bundleBaseUrl}/home-coordinator.bundle`
33+
: `${bundleBaseUrl}/away-coordinator.bundle`;
34+
35+
const auxiliaryVat =
36+
role === 'home'
37+
? {
38+
delegator: {
39+
bundleSpec: `${bundleBaseUrl}/delegator-vat.bundle`,
40+
globals: ['TextEncoder', 'TextDecoder'],
41+
},
42+
}
43+
: {
44+
redeemer: {
45+
bundleSpec: `${bundleBaseUrl}/redeemer-vat.bundle`,
46+
globals: ['TextEncoder', 'TextDecoder'],
47+
},
48+
};
49+
3350
return {
3451
bootstrap: 'coordinator',
3552
forceReset: options.forceReset ?? false,
3653
services,
3754
vats: {
3855
coordinator: {
39-
bundleSpec: `${bundleBaseUrl}/coordinator-vat.bundle`,
56+
bundleSpec: coordinatorBundle,
4057
globals: ['TextEncoder', 'TextDecoder', 'Date', 'setTimeout'],
4158
},
4259
keyring: {
@@ -50,13 +67,7 @@ export function makeWalletClusterConfig(
5067
fetch: allowedHosts ? { allowedHosts } : {},
5168
},
5269
},
53-
delegation: {
54-
bundleSpec: `${bundleBaseUrl}/delegation-vat.bundle`,
55-
globals: ['TextEncoder', 'TextDecoder'],
56-
...(delegationManagerAddress
57-
? { parameters: { delegationManagerAddress } }
58-
: {}),
59-
},
70+
...auxiliaryVat,
6071
},
6172
};
6273
}

packages/evm-wallet-experiment/src/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export type {
2727
Address,
2828
Action,
2929
Caveat,
30-
CaveatSpec,
3130
CaveatType,
3231
ChainConfig,
3332
CreateDelegationOptions,
@@ -44,13 +43,14 @@ export type {
4443
SwapQuote,
4544
SwapResult,
4645
TransactionRequest,
46+
TransferFungibleGrant,
47+
TransferNativeGrant,
4748
UserOperation,
4849
WalletCapabilities,
4950
} from './types.ts';
5051

5152
export {
5253
ActionStruct,
53-
CaveatSpecStruct,
5454
CaveatStruct,
5555
CaveatTypeValues,
5656
ChainConfigStruct,
@@ -66,6 +66,8 @@ export {
6666
SmartAccountConfigStruct,
6767
SwapQuoteStruct,
6868
TransactionRequestStruct,
69+
TransferFungibleGrantStruct,
70+
TransferNativeGrantStruct,
6971
UserOperationStruct,
7072
WalletCapabilitiesStruct,
7173
} from './types.ts';
@@ -182,11 +184,5 @@ export type {
182184
export { METHOD_CATALOG } from './lib/method-catalog.ts';
183185
export type { CatalogMethodName } from './lib/method-catalog.ts';
184186

185-
// Grant builder
186-
export {
187-
buildDelegationGrant,
188-
makeDelegationGrantBuilder,
189-
} from './lib/delegation-grant.ts';
190-
191187
// Twin factory
192188
export { makeDelegationTwin } from './lib/delegation-twin.ts';

0 commit comments

Comments
 (0)