Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
90 changes: 90 additions & 0 deletions packages/eth-wallet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"name": "@ocap/eth-wallet",
"version": "0.0.0",
"private": true,
"description": "Capability-driven Ethereum wallet subcluster for the OCAP kernel",
"homepage": "https://github.com/MetaMask/ocap-kernel/tree/main/packages/eth-wallet#readme",
"bugs": {
"url": "https://github.com/MetaMask/ocap-kernel/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/MetaMask/ocap-kernel.git"
},
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},
"files": [
"dist/"
],
"scripts": {
"build": "ocap bundle src/vats",
"build:docs": "typedoc",
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/eth-wallet",
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./dist ./.turbo ./logs",
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
"lint:dependencies": "depcheck --quiet",
"lint:eslint": "eslint . --cache",
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies",
"lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore --log-level error",
"publish:preview": "yarn npm publish --tag preview",
"test": "vitest run --config vitest.config.ts",
"test:clean": "yarn test --no-cache --coverage.clean",
"test:dev": "yarn test --mode development",
"test:verbose": "yarn test --reporter verbose",
"test:watch": "vitest --config vitest.config.ts",
"test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent"
},
"dependencies": {
"@endo/eventual-send": "^1.3.4",
"@metamask/kernel-utils": "workspace:^",
"@metamask/ocap-kernel": "workspace:^",
"@metamask/superstruct": "^3.2.1",
"viem": "^2.27.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.4",
"@metamask/auto-changelog": "^5.3.0",
"@metamask/eslint-config": "^15.0.0",
"@metamask/eslint-config-nodejs": "^15.0.0",
"@metamask/eslint-config-typescript": "^15.0.0",
"@ocap/repo-tools": "workspace:^",
"@ts-bridge/cli": "^0.6.3",
"@ts-bridge/shims": "^0.1.1",
"@typescript-eslint/eslint-plugin": "^8.29.0",
"@typescript-eslint/parser": "^8.29.0",
"@typescript-eslint/utils": "^8.29.0",
"@vitest/eslint-plugin": "^1.6.5",
"depcheck": "^1.4.7",
"eslint": "^9.23.0",
"eslint-config-prettier": "^10.1.1",
"eslint-import-resolver-typescript": "^4.3.1",
"eslint-plugin-import-x": "^4.10.0",
"eslint-plugin-jsdoc": "^50.6.9",
"eslint-plugin-n": "^17.17.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-promise": "^7.2.1",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"turbo": "^2.5.6",
"typedoc": "^0.28.1",
"typescript": "~5.8.2",
"typescript-eslint": "^8.29.0",
"vite": "^7.3.0",
"vitest": "^4.0.16"
},
"engines": {
"node": ">=22"
}
}
87 changes: 87 additions & 0 deletions packages/eth-wallet/src/cluster-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
});

expect(config.bootstrap).toBe('coordinator');
expect(config.forceReset).toBe(true);
expect(config.vats).toHaveProperty('coordinator');
expect(config.vats).toHaveProperty('keyring');
expect(config.vats).toHaveProperty('provider');
expect(config.vats).toHaveProperty('delegation');
});

it('includes OCAP URL services by default', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
});

expect(config.services).toStrictEqual([
'ocapURLIssuerService',
'ocapURLRedemptionService',
]);
});

it('allows custom services', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
services: ['customService'],
});

expect(config.services).toStrictEqual(['customService']);
});

it('sets delegation manager address as parameter', () => {
const address = '0xcccccccccccccccccccccccccccccccccccccccc' as Address;
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
delegationManagerAddress: address,
});

expect(config.vats.delegation).toHaveProperty('parameters');
expect(
(config.vats.delegation as { parameters: Record<string, unknown> })
.parameters.delegationManagerAddress,
).toBe(address);
});

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

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

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('designates coordinator as the bootstrap vat', () => {
const config = makeWalletClusterConfig({
bundleBaseUrl: BUNDLE_BASE_URL,
});
expect(config.bootstrap).toBe('coordinator');
expect(config.vats).toHaveProperty(config.bootstrap);
});
});
});
51 changes: 51 additions & 0 deletions packages/eth-wallet/src/cluster-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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;
services?: string[];
};

/**
* Create a ClusterConfig for the wallet subcluster.
*
* @param options - Configuration options.
* @returns The cluster configuration.
*/
export function makeWalletClusterConfig(
options: WalletClusterConfigOptions,
): ClusterConfig {
const {
bundleBaseUrl,
delegationManagerAddress,
services = ['ocapURLIssuerService', 'ocapURLRedemptionService'],
} = options;

return {
bootstrap: 'coordinator',
forceReset: true,
services,
vats: {
coordinator: {
bundleSpec: `${bundleBaseUrl}/coordinator-vat.bundle`,
},
keyring: {
bundleSpec: `${bundleBaseUrl}/keyring-vat.bundle`,
},
provider: {
bundleSpec: `${bundleBaseUrl}/provider-vat.bundle`,
},
delegation: {
bundleSpec: `${bundleBaseUrl}/delegation-vat.bundle`,
...(delegationManagerAddress
? { parameters: { delegationManagerAddress } }
: {}),
},
},
};
}
55 changes: 55 additions & 0 deletions packages/eth-wallet/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Address, CaveatType, Hex } from './types.ts';

/**
* The default BIP-44 HD path for Ethereum accounts: m/44'/60'/0'/0/{index}.
*/
export const ETH_HD_PATH_PREFIX = "m/44'/60'/0'/0" as const;

/**
* The root authority hash (no parent delegation).
*/
export const ROOT_AUTHORITY: Hex =
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';

/**
* The default DelegationManager verifying contract address.
* This is a placeholder; actual address depends on deployment.
*/
export const DEFAULT_DELEGATION_MANAGER: Address =
'0x0000000000000000000000000000000000000000';

/**
* Well-known enforcer contract addresses on supported chains.
* These are the MetaMask Delegation Framework deployer-deterministic addresses.
*
* For MVP these are placeholder addresses that will be replaced with actual
* deployments per chain.
*/
export const ENFORCER_ADDRESSES: Record<CaveatType, Address> = {
allowedTargets: '0x0000000000000000000000000000000000000001' as Address,
allowedMethods: '0x0000000000000000000000000000000000000002' as Address,
valueLte: '0x0000000000000000000000000000000000000003' as Address,
erc20TransferAmount: '0x0000000000000000000000000000000000000004' as Address,
limitedCalls: '0x0000000000000000000000000000000000000005' as Address,
timestamp: '0x0000000000000000000000000000000000000006' as Address,
};

/**
* EIP-712 type definitions for the Delegation Framework.
*/
export const DELEGATION_TYPES: Record<
string,
{ name: string; type: string }[]
> = {
Delegation: [
{ name: 'delegate', type: 'address' },
{ name: 'delegator', type: 'address' },
{ name: 'authority', type: 'bytes32' },
{ name: 'caveats', type: 'Caveat[]' },
{ name: 'salt', type: 'uint256' },
],
Caveat: [
{ name: 'enforcer', type: 'address' },
{ name: 'terms', type: 'bytes' },
],
};
69 changes: 69 additions & 0 deletions packages/eth-wallet/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Public API exports for @ocap/eth-wallet

// Constants
export {
DEFAULT_DELEGATION_MANAGER,
DELEGATION_TYPES,
ENFORCER_ADDRESSES,
ETH_HD_PATH_PREFIX,
ROOT_AUTHORITY,
} from './constants.ts';

// Cluster configuration
export { makeWalletClusterConfig } from './cluster-config.ts';
export type { WalletClusterConfigOptions } from './cluster-config.ts';

// Types
export type {
Address,
Action,
Caveat,
CaveatType,
ChainConfig,
CreateDelegationOptions,
Delegation,
DelegationStatus,
Eip712Domain,
Eip712TypedData,
Hex,
SigningRequest,
TransactionRequest,
WalletCapabilities,
} from './types.ts';

export {
ActionStruct,
CaveatStruct,
CaveatTypeValues,
ChainConfigStruct,
CreateDelegationOptionsStruct,
DelegationStatusValues,
DelegationStruct,
Eip712DomainStruct,
Eip712TypedDataStruct,
SigningRequestStruct,
TransactionRequestStruct,
WalletCapabilitiesStruct,
} from './types.ts';

// Caveat utilities (for creating delegations externally)
export {
encodeAllowedTargets,
encodeAllowedMethods,
encodeValueLte,
encodeErc20TransferAmount,
encodeLimitedCalls,
encodeTimestamp,
makeCaveat,
getEnforcerAddress,
} from './lib/caveats.ts';

// Delegation utilities
export {
makeDelegation,
prepareDelegationTypedData,
delegationMatchesAction,
finalizeDelegation,
computeDelegationId,
generateSalt,
} from './lib/delegation.ts';
Loading
Loading