browser-evm-signer 0.1.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/esm/_dnt.polyfills.d.ts +101 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +127 -0
- package/esm/browser.d.ts +13 -0
- package/esm/browser.d.ts.map +1 -0
- package/esm/browser.js +28 -0
- package/esm/config.d.ts +8 -0
- package/esm/config.d.ts.map +1 -0
- package/esm/config.js +100 -0
- package/esm/http-server.d.ts +17 -0
- package/esm/http-server.d.ts.map +1 -0
- package/esm/http-server.js +341 -0
- package/esm/mod.d.ts +11 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +9 -0
- package/esm/package.json +3 -0
- package/esm/pending-store.d.ts +89 -0
- package/esm/pending-store.d.ts.map +1 -0
- package/esm/pending-store.js +162 -0
- package/esm/transport.d.ts +9 -0
- package/esm/transport.d.ts.map +1 -0
- package/esm/transport.js +60 -0
- package/esm/types.d.ts +79 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +1 -0
- package/esm/version.d.ts +2 -0
- package/esm/version.d.ts.map +1 -0
- package/esm/version.js +14 -0
- package/esm/viem-account.d.ts +27 -0
- package/esm/viem-account.d.ts.map +1 -0
- package/esm/viem-account.js +36 -0
- package/esm/wallet-signer.d.ts +106 -0
- package/esm/wallet-signer.d.ts.map +1 -0
- package/esm/wallet-signer.js +178 -0
- package/package.json +46 -0
- package/web/assets/index-C17Xxzpm.js +46 -0
- package/web/assets/index-C5BDQo8n.css +1 -0
- package/web/assets/secp256k1-B4KiQCBs.js +1 -0
- package/web/favicon.svg +10 -0
- package/web/index.html +27 -0
package/esm/transport.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { custom, hexToString } from "viem";
|
|
2
|
+
import { getRpcUrl } from "./config.js";
|
|
3
|
+
/** Create a viem custom transport that routes wallet methods through WalletSigner. */
|
|
4
|
+
export function walletSignerTransport(signer, options) {
|
|
5
|
+
return custom({
|
|
6
|
+
async request({ method, params }) {
|
|
7
|
+
switch (method) {
|
|
8
|
+
case "personal_sign": {
|
|
9
|
+
const [messageHex] = params;
|
|
10
|
+
const message = hexToString(messageHex);
|
|
11
|
+
const { signature } = await signer.signMessage({ message });
|
|
12
|
+
return signature;
|
|
13
|
+
}
|
|
14
|
+
case "eth_sendTransaction": {
|
|
15
|
+
const [tx] = params;
|
|
16
|
+
const sendParams = { to: tx.to };
|
|
17
|
+
if (tx.data)
|
|
18
|
+
sendParams.data = tx.data;
|
|
19
|
+
if (tx.value)
|
|
20
|
+
sendParams.value = BigInt(tx.value).toString();
|
|
21
|
+
if (tx.gas)
|
|
22
|
+
sendParams.gasLimit = BigInt(tx.gas).toString();
|
|
23
|
+
if (tx.maxFeePerGas)
|
|
24
|
+
sendParams.maxFeePerGas = BigInt(tx.maxFeePerGas).toString();
|
|
25
|
+
if (tx.maxPriorityFeePerGas) {
|
|
26
|
+
sendParams.maxPriorityFeePerGas = BigInt(tx.maxPriorityFeePerGas).toString();
|
|
27
|
+
}
|
|
28
|
+
if (tx.chainId)
|
|
29
|
+
sendParams.chainId = Number(BigInt(tx.chainId));
|
|
30
|
+
const { txHash } = await signer.sendTransaction(sendParams);
|
|
31
|
+
return txHash;
|
|
32
|
+
}
|
|
33
|
+
case "eth_signTypedData_v4": {
|
|
34
|
+
const [, typedDataJson] = params;
|
|
35
|
+
const { domain, types, primaryType, message } = JSON.parse(typedDataJson);
|
|
36
|
+
const { signature } = await signer.signTypedData({ domain, types, primaryType, message });
|
|
37
|
+
return signature;
|
|
38
|
+
}
|
|
39
|
+
case "eth_chainId":
|
|
40
|
+
return `0x${signer.defaultChainId.toString(16)}`;
|
|
41
|
+
default: {
|
|
42
|
+
const rpcUrl = options?.rpcUrl ?? getRpcUrl(signer.defaultChainId);
|
|
43
|
+
if (!rpcUrl) {
|
|
44
|
+
throw new Error(`No RPC URL for chain ${signer.defaultChainId}. Provide rpcUrl in transport options.`);
|
|
45
|
+
}
|
|
46
|
+
const resp = await fetch(rpcUrl, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "Content-Type": "application/json" },
|
|
49
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params: params ?? [] }),
|
|
50
|
+
});
|
|
51
|
+
const json = await resp.json();
|
|
52
|
+
if (json.error) {
|
|
53
|
+
throw new Error(json.error.message ?? JSON.stringify(json.error));
|
|
54
|
+
}
|
|
55
|
+
return json.result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}, { retryCount: 0 });
|
|
60
|
+
}
|
package/esm/types.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface ChainConfig {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
rpcUrl: string;
|
|
5
|
+
nativeCurrency: {
|
|
6
|
+
name: string;
|
|
7
|
+
symbol: string;
|
|
8
|
+
decimals: number;
|
|
9
|
+
};
|
|
10
|
+
blockExplorer?: string;
|
|
11
|
+
}
|
|
12
|
+
export type RequestType = "connect" | "send_transaction" | "sign_message" | "sign_typed_data";
|
|
13
|
+
export interface BaseRequest {
|
|
14
|
+
id: string;
|
|
15
|
+
type: RequestType;
|
|
16
|
+
chainId?: number;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ConnectRequest extends BaseRequest {
|
|
20
|
+
type: "connect";
|
|
21
|
+
address?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SendTransactionRequest extends BaseRequest {
|
|
24
|
+
type: "send_transaction";
|
|
25
|
+
to: string;
|
|
26
|
+
value?: string;
|
|
27
|
+
data?: string;
|
|
28
|
+
gasLimit?: string;
|
|
29
|
+
maxFeePerGas?: string;
|
|
30
|
+
maxPriorityFeePerGas?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SignMessageRequest extends BaseRequest {
|
|
33
|
+
type: "sign_message";
|
|
34
|
+
message: string;
|
|
35
|
+
address?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface SignTypedDataRequest extends BaseRequest {
|
|
38
|
+
type: "sign_typed_data";
|
|
39
|
+
domain: TypedDataDomain;
|
|
40
|
+
types: Record<string, TypedDataField[]>;
|
|
41
|
+
primaryType: string;
|
|
42
|
+
message: Record<string, unknown>;
|
|
43
|
+
address?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface TypedDataDomain {
|
|
46
|
+
name?: string;
|
|
47
|
+
version?: string;
|
|
48
|
+
chainId?: number;
|
|
49
|
+
verifyingContract?: string;
|
|
50
|
+
salt?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface TypedDataField {
|
|
53
|
+
name: string;
|
|
54
|
+
type: string;
|
|
55
|
+
}
|
|
56
|
+
export type PendingRequest = ConnectRequest | SendTransactionRequest | SignMessageRequest | SignTypedDataRequest;
|
|
57
|
+
export interface SuccessResult {
|
|
58
|
+
success: true;
|
|
59
|
+
result: string;
|
|
60
|
+
}
|
|
61
|
+
export interface ErrorResult {
|
|
62
|
+
success: false;
|
|
63
|
+
error: string;
|
|
64
|
+
}
|
|
65
|
+
export type RequestResult = SuccessResult | ErrorResult;
|
|
66
|
+
export interface PendingEntry<T extends PendingRequest = PendingRequest> {
|
|
67
|
+
request: T;
|
|
68
|
+
resolve: (result: RequestResult) => void;
|
|
69
|
+
reject: (error: Error) => void;
|
|
70
|
+
}
|
|
71
|
+
export interface PendingApiResponse {
|
|
72
|
+
request: PendingRequest;
|
|
73
|
+
}
|
|
74
|
+
export interface CompleteApiRequest {
|
|
75
|
+
success: boolean;
|
|
76
|
+
result?: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/version.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,OAAO,QAAe,CAAC"}
|
package/esm/version.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
function getVersion() {
|
|
5
|
+
try {
|
|
6
|
+
const dir = dirname(fileURLToPath(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
|
|
7
|
+
const pkg = JSON.parse(readFileSync(join(dir, "..", "package.json"), "utf-8"));
|
|
8
|
+
return pkg.version;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return "dev";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const VERSION = getVersion();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Address, CustomSource, CustomTransport } from "viem";
|
|
2
|
+
import type { WalletSigner } from "./wallet-signer.js";
|
|
3
|
+
import { type WalletSignerTransportOptions } from "./transport.js";
|
|
4
|
+
/** A viem-compatible hybrid account: type "json-rpc" for transport-routed sends, with signMessage/signTypedData. */
|
|
5
|
+
export interface ViemBrowserAccount {
|
|
6
|
+
address: Address;
|
|
7
|
+
type: "json-rpc";
|
|
8
|
+
signMessage: CustomSource["signMessage"];
|
|
9
|
+
signTypedData: CustomSource["signTypedData"];
|
|
10
|
+
signTransaction: never;
|
|
11
|
+
}
|
|
12
|
+
export interface ConnectWalletViemOptions extends WalletSignerTransportOptions {
|
|
13
|
+
/** Pre-connected address — skips the connectWallet() browser prompt if provided. */
|
|
14
|
+
address?: Address;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Connect to a browser wallet and return a viem-compatible hybrid account + custom transport.
|
|
18
|
+
*
|
|
19
|
+
* The account uses type "json-rpc" so viem routes eth_sendTransaction through the transport
|
|
20
|
+
* (which forwards to the browser wallet), while signMessage/signTypedData are available for
|
|
21
|
+
* direct calls from application code.
|
|
22
|
+
*/
|
|
23
|
+
export declare function connectWalletViem(signer: WalletSigner, options?: ConnectWalletViemOptions): Promise<{
|
|
24
|
+
account: ViemBrowserAccount;
|
|
25
|
+
transport: CustomTransport;
|
|
26
|
+
}>;
|
|
27
|
+
//# sourceMappingURL=viem-account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viem-account.d.ts","sourceRoot":"","sources":["../src/viem-account.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAO,MAAM,MAAM,CAAC;AAExE,OAAO,KAAK,EAAuB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAyB,KAAK,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAE1F,oHAAoH;AACpH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;IACzC,aAAa,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IAC7C,eAAe,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,wBAAyB,SAAQ,4BAA4B;IAC5E,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC;IAAE,OAAO,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,eAAe,CAAA;CAAE,CAAC,CA8BtE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { walletSignerTransport } from "./transport.js";
|
|
2
|
+
/**
|
|
3
|
+
* Connect to a browser wallet and return a viem-compatible hybrid account + custom transport.
|
|
4
|
+
*
|
|
5
|
+
* The account uses type "json-rpc" so viem routes eth_sendTransaction through the transport
|
|
6
|
+
* (which forwards to the browser wallet), while signMessage/signTypedData are available for
|
|
7
|
+
* direct calls from application code.
|
|
8
|
+
*/
|
|
9
|
+
export async function connectWalletViem(signer, options) {
|
|
10
|
+
const address = (options?.address ?? (await signer.connectWallet()).address);
|
|
11
|
+
const signMessage = async ({ message }) => {
|
|
12
|
+
let msg;
|
|
13
|
+
if (typeof message === "string") {
|
|
14
|
+
msg = message;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
msg = typeof message.raw === "string" ? message.raw : new TextDecoder().decode(message.raw);
|
|
18
|
+
}
|
|
19
|
+
const { signature } = await signer.signMessage({ message: msg });
|
|
20
|
+
return signature;
|
|
21
|
+
};
|
|
22
|
+
// viem's CustomSource["signTypedData"] uses heavily generic conditional types (TypedDataDefinition)
|
|
23
|
+
// that TypeScript can't prove assignable to any concrete type inside the generic callback.
|
|
24
|
+
const signTypedData = async (params) => {
|
|
25
|
+
const { signature } = await signer.signTypedData(params);
|
|
26
|
+
return signature;
|
|
27
|
+
};
|
|
28
|
+
const account = {
|
|
29
|
+
address,
|
|
30
|
+
type: "json-rpc",
|
|
31
|
+
signMessage,
|
|
32
|
+
signTypedData,
|
|
33
|
+
signTransaction: undefined,
|
|
34
|
+
};
|
|
35
|
+
return { account, transport: walletSignerTransport(signer, options) };
|
|
36
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PendingStore } from "./pending-store.js";
|
|
2
|
+
import type { TypedDataDomain, TypedDataField } from "./types.js";
|
|
3
|
+
export interface WalletSignerOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
defaultChainId?: number;
|
|
6
|
+
/** Control browser opening: true (default) = auto-open, false = don't open, function = custom handler */
|
|
7
|
+
openBrowser?: boolean | ((url: string) => void | Promise<void>);
|
|
8
|
+
}
|
|
9
|
+
export interface SendTransactionParams {
|
|
10
|
+
to: string;
|
|
11
|
+
value?: string;
|
|
12
|
+
data?: string;
|
|
13
|
+
chainId?: number;
|
|
14
|
+
gasLimit?: string;
|
|
15
|
+
maxFeePerGas?: string;
|
|
16
|
+
maxPriorityFeePerGas?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SignMessageParams {
|
|
19
|
+
message: string;
|
|
20
|
+
address?: string;
|
|
21
|
+
chainId?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SignTypedDataParams {
|
|
24
|
+
domain: TypedDataDomain;
|
|
25
|
+
types: Record<string, TypedDataField[]>;
|
|
26
|
+
primaryType: string;
|
|
27
|
+
message: Record<string, unknown>;
|
|
28
|
+
address?: string;
|
|
29
|
+
chainId?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ConnectResult {
|
|
32
|
+
address: string;
|
|
33
|
+
approvalUrl: string;
|
|
34
|
+
}
|
|
35
|
+
export interface TransactionResult {
|
|
36
|
+
txHash: string;
|
|
37
|
+
approvalUrl: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SignResult {
|
|
40
|
+
signature: string;
|
|
41
|
+
approvalUrl: string;
|
|
42
|
+
}
|
|
43
|
+
export interface BalanceResult {
|
|
44
|
+
balance: string;
|
|
45
|
+
wei: string;
|
|
46
|
+
symbol: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Programmatic interface to the wallet signer.
|
|
50
|
+
* Each instance owns its own PendingStore and HTTP server.
|
|
51
|
+
*/
|
|
52
|
+
export declare class WalletSigner {
|
|
53
|
+
private _port;
|
|
54
|
+
private _defaultChainId;
|
|
55
|
+
private _pendingStore;
|
|
56
|
+
private _openBrowser;
|
|
57
|
+
private _httpServer;
|
|
58
|
+
constructor(options?: WalletSignerOptions);
|
|
59
|
+
/** The PendingStore owned by this signer */
|
|
60
|
+
get pendingStore(): PendingStore;
|
|
61
|
+
/** The configured default chain ID */
|
|
62
|
+
get defaultChainId(): number;
|
|
63
|
+
/** The HTTP server port, or null if not yet started */
|
|
64
|
+
get port(): number | null;
|
|
65
|
+
/**
|
|
66
|
+
* Start the HTTP server explicitly. Called automatically on first signing call.
|
|
67
|
+
* Returns the port the server is listening on.
|
|
68
|
+
*/
|
|
69
|
+
start(): Promise<number>;
|
|
70
|
+
/**
|
|
71
|
+
* Connect to a browser wallet and get the wallet address.
|
|
72
|
+
* Opens a browser window for user approval.
|
|
73
|
+
*/
|
|
74
|
+
connectWallet(options?: {
|
|
75
|
+
chainId?: number;
|
|
76
|
+
address?: string;
|
|
77
|
+
}): Promise<ConnectResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Send a transaction via the connected browser wallet.
|
|
80
|
+
* Opens a browser window for user approval.
|
|
81
|
+
*/
|
|
82
|
+
sendTransaction(params: SendTransactionParams): Promise<TransactionResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Sign a message using personal_sign.
|
|
85
|
+
* Opens a browser window for user approval.
|
|
86
|
+
*/
|
|
87
|
+
signMessage(params: SignMessageParams): Promise<SignResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Sign EIP-712 typed data.
|
|
90
|
+
* Opens a browser window for user approval.
|
|
91
|
+
*/
|
|
92
|
+
signTypedData(params: SignTypedDataParams): Promise<SignResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Get the native token balance of an address.
|
|
95
|
+
* Does not require browser interaction — reads directly from the blockchain.
|
|
96
|
+
*/
|
|
97
|
+
getBalance(params: {
|
|
98
|
+
address: string;
|
|
99
|
+
chainId?: number;
|
|
100
|
+
}): Promise<BalanceResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Shut down the HTTP server and cancel all pending requests.
|
|
103
|
+
*/
|
|
104
|
+
shutdown(): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=wallet-signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet-signer.d.ts","sourceRoot":"","sources":["../src/wallet-signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createPublicClient, formatEther, http } from "viem";
|
|
2
|
+
import { PendingStore } from "./pending-store.js";
|
|
3
|
+
import { createHttpServer } from "./http-server.js";
|
|
4
|
+
import { buildConnectUrl, buildSignUrl, openBrowser } from "./browser.js";
|
|
5
|
+
import { CHAINS, getDefaultChainId, getPort, getRpcUrl } from "./config.js";
|
|
6
|
+
/**
|
|
7
|
+
* Programmatic interface to the wallet signer.
|
|
8
|
+
* Each instance owns its own PendingStore and HTTP server.
|
|
9
|
+
*/
|
|
10
|
+
export class WalletSigner {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
Object.defineProperty(this, "_port", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: void 0
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(this, "_defaultChainId", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
writable: true,
|
|
22
|
+
value: void 0
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(this, "_pendingStore", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: void 0
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(this, "_openBrowser", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: void 0
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(this, "_httpServer", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: null
|
|
41
|
+
});
|
|
42
|
+
this._port = options?.port ?? getPort();
|
|
43
|
+
this._defaultChainId = options?.defaultChainId ?? getDefaultChainId();
|
|
44
|
+
this._pendingStore = new PendingStore();
|
|
45
|
+
const ob = options?.openBrowser ?? true;
|
|
46
|
+
if (typeof ob === "function") {
|
|
47
|
+
this._openBrowser = ob;
|
|
48
|
+
}
|
|
49
|
+
else if (ob) {
|
|
50
|
+
this._openBrowser = openBrowser;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this._openBrowser = () => { };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The PendingStore owned by this signer */
|
|
57
|
+
get pendingStore() {
|
|
58
|
+
return this._pendingStore;
|
|
59
|
+
}
|
|
60
|
+
/** The configured default chain ID */
|
|
61
|
+
get defaultChainId() {
|
|
62
|
+
return this._defaultChainId;
|
|
63
|
+
}
|
|
64
|
+
/** The HTTP server port, or null if not yet started */
|
|
65
|
+
get port() {
|
|
66
|
+
return this._httpServer?.port ?? null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start the HTTP server explicitly. Called automatically on first signing call.
|
|
70
|
+
* Returns the port the server is listening on.
|
|
71
|
+
*/
|
|
72
|
+
async start() {
|
|
73
|
+
if (this._httpServer) {
|
|
74
|
+
return this._httpServer.port;
|
|
75
|
+
}
|
|
76
|
+
this._httpServer = await createHttpServer(this._pendingStore, this._port);
|
|
77
|
+
return this._httpServer.port;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Connect to a browser wallet and get the wallet address.
|
|
81
|
+
* Opens a browser window for user approval.
|
|
82
|
+
*/
|
|
83
|
+
async connectWallet(options) {
|
|
84
|
+
const chainId = options?.chainId ?? this._defaultChainId;
|
|
85
|
+
const port = await this.start();
|
|
86
|
+
const { id, promise } = this._pendingStore.createConnectRequest({ chainId, address: options?.address });
|
|
87
|
+
const approvalUrl = buildConnectUrl(port, id);
|
|
88
|
+
await this._openBrowser(approvalUrl);
|
|
89
|
+
const result = await promise;
|
|
90
|
+
if (!result.success)
|
|
91
|
+
throw new Error(result.error);
|
|
92
|
+
return { address: result.result, approvalUrl };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Send a transaction via the connected browser wallet.
|
|
96
|
+
* Opens a browser window for user approval.
|
|
97
|
+
*/
|
|
98
|
+
async sendTransaction(params) {
|
|
99
|
+
const port = await this.start();
|
|
100
|
+
const { id, promise } = this._pendingStore.createSendTransactionRequest({
|
|
101
|
+
...params,
|
|
102
|
+
chainId: params.chainId ?? this._defaultChainId,
|
|
103
|
+
});
|
|
104
|
+
const approvalUrl = buildSignUrl(port, id);
|
|
105
|
+
await this._openBrowser(approvalUrl);
|
|
106
|
+
const result = await promise;
|
|
107
|
+
if (!result.success)
|
|
108
|
+
throw new Error(result.error);
|
|
109
|
+
return { txHash: result.result, approvalUrl };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Sign a message using personal_sign.
|
|
113
|
+
* Opens a browser window for user approval.
|
|
114
|
+
*/
|
|
115
|
+
async signMessage(params) {
|
|
116
|
+
const port = await this.start();
|
|
117
|
+
const { id, promise } = this._pendingStore.createSignMessageRequest({
|
|
118
|
+
...params,
|
|
119
|
+
chainId: params.chainId ?? this._defaultChainId,
|
|
120
|
+
});
|
|
121
|
+
const approvalUrl = buildSignUrl(port, id);
|
|
122
|
+
await this._openBrowser(approvalUrl);
|
|
123
|
+
const result = await promise;
|
|
124
|
+
if (!result.success)
|
|
125
|
+
throw new Error(result.error);
|
|
126
|
+
return { signature: result.result, approvalUrl };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Sign EIP-712 typed data.
|
|
130
|
+
* Opens a browser window for user approval.
|
|
131
|
+
*/
|
|
132
|
+
async signTypedData(params) {
|
|
133
|
+
const port = await this.start();
|
|
134
|
+
const { id, promise } = this._pendingStore.createSignTypedDataRequest({
|
|
135
|
+
...params,
|
|
136
|
+
chainId: params.chainId ?? this._defaultChainId,
|
|
137
|
+
});
|
|
138
|
+
const approvalUrl = buildSignUrl(port, id);
|
|
139
|
+
await this._openBrowser(approvalUrl);
|
|
140
|
+
const result = await promise;
|
|
141
|
+
if (!result.success)
|
|
142
|
+
throw new Error(result.error);
|
|
143
|
+
return { signature: result.result, approvalUrl };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the native token balance of an address.
|
|
147
|
+
* Does not require browser interaction — reads directly from the blockchain.
|
|
148
|
+
*/
|
|
149
|
+
async getBalance(params) {
|
|
150
|
+
const chainId = params.chainId ?? this._defaultChainId;
|
|
151
|
+
const rpcUrl = getRpcUrl(chainId);
|
|
152
|
+
if (!rpcUrl)
|
|
153
|
+
throw new Error(`Unknown chain ID: ${chainId}. No RPC URL configured.`);
|
|
154
|
+
const client = createPublicClient({ transport: http(rpcUrl) });
|
|
155
|
+
const balance = await client.getBalance({
|
|
156
|
+
address: params.address,
|
|
157
|
+
});
|
|
158
|
+
const chain = CHAINS[chainId];
|
|
159
|
+
const symbol = chain?.nativeCurrency.symbol || "ETH";
|
|
160
|
+
return {
|
|
161
|
+
balance: formatEther(balance),
|
|
162
|
+
wei: balance.toString(),
|
|
163
|
+
symbol,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Shut down the HTTP server and cancel all pending requests.
|
|
168
|
+
*/
|
|
169
|
+
async shutdown() {
|
|
170
|
+
if (this._httpServer) {
|
|
171
|
+
await this._httpServer.stop();
|
|
172
|
+
this._httpServer = null;
|
|
173
|
+
}
|
|
174
|
+
for (const id of this._pendingStore.getPendingIds()) {
|
|
175
|
+
this._pendingStore.cancel(id, "Wallet signer shutting down");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "browser-evm-signer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Route EVM transactions to browser wallets for signing — standalone library, no MCP dependency",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wallet",
|
|
7
|
+
"ethereum",
|
|
8
|
+
"evm",
|
|
9
|
+
"metamask",
|
|
10
|
+
"eip-6963",
|
|
11
|
+
"browser-wallet",
|
|
12
|
+
"signing",
|
|
13
|
+
"blockchain",
|
|
14
|
+
"web3",
|
|
15
|
+
"non-custodial",
|
|
16
|
+
"transaction-signing",
|
|
17
|
+
"viem"
|
|
18
|
+
],
|
|
19
|
+
"author": "Nikolay Bryskin",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/nikicat/mcp-wallet-signer"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"module": "./esm/mod.js",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./esm/mod.d.ts",
|
|
30
|
+
"default": "./esm/mod.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"open": "10.1.0"
|
|
41
|
+
},
|
|
42
|
+
"_generatedBy": "dnt@dev",
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"viem": "2.46.2"
|
|
45
|
+
}
|
|
46
|
+
}
|