@droplinked_inc/wallet-connection 0.1.1
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 +16 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/THREAT_MODEL.md +180 -0
- package/dist/chains.d.ts +25 -0
- package/dist/chains.d.ts.map +1 -0
- package/dist/chains.js +153 -0
- package/dist/chains.js.map +1 -0
- package/dist/connectors/evm.d.ts +144 -0
- package/dist/connectors/evm.d.ts.map +1 -0
- package/dist/connectors/evm.js +330 -0
- package/dist/connectors/evm.js.map +1 -0
- package/dist/connectors/phantom.d.ts +167 -0
- package/dist/connectors/phantom.d.ts.map +1 -0
- package/dist/connectors/phantom.js +340 -0
- package/dist/connectors/phantom.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +70 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +74 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +210 -0
- package/dist/provider.js.map +1 -0
- package/dist/session.d.ts +68 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +99 -0
- package/dist/session.js.map +1 -0
- package/dist/signing.d.ts +94 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +178 -0
- package/dist/signing.js.map +1 -0
- package/dist/types.d.ts +234 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +150 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** EIP-1193 request method signature. */
|
|
2
|
+
export interface Eip1193RequestArgs {
|
|
3
|
+
readonly method: string;
|
|
4
|
+
readonly params?: readonly unknown[] | object;
|
|
5
|
+
}
|
|
6
|
+
export interface Eip1193Provider {
|
|
7
|
+
request(args: Eip1193RequestArgs): Promise<unknown>;
|
|
8
|
+
readonly isMetaMask?: boolean;
|
|
9
|
+
readonly isCoinbaseWallet?: boolean;
|
|
10
|
+
/** MetaMask's multi-wallet bridge (deprecated but still in the wild). */
|
|
11
|
+
readonly providers?: readonly Eip1193Provider[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves the EIP-1193 provider from the host environment. Returns
|
|
15
|
+
* undefined when not in a browser or when no injection is present —
|
|
16
|
+
* callers should throw a typed error in that case.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getInjectedProvider(): Eip1193Provider | undefined;
|
|
19
|
+
export declare function requireInjectedProvider(): Eip1193Provider;
|
|
20
|
+
/**
|
|
21
|
+
* Canonical rdns identifiers for the wallets this package supports.
|
|
22
|
+
* Match against these — never against `info.name`, which any extension
|
|
23
|
+
* can set.
|
|
24
|
+
*/
|
|
25
|
+
export declare const WALLET_RDNS: {
|
|
26
|
+
readonly metamask: "io.metamask";
|
|
27
|
+
readonly coinbase: "com.coinbase.wallet";
|
|
28
|
+
readonly phantom: "app.phantom";
|
|
29
|
+
};
|
|
30
|
+
export type WalletRdns = (typeof WALLET_RDNS)[keyof typeof WALLET_RDNS];
|
|
31
|
+
export interface Eip6963ProviderInfo {
|
|
32
|
+
readonly uuid: string;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly icon: string;
|
|
35
|
+
/** Reverse-DNS identifier — the only spoofing-resistant wallet ID. */
|
|
36
|
+
readonly rdns: string;
|
|
37
|
+
}
|
|
38
|
+
export interface Eip6963ProviderDetail {
|
|
39
|
+
readonly info: Eip6963ProviderInfo;
|
|
40
|
+
readonly provider: Eip1193Provider;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Synchronously collect EIP-6963 provider announcements. Dispatches
|
|
44
|
+
* `eip6963:requestProvider` and listens for `eip6963:announceProvider`
|
|
45
|
+
* responses. Announcements are deduplicated by `info.uuid`.
|
|
46
|
+
*
|
|
47
|
+
* NOTE: EIP-6963 announcements are synchronous; wallets respond to the
|
|
48
|
+
* request event before the dispatch returns. Async polling is not
|
|
49
|
+
* required.
|
|
50
|
+
*/
|
|
51
|
+
export declare function discoverEip6963Providers(): ReadonlyMap<string, Eip6963ProviderDetail>;
|
|
52
|
+
/** Find the first announced provider whose `info.rdns` matches `rdns`. */
|
|
53
|
+
export declare function findProviderByRdns(rdns: WalletRdns | string): Eip6963ProviderDetail | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* MetaMask-specific selection.
|
|
56
|
+
*
|
|
57
|
+
* Strict (EIP-6963) path: match on `info.rdns === 'io.metamask'`.
|
|
58
|
+
* Legacy fallback: when no EIP-6963 announcement is heard, fall back to
|
|
59
|
+
* `window.ethereum.isMetaMask` / `ethereum.providers[i].isMetaMask`.
|
|
60
|
+
* The legacy path is strictly weaker — an extension that injects
|
|
61
|
+
* `window.ethereum = { isMetaMask: true, request: drainerRequest }`
|
|
62
|
+
* before MetaMask loads will be matched there. Document this to
|
|
63
|
+
* consumers in JSDoc + THREAT_MODEL.md.
|
|
64
|
+
*/
|
|
65
|
+
export declare function selectMetaMaskProvider(): Eip1193Provider;
|
|
66
|
+
export declare function selectCoinbaseProvider(): Eip1193Provider;
|
|
67
|
+
export declare function getAccounts(provider: Eip1193Provider): Promise<string[]>;
|
|
68
|
+
export declare function requestAccounts(provider: Eip1193Provider): Promise<string[]>;
|
|
69
|
+
export declare function getChainId(provider: Eip1193Provider): Promise<string>;
|
|
70
|
+
export declare function isWalletConnected(provider: Eip1193Provider): Promise<boolean>;
|
|
71
|
+
export declare function getBalance(provider: Eip1193Provider, address: string): Promise<bigint>;
|
|
72
|
+
export declare function isMetamaskInstalled(): boolean;
|
|
73
|
+
export declare function isCoinBaseInstalled(): boolean;
|
|
74
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAoBA,yCAAyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAQD;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,SAAS,CAMjE;AAED,wBAAgB,uBAAuB,IAAI,eAAe,CAMzD;AAaD;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AA+BD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAAC,MAAM,EAAE,qBAAqB,CAAC,CA2BrF;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAM/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CAgBxD;AAED,wBAAgB,sBAAsB,IAAI,eAAe,CAgBxD;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG9E;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGlF;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAG3E;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAGnF;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAOjB;AAMD,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal EIP-1193 provider type and probing helpers.
|
|
3
|
+
*
|
|
4
|
+
* The original v1.0.1 stuffed `window.ethereum` into `any` everywhere. This
|
|
5
|
+
* module replaces that with a typed shape and validates each response.
|
|
6
|
+
*
|
|
7
|
+
* Wallet-impersonation hardening (HIGH-1):
|
|
8
|
+
*
|
|
9
|
+
* `isMetaMask` / `isCoinbaseWallet` are self-reported flags any
|
|
10
|
+
* extension can set. To pick a wallet by *identity* rather than by
|
|
11
|
+
* claim, this module also implements EIP-6963 multi-provider
|
|
12
|
+
* discovery: wallets emit `eip6963:announceProvider` events with a
|
|
13
|
+
* reverse-DNS `rdns` identifier (`io.metamask`, `com.coinbase.wallet`,
|
|
14
|
+
* `app.phantom`, …). When the user picks a wallet we match on `rdns`,
|
|
15
|
+
* not on the spoofable `isXxx` flag. The legacy `window.ethereum`
|
|
16
|
+
* path remains as a fallback with a documented weaker guarantee.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { WalletNotFoundException } from './errors.js';
|
|
20
|
+
const AccountsResponseSchema = z.array(z.string().regex(/^0x[a-fA-F0-9]{40}$/u));
|
|
21
|
+
const HexResponseSchema = z.string().regex(/^0x[a-fA-F0-9]*$/u);
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the EIP-1193 provider from the host environment. Returns
|
|
24
|
+
* undefined when not in a browser or when no injection is present —
|
|
25
|
+
* callers should throw a typed error in that case.
|
|
26
|
+
*/
|
|
27
|
+
export function getInjectedProvider() {
|
|
28
|
+
if (typeof globalThis === 'undefined') {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const w = globalThis;
|
|
32
|
+
return w.ethereum;
|
|
33
|
+
}
|
|
34
|
+
export function requireInjectedProvider() {
|
|
35
|
+
const p = getInjectedProvider();
|
|
36
|
+
if (p === undefined) {
|
|
37
|
+
throw new WalletNotFoundException('No EVM wallet provider found on window');
|
|
38
|
+
}
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
/* -------------------------------------------------------------------------- */
|
|
42
|
+
/* EIP-6963: Multi-Injected Provider Discovery */
|
|
43
|
+
/* */
|
|
44
|
+
/* https://eips.ethereum.org/EIPS/eip-6963 */
|
|
45
|
+
/* */
|
|
46
|
+
/* Wallets dispatch `eip6963:announceProvider` whenever the page emits */
|
|
47
|
+
/* `eip6963:requestProvider`. We collect announcements into a Map keyed by */
|
|
48
|
+
/* the wallet's reverse-DNS `rdns` identifier — the canonical wallet */
|
|
49
|
+
/* identity, NOT the spoofable `info.name` string. */
|
|
50
|
+
/* -------------------------------------------------------------------------- */
|
|
51
|
+
/**
|
|
52
|
+
* Canonical rdns identifiers for the wallets this package supports.
|
|
53
|
+
* Match against these — never against `info.name`, which any extension
|
|
54
|
+
* can set.
|
|
55
|
+
*/
|
|
56
|
+
export const WALLET_RDNS = {
|
|
57
|
+
metamask: 'io.metamask',
|
|
58
|
+
coinbase: 'com.coinbase.wallet',
|
|
59
|
+
phantom: 'app.phantom',
|
|
60
|
+
};
|
|
61
|
+
function getEventTarget() {
|
|
62
|
+
const g = globalThis;
|
|
63
|
+
// Test injection hook — lets node `testEnvironment: 'node'` provide
|
|
64
|
+
// its own EventTarget when one is not natively available.
|
|
65
|
+
if (g.__droplinkedEventTarget__ !== undefined &&
|
|
66
|
+
typeof g.__droplinkedEventTarget__.addEventListener === 'function') {
|
|
67
|
+
return g.__droplinkedEventTarget__;
|
|
68
|
+
}
|
|
69
|
+
if (typeof g.addEventListener === 'function')
|
|
70
|
+
return g;
|
|
71
|
+
if (g.window !== undefined && typeof g.window.addEventListener === 'function')
|
|
72
|
+
return g.window;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Synchronously collect EIP-6963 provider announcements. Dispatches
|
|
77
|
+
* `eip6963:requestProvider` and listens for `eip6963:announceProvider`
|
|
78
|
+
* responses. Announcements are deduplicated by `info.uuid`.
|
|
79
|
+
*
|
|
80
|
+
* NOTE: EIP-6963 announcements are synchronous; wallets respond to the
|
|
81
|
+
* request event before the dispatch returns. Async polling is not
|
|
82
|
+
* required.
|
|
83
|
+
*/
|
|
84
|
+
export function discoverEip6963Providers() {
|
|
85
|
+
const map = new Map();
|
|
86
|
+
const target = getEventTarget();
|
|
87
|
+
if (target === undefined)
|
|
88
|
+
return map;
|
|
89
|
+
const handler = (e) => {
|
|
90
|
+
const ann = e;
|
|
91
|
+
const detail = ann.detail;
|
|
92
|
+
if (detail === undefined ||
|
|
93
|
+
detail.info === undefined ||
|
|
94
|
+
typeof detail.info.uuid !== 'string' ||
|
|
95
|
+
typeof detail.info.rdns !== 'string' ||
|
|
96
|
+
detail.provider === undefined ||
|
|
97
|
+
typeof detail.provider.request !== 'function') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
map.set(detail.info.uuid, detail);
|
|
101
|
+
};
|
|
102
|
+
target.addEventListener('eip6963:announceProvider', handler);
|
|
103
|
+
try {
|
|
104
|
+
target.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
target.removeEventListener('eip6963:announceProvider', handler);
|
|
108
|
+
}
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
/** Find the first announced provider whose `info.rdns` matches `rdns`. */
|
|
112
|
+
export function findProviderByRdns(rdns) {
|
|
113
|
+
const map = discoverEip6963Providers();
|
|
114
|
+
for (const detail of map.values()) {
|
|
115
|
+
if (detail.info.rdns === rdns)
|
|
116
|
+
return detail;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* MetaMask-specific selection.
|
|
122
|
+
*
|
|
123
|
+
* Strict (EIP-6963) path: match on `info.rdns === 'io.metamask'`.
|
|
124
|
+
* Legacy fallback: when no EIP-6963 announcement is heard, fall back to
|
|
125
|
+
* `window.ethereum.isMetaMask` / `ethereum.providers[i].isMetaMask`.
|
|
126
|
+
* The legacy path is strictly weaker — an extension that injects
|
|
127
|
+
* `window.ethereum = { isMetaMask: true, request: drainerRequest }`
|
|
128
|
+
* before MetaMask loads will be matched there. Document this to
|
|
129
|
+
* consumers in JSDoc + THREAT_MODEL.md.
|
|
130
|
+
*/
|
|
131
|
+
export function selectMetaMaskProvider() {
|
|
132
|
+
const detail = findProviderByRdns(WALLET_RDNS.metamask);
|
|
133
|
+
if (detail !== undefined)
|
|
134
|
+
return detail.provider;
|
|
135
|
+
const root = requireInjectedProvider();
|
|
136
|
+
if (Array.isArray(root.providers)) {
|
|
137
|
+
const mm = root.providers.find((p) => p.isMetaMask === true);
|
|
138
|
+
if (mm === undefined) {
|
|
139
|
+
throw new WalletNotFoundException('MetaMask provider not found');
|
|
140
|
+
}
|
|
141
|
+
return mm;
|
|
142
|
+
}
|
|
143
|
+
if (root.isMetaMask !== true) {
|
|
144
|
+
throw new WalletNotFoundException('MetaMask provider not found');
|
|
145
|
+
}
|
|
146
|
+
return root;
|
|
147
|
+
}
|
|
148
|
+
export function selectCoinbaseProvider() {
|
|
149
|
+
const detail = findProviderByRdns(WALLET_RDNS.coinbase);
|
|
150
|
+
if (detail !== undefined)
|
|
151
|
+
return detail.provider;
|
|
152
|
+
const root = requireInjectedProvider();
|
|
153
|
+
if (Array.isArray(root.providers)) {
|
|
154
|
+
const cb = root.providers.find((p) => p.isCoinbaseWallet === true);
|
|
155
|
+
if (cb === undefined) {
|
|
156
|
+
throw new WalletNotFoundException('Coinbase Wallet provider not found');
|
|
157
|
+
}
|
|
158
|
+
return cb;
|
|
159
|
+
}
|
|
160
|
+
if (root.isCoinbaseWallet !== true) {
|
|
161
|
+
throw new WalletNotFoundException('Coinbase Wallet provider not found');
|
|
162
|
+
}
|
|
163
|
+
return root;
|
|
164
|
+
}
|
|
165
|
+
export async function getAccounts(provider) {
|
|
166
|
+
const raw = await provider.request({ method: 'eth_accounts' });
|
|
167
|
+
return AccountsResponseSchema.parse(raw);
|
|
168
|
+
}
|
|
169
|
+
export async function requestAccounts(provider) {
|
|
170
|
+
const raw = await provider.request({ method: 'eth_requestAccounts' });
|
|
171
|
+
return AccountsResponseSchema.parse(raw);
|
|
172
|
+
}
|
|
173
|
+
export async function getChainId(provider) {
|
|
174
|
+
const raw = await provider.request({ method: 'eth_chainId' });
|
|
175
|
+
return HexResponseSchema.parse(raw);
|
|
176
|
+
}
|
|
177
|
+
export async function isWalletConnected(provider) {
|
|
178
|
+
const accounts = await getAccounts(provider);
|
|
179
|
+
return accounts.length > 0;
|
|
180
|
+
}
|
|
181
|
+
export async function getBalance(provider, address) {
|
|
182
|
+
const raw = await provider.request({
|
|
183
|
+
method: 'eth_getBalance',
|
|
184
|
+
params: [address, 'latest'],
|
|
185
|
+
});
|
|
186
|
+
const hex = HexResponseSchema.parse(raw);
|
|
187
|
+
return BigInt(hex);
|
|
188
|
+
}
|
|
189
|
+
/* -------------------------------------------------------------------------- */
|
|
190
|
+
/* Back-compat free-standing probes (mirror v1.0.1 boolean helpers) */
|
|
191
|
+
/* -------------------------------------------------------------------------- */
|
|
192
|
+
export function isMetamaskInstalled() {
|
|
193
|
+
try {
|
|
194
|
+
selectMetaMaskProvider();
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export function isCoinBaseInstalled() {
|
|
202
|
+
try {
|
|
203
|
+
selectCoinbaseProvider();
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session storage helpers. The original v1.0.1 had no notion of sessions
|
|
3
|
+
* — every call required the caller to pass an address as a string, which
|
|
4
|
+
* encouraged callers to stash addresses in cookies/localStorage with no
|
|
5
|
+
* binding to chain or origin.
|
|
6
|
+
*
|
|
7
|
+
* This module provides a typed, expiring, origin-bound session record.
|
|
8
|
+
* Storage backend is pluggable; defaults to `sessionStorage` (cleared on
|
|
9
|
+
* tab close) rather than `localStorage` (persists forever) — that matches
|
|
10
|
+
* the threat model for wallet sessions.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { type EvmAddress } from './types.js';
|
|
14
|
+
import type { Hex } from 'viem';
|
|
15
|
+
/**
|
|
16
|
+
* Hard upper bound on session TTL (HIGH-2 mitigation). Because the
|
|
17
|
+
* raw EIP-712 signature is persisted in sessionStorage alongside the
|
|
18
|
+
* payload, it functions as a bearer credential for whoever can read
|
|
19
|
+
* the storage entry — any XSS in any consumer, any malicious browser
|
|
20
|
+
* extension with the `storage` permission, or a screen-share leak.
|
|
21
|
+
* We refuse to issue a session longer than 15 minutes so the blast
|
|
22
|
+
* radius of a single sessionStorage exfil is bounded.
|
|
23
|
+
*
|
|
24
|
+
* Consumers that need longer-lived sessions MUST exchange the
|
|
25
|
+
* signature for a server-issued opaque session token and drop the
|
|
26
|
+
* raw signature client-side.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SESSION_MAX_TTL_SECONDS: number;
|
|
29
|
+
export declare const WalletSessionSchema: z.ZodObject<{
|
|
30
|
+
address: z.ZodEffects<z.ZodString, `0x${string}`, string>;
|
|
31
|
+
chainId: z.ZodNumber;
|
|
32
|
+
origin: z.ZodString;
|
|
33
|
+
signature: z.ZodString;
|
|
34
|
+
issuedAt: z.ZodString;
|
|
35
|
+
expiresAt: z.ZodString;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
address: `0x${string}`;
|
|
38
|
+
chainId: number;
|
|
39
|
+
issuedAt: string;
|
|
40
|
+
signature: string;
|
|
41
|
+
origin: string;
|
|
42
|
+
expiresAt: string;
|
|
43
|
+
}, {
|
|
44
|
+
address: string;
|
|
45
|
+
chainId: number;
|
|
46
|
+
issuedAt: string;
|
|
47
|
+
signature: string;
|
|
48
|
+
origin: string;
|
|
49
|
+
expiresAt: string;
|
|
50
|
+
}>;
|
|
51
|
+
export type WalletSession = z.infer<typeof WalletSessionSchema>;
|
|
52
|
+
export interface SessionStorage {
|
|
53
|
+
getItem(key: string): string | null;
|
|
54
|
+
setItem(key: string, value: string): void;
|
|
55
|
+
removeItem(key: string): void;
|
|
56
|
+
}
|
|
57
|
+
export declare function saveSession(session: WalletSession, storage?: SessionStorage | undefined): void;
|
|
58
|
+
export declare function loadSession(storage?: SessionStorage | undefined, nowMs?: number): WalletSession | undefined;
|
|
59
|
+
export declare function clearSession(storage?: SessionStorage | undefined): void;
|
|
60
|
+
export interface BuildSessionArgs {
|
|
61
|
+
readonly address: EvmAddress;
|
|
62
|
+
readonly chainId: number;
|
|
63
|
+
readonly origin: string;
|
|
64
|
+
readonly signature: Hex;
|
|
65
|
+
readonly ttlSeconds: number;
|
|
66
|
+
}
|
|
67
|
+
export declare function buildSession(args: BuildSessionArgs): WalletSession;
|
|
68
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE/D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAIhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,QAAU,CAAC;AAE/C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;EAO9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAOD,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,cAAc,GAAG,SAA4B,GACrD,IAAI,CAMN;AAED,wBAAgB,WAAW,CACzB,OAAO,GAAE,cAAc,GAAG,SAA4B,EACtD,KAAK,GAAE,MAAmB,GACzB,aAAa,GAAG,SAAS,CA0B3B;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,cAAc,GAAG,SAA4B,GACrD,IAAI,CAKN;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAiBlE"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session storage helpers. The original v1.0.1 had no notion of sessions
|
|
3
|
+
* — every call required the caller to pass an address as a string, which
|
|
4
|
+
* encouraged callers to stash addresses in cookies/localStorage with no
|
|
5
|
+
* binding to chain or origin.
|
|
6
|
+
*
|
|
7
|
+
* This module provides a typed, expiring, origin-bound session record.
|
|
8
|
+
* Storage backend is pluggable; defaults to `sessionStorage` (cleared on
|
|
9
|
+
* tab close) rather than `localStorage` (persists forever) — that matches
|
|
10
|
+
* the threat model for wallet sessions.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { EvmAddressSchema } from './types.js';
|
|
14
|
+
import { SignaturePayloadInvalidError } from './errors.js';
|
|
15
|
+
const STORAGE_KEY = 'droplinked:wallet-session:v1';
|
|
16
|
+
/**
|
|
17
|
+
* Hard upper bound on session TTL (HIGH-2 mitigation). Because the
|
|
18
|
+
* raw EIP-712 signature is persisted in sessionStorage alongside the
|
|
19
|
+
* payload, it functions as a bearer credential for whoever can read
|
|
20
|
+
* the storage entry — any XSS in any consumer, any malicious browser
|
|
21
|
+
* extension with the `storage` permission, or a screen-share leak.
|
|
22
|
+
* We refuse to issue a session longer than 15 minutes so the blast
|
|
23
|
+
* radius of a single sessionStorage exfil is bounded.
|
|
24
|
+
*
|
|
25
|
+
* Consumers that need longer-lived sessions MUST exchange the
|
|
26
|
+
* signature for a server-issued opaque session token and drop the
|
|
27
|
+
* raw signature client-side.
|
|
28
|
+
*/
|
|
29
|
+
export const SESSION_MAX_TTL_SECONDS = 15 * 60;
|
|
30
|
+
export const WalletSessionSchema = z.object({
|
|
31
|
+
address: EvmAddressSchema,
|
|
32
|
+
chainId: z.number().int().positive(),
|
|
33
|
+
origin: z.string().min(1),
|
|
34
|
+
signature: z.string().regex(/^0x[a-fA-F0-9]+$/u),
|
|
35
|
+
issuedAt: z.string().datetime(),
|
|
36
|
+
expiresAt: z.string().datetime(),
|
|
37
|
+
});
|
|
38
|
+
function defaultStorage() {
|
|
39
|
+
const g = globalThis;
|
|
40
|
+
return g.sessionStorage;
|
|
41
|
+
}
|
|
42
|
+
export function saveSession(session, storage = defaultStorage()) {
|
|
43
|
+
if (storage === undefined) {
|
|
44
|
+
return; // headless / SSR — silently no-op
|
|
45
|
+
}
|
|
46
|
+
WalletSessionSchema.parse(session);
|
|
47
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(session));
|
|
48
|
+
}
|
|
49
|
+
export function loadSession(storage = defaultStorage(), nowMs = Date.now()) {
|
|
50
|
+
if (storage === undefined) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const raw = storage.getItem(STORAGE_KEY);
|
|
54
|
+
if (raw === null) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
let parsedJson;
|
|
58
|
+
try {
|
|
59
|
+
parsedJson = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
storage.removeItem(STORAGE_KEY);
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const result = WalletSessionSchema.safeParse(parsedJson);
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
storage.removeItem(STORAGE_KEY);
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const exp = Date.parse(result.data.expiresAt);
|
|
71
|
+
if (Number.isNaN(exp) || exp <= nowMs) {
|
|
72
|
+
storage.removeItem(STORAGE_KEY);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return result.data;
|
|
76
|
+
}
|
|
77
|
+
export function clearSession(storage = defaultStorage()) {
|
|
78
|
+
if (storage === undefined) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
storage.removeItem(STORAGE_KEY);
|
|
82
|
+
}
|
|
83
|
+
export function buildSession(args) {
|
|
84
|
+
if (args.ttlSeconds <= 0 || args.ttlSeconds > SESSION_MAX_TTL_SECONDS) {
|
|
85
|
+
throw new SignaturePayloadInvalidError(`ttlSeconds must be in (0, ${SESSION_MAX_TTL_SECONDS}]; got ${args.ttlSeconds}. ` +
|
|
86
|
+
'Long-lived sessions MUST be issued server-side, not from this client library.');
|
|
87
|
+
}
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const session = {
|
|
90
|
+
address: args.address,
|
|
91
|
+
chainId: args.chainId,
|
|
92
|
+
origin: args.origin,
|
|
93
|
+
signature: args.signature,
|
|
94
|
+
issuedAt: new Date(now).toISOString(),
|
|
95
|
+
expiresAt: new Date(now + args.ttlSeconds * 1000).toISOString(),
|
|
96
|
+
};
|
|
97
|
+
return WalletSessionSchema.parse(session);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIP-712 typed-data signing + verification.
|
|
3
|
+
*
|
|
4
|
+
* Hardening deltas vs. v1.0.1:
|
|
5
|
+
* 1. v1.0.1 used `personal_sign` of a static plaintext — replayable
|
|
6
|
+
* across chains, origins, time. We use EIP-712 typed data with
|
|
7
|
+
* explicit domain (chainId + origin) and a per-attempt nonce.
|
|
8
|
+
* 2. Nonces are 256-bit cryptographically-random url-safe strings.
|
|
9
|
+
* 3. Signature verification is constant-time at the byte-array level.
|
|
10
|
+
* 4. Recovered address is compared as a normalized lowercase hex string.
|
|
11
|
+
*/
|
|
12
|
+
import { type Hex } from 'viem';
|
|
13
|
+
import { type LoginPayload, type EvmAddress } from './types.js';
|
|
14
|
+
import type { Eip1193Provider } from './provider.js';
|
|
15
|
+
/** EIP-712 domain name — kept short, embedded in every signed payload. */
|
|
16
|
+
export declare const TYPED_DATA_DOMAIN_NAME: "droplinked";
|
|
17
|
+
/** EIP-712 type definitions. */
|
|
18
|
+
export declare const LOGIN_TYPES: {
|
|
19
|
+
readonly Login: readonly [{
|
|
20
|
+
readonly name: "domain";
|
|
21
|
+
readonly type: "string";
|
|
22
|
+
}, {
|
|
23
|
+
readonly name: "address";
|
|
24
|
+
readonly type: "address";
|
|
25
|
+
}, {
|
|
26
|
+
readonly name: "statement";
|
|
27
|
+
readonly type: "string";
|
|
28
|
+
}, {
|
|
29
|
+
readonly name: "uri";
|
|
30
|
+
readonly type: "string";
|
|
31
|
+
}, {
|
|
32
|
+
readonly name: "version";
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "chainId";
|
|
36
|
+
readonly type: "uint256";
|
|
37
|
+
}, {
|
|
38
|
+
readonly name: "nonce";
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
}, {
|
|
41
|
+
readonly name: "issuedAt";
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
}, {
|
|
44
|
+
readonly name: "expirationTime";
|
|
45
|
+
readonly type: "string";
|
|
46
|
+
}];
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Generates a 256-bit random url-safe nonce. Uses Web Crypto when available;
|
|
50
|
+
* throws if no CSPRNG is reachable (we refuse to fall back to Math.random).
|
|
51
|
+
*/
|
|
52
|
+
export declare function generateNonce(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Build a canonical login payload. Always binds:
|
|
55
|
+
* - origin (window.location.origin or caller-provided)
|
|
56
|
+
* - chainId (must match the wallet's current chain at signing time)
|
|
57
|
+
* - fresh nonce
|
|
58
|
+
* - issuedAt timestamp
|
|
59
|
+
* - optional expirationTime
|
|
60
|
+
*/
|
|
61
|
+
export interface BuildLoginPayloadArgs {
|
|
62
|
+
readonly address: EvmAddress;
|
|
63
|
+
readonly chainId: number;
|
|
64
|
+
readonly origin: string;
|
|
65
|
+
readonly statement?: string;
|
|
66
|
+
readonly expiresInSeconds?: number;
|
|
67
|
+
}
|
|
68
|
+
export declare function buildLoginPayload(args: BuildLoginPayloadArgs): LoginPayload;
|
|
69
|
+
/**
|
|
70
|
+
* Request the wallet to sign the typed payload. Returns the 0x-prefixed
|
|
71
|
+
* signature.
|
|
72
|
+
*/
|
|
73
|
+
export declare function signLoginPayload(provider: Eip1193Provider, payload: LoginPayload): Promise<Hex>;
|
|
74
|
+
/**
|
|
75
|
+
* Verify a previously-issued login signature. Server-side and client-side
|
|
76
|
+
* use the same function. Returns void on success, throws on any mismatch.
|
|
77
|
+
*/
|
|
78
|
+
export interface VerifyLoginArgs {
|
|
79
|
+
readonly payload: LoginPayload;
|
|
80
|
+
readonly signature: Hex;
|
|
81
|
+
readonly expectedAddress: EvmAddress;
|
|
82
|
+
readonly expectedChainId: number;
|
|
83
|
+
readonly expectedOrigin: string;
|
|
84
|
+
readonly nowMs?: number;
|
|
85
|
+
}
|
|
86
|
+
export declare function verifyLoginSignature(args: VerifyLoginArgs): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Constant-time string equality. Compares the UTF-8 byte representation;
|
|
89
|
+
* always touches every byte of both inputs regardless of mismatch position.
|
|
90
|
+
* Returns false immediately on length mismatch — note that length is
|
|
91
|
+
* inherently leaky and not considered a secret here.
|
|
92
|
+
*/
|
|
93
|
+
export declare function constantTimeStringEquals(a: string, b: string): boolean;
|
|
94
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAiC,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAOpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,EAAG,YAAqB,CAAC;AAE5D,gCAAgC;AAChC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYd,CAAC;AAEX;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CActC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,YAAY,CA4B3E;AAUD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,GAAG,CAAC,CAmBd;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE/E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAWtE"}
|