@farmslot/adapter-web 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +24 -1
- package/dist/web-dapp/cleanup.d.mts +15 -0
- package/dist/web-dapp/index.d.mts +123 -0
- package/dist/web-dapp/inject.d.mts +12 -0
- package/dist/web-dapp/launch.d.mts +56 -0
- package/dist/web-dapp/lib/blocked-hosts.d.mts +3 -0
- package/dist/web-dapp/lib/cdp-client.d.mts +8 -0
- package/dist/web-dapp/lib/display.d.mts +26 -0
- package/dist/web-dapp/lib/hud.d.mts +14 -0
- package/dist/web-dapp/lib/paths.d.mts +11 -0
- package/dist/web-dapp/lib/policy-fence.d.mts +18 -0
- package/dist/web-dapp/lib/policy.d.mts +4 -0
- package/dist/web-dapp/lib/processes.d.mts +57 -0
- package/dist/web-dapp/lib/readiness.d.mts +85 -0
- package/dist/web-dapp/lib/runtime.d.mts +29 -0
- package/dist/web-dapp/lib/signers.d.mts +13 -0
- package/dist/web-dapp/lib/viem.d.mts +1 -0
- package/dist/web-dapp/stop.d.mts +2 -0
- package/dist/web-dapp/verify.d.mts +2 -0
- package/dist/web-dapp/wallet-host.d.mts +2 -0
- package/package.json +17 -10
- package/src/web-dapp/cleanup.mjs +74 -0
- package/src/web-dapp/index.mjs +590 -0
- package/src/web-dapp/inject.mjs +100 -0
- package/src/web-dapp/launch.mjs +806 -0
- package/src/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/src/web-dapp/lib/cdp-client.mjs +86 -0
- package/src/web-dapp/lib/display.mjs +169 -0
- package/src/web-dapp/lib/hud.mjs +156 -0
- package/src/web-dapp/lib/paths.mjs +55 -0
- package/src/web-dapp/lib/policy-fence.mjs +128 -0
- package/src/web-dapp/lib/policy.mjs +108 -0
- package/src/web-dapp/lib/processes.mjs +215 -0
- package/src/web-dapp/lib/readiness.mjs +438 -0
- package/src/web-dapp/lib/runtime.mjs +195 -0
- package/src/web-dapp/lib/signers.mjs +105 -0
- package/src/web-dapp/lib/viem.mjs +26 -0
- package/src/web-dapp/stop.mjs +44 -0
- package/src/web-dapp/verify.mjs +68 -0
- package/src/web-dapp/wallet-host.mjs +728 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Signer hooks for the extension signer (signer=extension). The wallet that
|
|
2
|
+
// drives a real browser extension is the host product's (MetaMask's, for
|
|
3
|
+
// mm-harness), so web-dapp's launcher, wallet host and readiness call it
|
|
4
|
+
// through hooks instead of knowing it. signer=injected needs none: the strict
|
|
5
|
+
// test wallet in @farmslot/adapter-web/dapp is generic.
|
|
6
|
+
//
|
|
7
|
+
// A signer module is an ESM file exporting `signers`, an object keyed by signer
|
|
8
|
+
// mode (`extension`, and `injected` for its identity):
|
|
9
|
+
//
|
|
10
|
+
// export const signers = { extension: {
|
|
11
|
+
// // Before the browser starts: load the extension and seed the profile.
|
|
12
|
+
// // Returns { browserArgs, secrets, state, afterBrowserStart? }:
|
|
13
|
+
// // browserArgs extra browser arguments (--load-extension=...)
|
|
14
|
+
// // secrets files holding key material, removed when the launch ends
|
|
15
|
+
// // state recorded under `extension` in browser.json
|
|
16
|
+
// // afterBrowserStart(ctx) runs once the browser answers on CDP; ctx is
|
|
17
|
+
// // { cdpPort, launchMethod, windowBounds, withBrowserClient,
|
|
18
|
+
// // openBackgroundWindow(url), log, trackSecret }. May return { state, hostArgs }:
|
|
19
|
+
// // state merges into the recorded state, hostArgs go to the wallet host.
|
|
20
|
+
// // `trackSecret(file)` registers a key-material file at once, so it is removed
|
|
21
|
+
// // even when prepareProfile throws part-way; `secrets` may list more.
|
|
22
|
+
// async prepareProfile({ target, runtime, profile, account, fixture, env,
|
|
23
|
+
// freshProfile, log, writePrivateFile, trackSecret }) {},
|
|
24
|
+
// // Required with prepareProfile: without it the wallet host would close the
|
|
25
|
+
// // wallet's own tabs. In the wallet host: judge the wallet's own confirmation
|
|
26
|
+
// // surfaces. Returns
|
|
27
|
+
// // { isWalletSurfaceUrl(url), observe(targetId, url) }; `args` are the wallet
|
|
28
|
+
// // host's flags (e.g. args['extension-id']). `focus`, when the host observes
|
|
29
|
+
// // focus, has observe(surface, productName) to record a window that took it.
|
|
30
|
+
// confirm({ args, appendEntry, say, focus }) {},
|
|
31
|
+
// // In readiness: the signer's own checks (build them with `check` from
|
|
32
|
+
// // readiness.mjs), `required` when signer=extension is requested.
|
|
33
|
+
// async readinessChecks({ target, env, required }) {},
|
|
34
|
+
// } };
|
|
35
|
+
//
|
|
36
|
+
// `signers.injected: { identity }` is the EIP-6963 identity
|
|
37
|
+
// ({ info: { uuid, name, icon, rdns }, isMetaMask }) the injected strict wallet
|
|
38
|
+
// presents, for an app that only connects to a known wallet. Without it the
|
|
39
|
+
// strict wallet presents a generic identity.
|
|
40
|
+
// A configured module makes signer=extension the default (`defaultSigner`), so
|
|
41
|
+
// a host whose module only sets an identity (`signers = { injected: { identity } }`)
|
|
42
|
+
// sets the signer explicitly (--signer injected or TERMINAL_SIGNER=injected).
|
|
43
|
+
//
|
|
44
|
+
// The module path is the one source of these hooks: every process (readiness,
|
|
45
|
+
// launch, verify, the wallet host) loads it through RECIPE_WEB_DAPP_SIGNER_MODULE
|
|
46
|
+
// or --signer-module on launch and verify, or `signerModule` of
|
|
47
|
+
// createWebDappAdapter. With no signer requested, the signer is extension when
|
|
48
|
+
// a module is configured and injected otherwise (`defaultSigner`).
|
|
49
|
+
|
|
50
|
+
import path from 'node:path';
|
|
51
|
+
import { pathToFileURL } from 'node:url';
|
|
52
|
+
|
|
53
|
+
export const SIGNER_MODULE_ENV = 'RECIPE_WEB_DAPP_SIGNER_MODULE';
|
|
54
|
+
|
|
55
|
+
// The signer a run uses when none is requested: extension when a signer module
|
|
56
|
+
// is configured (by path or RECIPE_WEB_DAPP_SIGNER_MODULE), else the injected
|
|
57
|
+
// strict wallet, which needs none.
|
|
58
|
+
export function defaultSigner(signerModule, env = process.env) {
|
|
59
|
+
return signerModule || env[SIGNER_MODULE_ENV] ? 'extension' : 'injected';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function loadSigners(env = process.env) {
|
|
63
|
+
const file = env[SIGNER_MODULE_ENV];
|
|
64
|
+
if (!file) return {};
|
|
65
|
+
const resolved = path.resolve(file);
|
|
66
|
+
const loaded = await import(pathToFileURL(resolved).href);
|
|
67
|
+
const signers = loaded.signers ?? loaded.default;
|
|
68
|
+
if (!signers || typeof signers !== 'object') {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`web-dapp signer module ${resolved} must export \`signers\`, an object keyed by signer mode.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return signers;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The injected strict wallet's EIP-6963 identity: the signer module's
|
|
77
|
+
// `injected.identity` when it gives one (a host whose app only connects to a
|
|
78
|
+
// known wallet, e.g. mm-harness presenting MetaMask so the app connects to it
|
|
79
|
+
// the way it connects to the extension), else a generic one.
|
|
80
|
+
export const GENERIC_INJECTED_IDENTITY = Object.freeze({
|
|
81
|
+
info: Object.freeze({
|
|
82
|
+
uuid: '5d1e5b4e-e2e0-4c3f-9a1d-000000000001',
|
|
83
|
+
name: 'Strict test wallet',
|
|
84
|
+
icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
|
|
85
|
+
rdns: 'io.farmslot.strict-wallet',
|
|
86
|
+
}),
|
|
87
|
+
isMetaMask: false,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export function injectedWalletIdentity(signers = {}) {
|
|
91
|
+
const identity = signers.injected?.identity;
|
|
92
|
+
if (identity === undefined) return GENERIC_INJECTED_IDENTITY;
|
|
93
|
+
const info = identity?.info;
|
|
94
|
+
if (
|
|
95
|
+
!info ||
|
|
96
|
+
typeof info.uuid !== 'string' ||
|
|
97
|
+
typeof info.name !== 'string' ||
|
|
98
|
+
typeof info.rdns !== 'string'
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'web-dapp signer module: injected.identity needs info.uuid, info.name and info.rdns strings.',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return identity;
|
|
105
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// viem is the account derivation the wallet fixture needs (mnemonic and private
|
|
2
|
+
// key accounts). adapter-web does not depend on it: it is resolved from the app
|
|
3
|
+
// checkout under test (the Next.js app carries viem), else from where
|
|
4
|
+
// adapter-web is installed, else from the working directory.
|
|
5
|
+
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
export function loadViemAccounts(projectRoot) {
|
|
10
|
+
const bases = [
|
|
11
|
+
...(projectRoot ? [path.join(path.resolve(projectRoot), 'package.json')] : []),
|
|
12
|
+
import.meta.url,
|
|
13
|
+
path.join(process.cwd(), 'package.json'),
|
|
14
|
+
];
|
|
15
|
+
for (const base of bases) {
|
|
16
|
+
try {
|
|
17
|
+
return createRequire(base)('viem/accounts');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error?.code !== 'MODULE_NOT_FOUND') throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw new Error(
|
|
23
|
+
'viem is not installed where web-dapp can see it (the app checkout, then adapter-web).\n' +
|
|
24
|
+
'Next: install viem in the app checkout under test.',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// stop.mjs — stop the web-dapp slot browser, wallet host and console collector, verify the CDP port
|
|
3
|
+
// is free. Idempotent: nothing running is success.
|
|
4
|
+
//
|
|
5
|
+
// Inputs: --target <checkout> [--cdp-port <port>] [--json]
|
|
6
|
+
// Outputs: JSON summary on stdout. Exit 0 stopped/nothing to stop; 1 port still owned.
|
|
7
|
+
// Never touches: the Next.js dev server (Farmslot owns it), browser profiles.
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
|
|
13
|
+
import { webDappPolicy } from './lib/runtime.mjs';
|
|
14
|
+
|
|
15
|
+
const usage = 'Usage: stop.mjs --target <checkout> [--cdp-port <port>] [--json]';
|
|
16
|
+
|
|
17
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
18
|
+
const argv = process.argv.slice(2);
|
|
19
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
20
|
+
process.stdout.write(`${usage}\n`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
const value = (flag) => {
|
|
24
|
+
const index = argv.indexOf(flag);
|
|
25
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
26
|
+
};
|
|
27
|
+
const target = value('--target') ?? process.cwd();
|
|
28
|
+
const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
29
|
+
try {
|
|
30
|
+
const log = (message) =>
|
|
31
|
+
process.stderr.write(`[${webDappPolicy().adapterId}/stop] ${message}\n`);
|
|
32
|
+
const result = await stopWebDappBrowser(target, {
|
|
33
|
+
cdpPort: rawPort ? Number(rawPort) : undefined,
|
|
34
|
+
log,
|
|
35
|
+
});
|
|
36
|
+
const collector = await stopConsoleCollector(target, { log });
|
|
37
|
+
process.stdout.write(
|
|
38
|
+
`${JSON.stringify({ status: 'pass', ...result, consoleCollector: collector })}\n`,
|
|
39
|
+
);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
process.stdout.write(`${JSON.stringify({ status: 'fail', error: error.message })}\n`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// verify.mjs — `mm-harness verify --adapter <web-dapp adapter>`: read-only readiness of a
|
|
3
|
+
// web-dapp slot (checkout, deps, dev server, forced testnet, fixture,
|
|
4
|
+
// browser start probe, the extension signer's own checks, CDP port).
|
|
5
|
+
//
|
|
6
|
+
// Inputs: --target <checkout> [--cdp-port <port>] [--watcher-port <app port>]
|
|
7
|
+
// [--signer extension|injected] [--signer-module <path>] [--account <name>] [--json]
|
|
8
|
+
// Outputs: readiness JSON (or a human summary) on stdout. Exit 0 ready; 1 not ready.
|
|
9
|
+
// Never launches the slot browser or the dev server.
|
|
10
|
+
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
import { webDappReadiness } from './lib/readiness.mjs';
|
|
15
|
+
import { webDappPolicy } from './lib/runtime.mjs';
|
|
16
|
+
import { defaultSigner, loadSigners, SIGNER_MODULE_ENV } from './lib/signers.mjs';
|
|
17
|
+
import { resolveBrowser } from './launch.mjs';
|
|
18
|
+
|
|
19
|
+
const usage =
|
|
20
|
+
'Usage: verify.mjs --target <checkout> [--cdp-port <port>] [--watcher-port <app port>] [--signer extension|injected] [--signer-module <path>] [--account <name>] [--json]';
|
|
21
|
+
|
|
22
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
23
|
+
const argv = process.argv.slice(2);
|
|
24
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
25
|
+
process.stdout.write(`${usage}\n`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
const value = (flag) => {
|
|
29
|
+
const index = argv.indexOf(flag);
|
|
30
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
31
|
+
};
|
|
32
|
+
const num = (raw) => (raw && /^\d+$/u.test(raw) ? Number(raw) : undefined);
|
|
33
|
+
const policy = webDappPolicy();
|
|
34
|
+
const signerModule = value('--signer-module');
|
|
35
|
+
const signers = await loadSigners({
|
|
36
|
+
...process.env,
|
|
37
|
+
...(signerModule ? { [SIGNER_MODULE_ENV]: signerModule } : {}),
|
|
38
|
+
});
|
|
39
|
+
const report = await webDappReadiness({
|
|
40
|
+
policy,
|
|
41
|
+
signers,
|
|
42
|
+
target: path.resolve(value('--target') ?? process.cwd()),
|
|
43
|
+
appPort: num(
|
|
44
|
+
value('--watcher-port') ?? process.env.TERMINAL_APP_PORT ?? process.env.WATCHER_PORT,
|
|
45
|
+
),
|
|
46
|
+
cdpPort: num(value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT),
|
|
47
|
+
signer: value('--signer') ?? process.env.TERMINAL_SIGNER ?? defaultSigner(signerModule),
|
|
48
|
+
account: value('--account') ?? process.env.TERMINAL_ACCOUNT ?? 'dev1',
|
|
49
|
+
probeBrowser: resolveBrowser,
|
|
50
|
+
});
|
|
51
|
+
if (argv.includes('--json')) {
|
|
52
|
+
process.stdout.write(
|
|
53
|
+
`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter: policy.adapterId, ...report }, null, 2)}\n`,
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
for (const check of report.checks) {
|
|
57
|
+
const mark =
|
|
58
|
+
check.status === 'pass' ? '✓' : check.status === 'warn' || !check.required ? '·' : '✗';
|
|
59
|
+
process.stdout.write(`${mark} ${check.id}: ${check.detail}\n`);
|
|
60
|
+
}
|
|
61
|
+
process.stdout.write(
|
|
62
|
+
report.status === 'pass'
|
|
63
|
+
? `${policy.adapterId} slot ready\n`
|
|
64
|
+
: `not ready: ${report.failed.join(', ')}\n`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
process.exit(report.status === 'pass' ? 0 : 1);
|
|
68
|
+
}
|