Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a00ef79
refactor(evm-wallet): replace DelegationGrant with discriminated unio…
grypez Apr 15, 2026
1981c86
feat(evm-wallet): add delegator-vat for home-side grant building
grypez Apr 15, 2026
7d9d32b
feat(evm-wallet): add redeemer-vat for away-side grant storage
grypez Apr 15, 2026
16dc003
feat(evm-wallet): add home-coordinator as home-side split of coordina…
grypez Apr 15, 2026
9711ca5
feat(evm-wallet): rewrite delegation-twin for semantic grant enforcement
grypez Apr 19, 2026
d7c9162
feat(evm-wallet): add away-coordinator with delegation routing and pe…
grypez Apr 19, 2026
1c636b1
refactor(evm-wallet): remove coordinator-vat, delegation-vat, delegat…
grypez Apr 15, 2026
f7c0c5e
test(evm-wallet-experiment): migrate Docker e2e tests to new coordina…
grypez Apr 20, 2026
746b5cb
fix(evm-wallet): normalize token addresses and preserve twin spend co…
grypez Apr 20, 2026
4670710
refactor(evm-wallet): use superstruct to validate CapData shape in do…
grypez Apr 21, 2026
282ba34
refactor(evm-wallet): use superstruct to validate CapData in openclaw…
grypez Apr 21, 2026
957d4e2
refactor(evm-wallet): extract shared tx helpers to lib/tx-utils
grypez Apr 21, 2026
674361f
fix(evm-wallet): normalize token to lowercase before routing to deleg…
grypez Apr 21, 2026
1e4fa62
fix(evm-wallet): migrate node test scripts to home/away coordinator API
grypez Apr 21, 2026
5244989
fix(evm-wallet): address code review — guards, baggage validation, tests
grypez Apr 21, 2026
7735f14
fix(evm-wallet): coerce amount to bigint at JSON boundary in away-coo…
grypez Apr 21, 2026
9c824f4
fix(evm-wallet): track cumulative spend (totalLimit) in transferNativ…
grypez Apr 21, 2026
03ee6c0
fix(evm-wallet): persist homeCoordRef in baggage across kernel restarts
grypez Apr 22, 2026
33c067b
fix(evm-wallet): rename maxAmount→totalLimit in TransferFungibleGrant…
grypez Apr 22, 2026
bc89843
fix(evm-wallet): normalize maxAmount to bigint before toString(16) in…
grypez Apr 22, 2026
0c68d6f
docs(evm-wallet): clarify twin routing error semantics in JSDoc
grypez Apr 22, 2026
d9ef637
fix(evm-wallet): use totalLimit instead of maxAmount in delegation-tw…
grypez Apr 22, 2026
088e26c
fix(evm-wallet): collect all twin errors and throw with cause array
grypez Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/evm-wallet-experiment/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"dist/"
],
"scripts": {
"build": "ocap bundle src/vats/coordinator-vat.ts src/vats/keyring-vat.ts src/vats/provider-vat.ts src/vats/delegation-vat.ts",
"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",
"build:docs": "typedoc",
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/evm-wallet-experiment",
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./dist ./.turbo ./logs",
Expand Down
65 changes: 42 additions & 23 deletions packages/evm-wallet-experiment/src/cluster-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { describe, it, expect } from 'vitest';

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

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

describe('cluster-config', () => {
describe('makeWalletClusterConfig', () => {
it('creates a valid ClusterConfig', () => {
it('defaults to home role', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
});
Expand All @@ -17,7 +16,34 @@ describe('cluster-config', () => {
expect(config.vats).toHaveProperty('coordinator');
expect(config.vats).toHaveProperty('keyring');
expect(config.vats).toHaveProperty('provider');
expect(config.vats).toHaveProperty('delegation');
expect(config.vats).toHaveProperty('delegator');
expect(config.vats).not.toHaveProperty('redeemer');
});

it('uses home-coordinator.bundle for home role', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
role: 'home',
});

const coordConfig = config.vats.coordinator as { bundleSpec: string };
expect(coordConfig.bundleSpec).toBe(
`${BUNDLE_BASE_URL}/home-coordinator.bundle`,
);
});

it('uses away-coordinator.bundle for away role', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
role: 'away',
});

const coordConfig = config.vats.coordinator as { bundleSpec: string };
expect(coordConfig.bundleSpec).toBe(
`${BUNDLE_BASE_URL}/away-coordinator.bundle`,
);
expect(config.vats).toHaveProperty('redeemer');
expect(config.vats).not.toHaveProperty('delegator');
});

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

it('sets delegation manager address as parameter', () => {
const address = '0xcccccccccccccccccccccccccccccccccccccccc' as Address;
it('has four vats for home role', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
delegationManagerAddress: address,
role: 'home',
});
const vatNames = Object.keys(config.vats);

expect(config.vats.delegation).toHaveProperty('parameters');
expect(
(config.vats.delegation as { parameters: Record<string, unknown> })
.parameters.delegationManagerAddress,
).toBe(address);
expect(vatNames).toStrictEqual([
'coordinator',
'keyring',
'provider',
'delegator',
]);
});

it('has four vats with bundleSpec', () => {
it('has four vats for away role', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
role: 'away',
});
const vatNames = Object.keys(config.vats);

expect(vatNames).toStrictEqual([
'coordinator',
'keyring',
'provider',
'delegation',
'redeemer',
]);

for (const vatName of vatNames) {
const vatConfig = config.vats[vatName] as { bundleSpec: string };
expect(vatConfig).toHaveProperty('bundleSpec');
expect(vatConfig.bundleSpec).toBe(
`${BUNDLE_BASE_URL}/${vatName}-vat.bundle`,
);
}
});

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

const baseGlobals = ['TextEncoder', 'TextDecoder'];
for (const vatName of ['keyring', 'provider', 'delegation']) {
for (const vatName of ['keyring', 'provider', 'delegator']) {
const vatConfig = config.vats[vatName] as { globals?: string[] };
expect(vatConfig.globals).toStrictEqual(baseGlobals);
}

// Coordinator additionally needs Date and setTimeout
const coordConfig = config.vats.coordinator as { globals?: string[] };
expect(coordConfig.globals).toStrictEqual([
'TextEncoder',
Expand Down
37 changes: 24 additions & 13 deletions packages/evm-wallet-experiment/src/cluster-config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import type { ClusterConfig } from '@metamask/ocap-kernel';

import type { Address } from './types.ts';

/**
* Options for creating a wallet cluster configuration.
*/
export type WalletClusterConfigOptions = {
bundleBaseUrl: string;
delegationManagerAddress?: Address;
chainId?: number;
role?: 'home' | 'away';
forceReset?: boolean;
services?: string[];
allowedHosts?: string[];
Expand All @@ -25,18 +22,38 @@ export function makeWalletClusterConfig(
): ClusterConfig {
const {
bundleBaseUrl,
delegationManagerAddress,
role = 'home',
services = ['ocapURLIssuerService', 'ocapURLRedemptionService'],
allowedHosts,
} = options;

const coordinatorBundle =
role === 'home'
? `${bundleBaseUrl}/home-coordinator.bundle`
: `${bundleBaseUrl}/away-coordinator.bundle`;

const auxiliaryVat =
role === 'home'
? {
delegator: {
bundleSpec: `${bundleBaseUrl}/delegator-vat.bundle`,
globals: ['TextEncoder', 'TextDecoder'],
},
}
: {
redeemer: {
bundleSpec: `${bundleBaseUrl}/redeemer-vat.bundle`,
globals: ['TextEncoder', 'TextDecoder'],
},
};

return {
bootstrap: 'coordinator',
forceReset: options.forceReset ?? false,
services,
vats: {
coordinator: {
bundleSpec: `${bundleBaseUrl}/coordinator-vat.bundle`,
bundleSpec: coordinatorBundle,
globals: ['TextEncoder', 'TextDecoder', 'Date', 'setTimeout'],
},
keyring: {
Expand All @@ -50,13 +67,7 @@ export function makeWalletClusterConfig(
fetch: allowedHosts ? { allowedHosts } : {},
},
},
delegation: {
bundleSpec: `${bundleBaseUrl}/delegation-vat.bundle`,
globals: ['TextEncoder', 'TextDecoder'],
...(delegationManagerAddress
? { parameters: { delegationManagerAddress } }
: {}),
},
...auxiliaryVat,
},
};
}
11 changes: 2 additions & 9 deletions packages/evm-wallet-experiment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export type {
Address,
Action,
Caveat,
CaveatSpec,
CaveatType,
ChainConfig,
CreateDelegationOptions,
Expand All @@ -44,18 +43,18 @@ export type {
SwapQuote,
SwapResult,
TransactionRequest,
TransferFungibleGrant,
TransferNativeGrant,
UserOperation,
WalletCapabilities,
} from './types.ts';

export {
ActionStruct,
CaveatSpecStruct,
CaveatStruct,
CaveatTypeValues,
ChainConfigStruct,
CreateDelegationOptionsStruct,
DelegationGrantStruct,
Comment thread
cursor[bot] marked this conversation as resolved.
DelegationStatusValues,
DelegationStruct,
Eip712DomainStruct,
Expand Down Expand Up @@ -182,11 +181,5 @@ export type {
export { METHOD_CATALOG } from './lib/method-catalog.ts';
export type { CatalogMethodName } from './lib/method-catalog.ts';

// Grant builder
export {
buildDelegationGrant,
makeDelegationGrantBuilder,
} from './lib/delegation-grant.ts';

// Twin factory
export { makeDelegationTwin } from './lib/delegation-twin.ts';
Loading
Loading