@openkey/sdk 0.0.1 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # @openkey/sdk
2
+
3
+ The official JavaScript/TypeScript SDK for [OpenKey](https://openkey.so) - secure key management powered by passkeys.
4
+
5
+ ## What is OpenKey?
6
+
7
+ OpenKey provides secure cryptographic key management using passkeys (WebAuthn). Instead of managing seed phrases or private keys directly, OpenKey stores your keys securely and lets you authorize operations with biometric authentication (Face ID, Touch ID, etc.).
8
+
9
+ **Key features:**
10
+ - **Passkey-secured keys** - Keys are protected by WebAuthn, not passwords
11
+ - **No seed phrases** - Never worry about losing or exposing recovery phrases
12
+ - **Popup-based signing** - Clean UX with authorization popups
13
+ - **EIP-1193 compatible** - Works with ethers.js, viem, and other web3 libraries
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @openkey/sdk
19
+ # or
20
+ yarn add @openkey/sdk
21
+ # or
22
+ bun add @openkey/sdk
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { OpenKey } from '@openkey/sdk';
29
+
30
+ // Initialize the SDK
31
+ const openkey = new OpenKey({
32
+ host: 'https://openkey.so', // Production
33
+ // host: 'http://localhost:5173', // Development
34
+ });
35
+
36
+ // Connect to OpenKey (opens popup for user to select/create a key)
37
+ const { address, keyId } = await openkey.connect();
38
+ console.log('Connected:', address);
39
+
40
+ // Sign a message (opens popup for user approval)
41
+ const signature = await openkey.signMessage({
42
+ message: 'Hello, OpenKey!',
43
+ keyId,
44
+ });
45
+ console.log('Signature:', signature);
46
+ ```
47
+
48
+ ## API Reference
49
+
50
+ ### `new OpenKey(options)`
51
+
52
+ Create a new OpenKey instance.
53
+
54
+ ```typescript
55
+ const openkey = new OpenKey({
56
+ host: 'https://openkey.so', // OpenKey server URL
57
+ });
58
+ ```
59
+
60
+ **Options:**
61
+ - `host` (string): The OpenKey server URL. Use `https://openkey.so` for production.
62
+
63
+ ### `openkey.connect()`
64
+
65
+ Opens the OpenKey popup for the user to authenticate and select a key.
66
+
67
+ ```typescript
68
+ const { address, keyId } = await openkey.connect();
69
+ ```
70
+
71
+ **Returns:**
72
+ - `address` (string): The Ethereum address of the selected key
73
+ - `keyId` (string): The unique identifier for the key (used in signing operations)
74
+
75
+ ### `openkey.signMessage(params)`
76
+
77
+ Sign a message with the user's key. Opens a popup for user approval.
78
+
79
+ ```typescript
80
+ const signature = await openkey.signMessage({
81
+ message: 'Hello, World!',
82
+ keyId: 'key-id-from-connect',
83
+ });
84
+ ```
85
+
86
+ **Parameters:**
87
+ - `message` (string): The message to sign
88
+ - `keyId` (string): The key ID from `connect()`
89
+
90
+ **Returns:**
91
+ - `signature` (string): The hex-encoded signature
92
+
93
+ ### `openkey.signTypedData(params)`
94
+
95
+ Sign EIP-712 typed data with the user's key.
96
+
97
+ ```typescript
98
+ const signature = await openkey.signTypedData({
99
+ domain: {
100
+ name: 'My App',
101
+ version: '1',
102
+ chainId: 1,
103
+ },
104
+ types: {
105
+ Message: [{ name: 'content', type: 'string' }],
106
+ },
107
+ primaryType: 'Message',
108
+ message: {
109
+ content: 'Hello!',
110
+ },
111
+ keyId: 'key-id-from-connect',
112
+ });
113
+ ```
114
+
115
+ ## Integration with ethers.js
116
+
117
+ OpenKey can be used as an EIP-1193 provider with ethers.js:
118
+
119
+ ```typescript
120
+ import { OpenKey } from '@openkey/sdk';
121
+ import { BrowserProvider } from 'ethers';
122
+
123
+ // Create OpenKey instance
124
+ const openkey = new OpenKey({ host: 'https://openkey.so' });
125
+
126
+ // Connect and get the address
127
+ const { address, keyId } = await openkey.connect();
128
+
129
+ // Create an EIP-1193 compatible provider
130
+ const eip1193Provider = {
131
+ async request({ method, params }: { method: string; params?: any[] }) {
132
+ switch (method) {
133
+ case 'eth_accounts':
134
+ case 'eth_requestAccounts':
135
+ return [address];
136
+ case 'eth_chainId':
137
+ return '0x1'; // Mainnet
138
+ case 'personal_sign': {
139
+ const message = hexToString(params![0]);
140
+ const result = await openkey.signMessage({ message, keyId });
141
+ return result.signature;
142
+ }
143
+ default:
144
+ throw new Error(`Unsupported method: ${method}`);
145
+ }
146
+ },
147
+ };
148
+
149
+ // Use with ethers
150
+ const provider = new BrowserProvider(eip1193Provider);
151
+ const signer = await provider.getSigner();
152
+ ```
153
+
154
+ ## Integration with TinyCloud
155
+
156
+ OpenKey works seamlessly with [TinyCloud](https://tinycloud.xyz) for decentralized storage:
157
+
158
+ ```typescript
159
+ import { OpenKey } from '@openkey/sdk';
160
+ import { TinyCloudWeb } from '@tinycloud/web-sdk';
161
+ import { BrowserProvider } from 'ethers';
162
+
163
+ // Setup OpenKey
164
+ const openkey = new OpenKey({ host: 'https://openkey.so' });
165
+ const { address, keyId } = await openkey.connect();
166
+
167
+ // Create EIP-1193 provider (see above)
168
+ const eip1193Provider = createOpenKeyProvider(openkey, address, keyId);
169
+ const ethersProvider = new BrowserProvider(eip1193Provider);
170
+
171
+ // Initialize TinyCloud with OpenKey as the signer
172
+ const tcw = new TinyCloudWeb({
173
+ providers: {
174
+ web3: {
175
+ driver: ethersProvider,
176
+ },
177
+ },
178
+ });
179
+
180
+ // Sign in to TinyCloud (uses OpenKey for SIWE signature)
181
+ await tcw.signIn();
182
+
183
+ // Now you can use TinyCloud storage
184
+ await tcw.kv.put('hello', 'world');
185
+ const result = await tcw.kv.get('hello');
186
+ ```
187
+
188
+ ## Development
189
+
190
+ For local development, point the SDK to your local OpenKey instance:
191
+
192
+ ```typescript
193
+ const openkey = new OpenKey({
194
+ host: 'http://localhost:5173',
195
+ });
196
+ ```
197
+
198
+ You can also enable dev mode in your app to use local OpenKey:
199
+
200
+ ```javascript
201
+ localStorage.setItem('__DEV_MODE__', 'true');
202
+ ```
203
+
204
+ ## Browser Support
205
+
206
+ OpenKey requires WebAuthn support:
207
+ - Chrome 67+
208
+ - Firefox 60+
209
+ - Safari 13+
210
+ - Edge 79+
211
+
212
+ ## Security
213
+
214
+ - Keys never leave the OpenKey TEE (Trusted Execution Environment)
215
+ - All signing operations require explicit user approval via passkey
216
+ - No seed phrases or private keys to manage or lose
217
+
218
+ ## Links
219
+
220
+ - [OpenKey Website](https://openkey.so)
221
+ - [Documentation](https://docs.openkey.so)
222
+ - [GitHub](https://github.com/AstroX11/openkey)
223
+ - [TinyCloud Integration](https://tinycloud.xyz)
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,190 @@
1
+ interface EIP1193Provider {
2
+ request(args: {
3
+ method: string;
4
+ params?: any[];
5
+ }): Promise<any>;
6
+ }
7
+ interface OpenKeyConfig {
8
+ /** OpenKey host URL (default: https://openkey.so) */
9
+ host?: string;
10
+ /** App identifier for display */
11
+ appName?: string;
12
+ /** Use popup instead of iframe (default: true) */
13
+ usePopup?: boolean;
14
+ /** App-provided wallet provider for external key signing */
15
+ externalProvider?: EIP1193Provider;
16
+ }
17
+ interface SignMessageRequest {
18
+ message: string;
19
+ keyId?: string;
20
+ }
21
+ interface SignTypedDataRequest {
22
+ domain: {
23
+ name?: string;
24
+ version?: string;
25
+ chainId?: number;
26
+ verifyingContract?: string;
27
+ };
28
+ types: Record<string, Array<{
29
+ name: string;
30
+ type: string;
31
+ }>>;
32
+ primaryType: string;
33
+ message: Record<string, unknown>;
34
+ keyId?: string;
35
+ }
36
+ interface SignResult {
37
+ signature: string;
38
+ address: string;
39
+ }
40
+ interface AuthResult {
41
+ address: string;
42
+ keyId: string;
43
+ keyType: 'MANAGED' | 'EXTERNAL';
44
+ }
45
+ interface OpenKeyError {
46
+ code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';
47
+ message: string;
48
+ }
49
+ /** OAuth configuration for third-party apps */
50
+ interface OAuthConfig {
51
+ /** OAuth client_id (registered with OpenKey) */
52
+ clientId: string;
53
+ /** Redirect URI (must match registered URI) */
54
+ redirectUri: string;
55
+ /** State parameter for CSRF protection (auto-generated if not provided) */
56
+ state?: string;
57
+ }
58
+ /** Result from OAuth authorization */
59
+ interface OAuthResult {
60
+ /** Authorization code (exchange for tokens) */
61
+ code: string;
62
+ /** State parameter (verify matches request) */
63
+ state: string;
64
+ }
65
+ /** Response from token exchange */
66
+ interface OAuthTokenResponse {
67
+ access_token: string;
68
+ token_type: 'Bearer';
69
+ expires_in: number;
70
+ id_token: string;
71
+ scope: string;
72
+ refresh_token?: string;
73
+ }
74
+ declare class OpenKey {
75
+ private host;
76
+ private appName;
77
+ private usePopup;
78
+ private config;
79
+ private popup;
80
+ private iframe;
81
+ private lastAuth;
82
+ private discoveredProviders;
83
+ constructor(config?: OpenKeyConfig);
84
+ /**
85
+ * Connect to OpenKey and get user's wallet address
86
+ * Opens auth flow for user to select/create a key
87
+ */
88
+ connect(): Promise<AuthResult>;
89
+ /**
90
+ * Link an external wallet to the user's OpenKey account
91
+ * Opens link-wallet widget flow
92
+ */
93
+ linkWallet(): Promise<{
94
+ address: string;
95
+ keyId: string;
96
+ }>;
97
+ /**
98
+ * Sign a message with the user's OpenKey wallet
99
+ * For external keys, routes directly to the user's wallet provider
100
+ */
101
+ signMessage(request: SignMessageRequest): Promise<SignResult>;
102
+ /**
103
+ * Sign typed data (EIP-712) with the user's OpenKey wallet
104
+ * For external keys, routes directly to the user's wallet provider
105
+ */
106
+ signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
107
+ /**
108
+ * Find a wallet provider that controls the given address.
109
+ * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum
110
+ */
111
+ findWalletProvider(targetAddress: string): Promise<EIP1193Provider>;
112
+ private signWithExternalWallet;
113
+ private signTypedDataWithExternalWallet;
114
+ private signWithPopup;
115
+ private signTypedDataWithPopup;
116
+ private toHex;
117
+ /**
118
+ * Check if user is already authenticated
119
+ */
120
+ isConnected(): Promise<boolean>;
121
+ /**
122
+ * OAuth 2.1 methods for third-party app authentication
123
+ * Use this when your app has registered OAuth client credentials with OpenKey
124
+ */
125
+ oauth: {
126
+ /**
127
+ * Start OAuth authorization flow
128
+ * Opens popup/redirect to OpenKey authorization endpoint
129
+ * @returns Promise that resolves with authorization code after user consent
130
+ */
131
+ connect: (config: OAuthConfig) => Promise<OAuthResult>;
132
+ /**
133
+ * Exchange authorization code for tokens
134
+ * Call this after receiving the callback with authorization code
135
+ * @param code - Authorization code from callback URL
136
+ * @param config - Same config used for connect()
137
+ * @returns Promise that resolves with access_token, id_token, etc.
138
+ */
139
+ exchangeCode: (code: string, config: OAuthConfig) => Promise<OAuthTokenResponse>;
140
+ /**
141
+ * Verify state parameter from callback
142
+ * Call this before exchangeCode to prevent CSRF attacks
143
+ * @param receivedState - State parameter from callback URL
144
+ * @returns true if state matches, false otherwise
145
+ */
146
+ verifyState: (receivedState: string) => boolean;
147
+ /**
148
+ * Parse authorization response from callback URL
149
+ * @param url - Callback URL (defaults to current window.location.href)
150
+ * @returns Parsed code and state, or error
151
+ */
152
+ parseCallback: (url?: string) => {
153
+ code: string;
154
+ state: string;
155
+ } | {
156
+ error: string;
157
+ errorDescription?: string;
158
+ };
159
+ };
160
+ private openOAuthFlow;
161
+ private openOAuthPopup;
162
+ private openFlow;
163
+ private openPopup;
164
+ private openIframe;
165
+ /**
166
+ * Disconnect and close any open flows
167
+ */
168
+ disconnect(): void;
169
+ }
170
+ /**
171
+ * Unified EIP-1193 provider that wraps an OpenKey instance.
172
+ * Transparently routes signing to either OpenKey (managed keys) or the user's
173
+ * wallet (external keys).
174
+ */
175
+ declare class OpenKeyEIP1193Provider implements EIP1193Provider {
176
+ private openkey;
177
+ private address;
178
+ private keyId;
179
+ private keyType;
180
+ constructor(openkey: OpenKey, authResult: AuthResult);
181
+ request({ method, params }: {
182
+ method: string;
183
+ params?: any[];
184
+ }): Promise<any>;
185
+ private hexToString;
186
+ }
187
+
188
+ declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
189
+
190
+ export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ interface EIP1193Provider {
2
+ request(args: {
3
+ method: string;
4
+ params?: any[];
5
+ }): Promise<any>;
6
+ }
1
7
  interface OpenKeyConfig {
2
8
  /** OpenKey host URL (default: https://openkey.so) */
3
9
  host?: string;
@@ -5,6 +11,8 @@ interface OpenKeyConfig {
5
11
  appName?: string;
6
12
  /** Use popup instead of iframe (default: true) */
7
13
  usePopup?: boolean;
14
+ /** App-provided wallet provider for external key signing */
15
+ externalProvider?: EIP1193Provider;
8
16
  }
9
17
  interface SignMessageRequest {
10
18
  message: string;
@@ -32,35 +40,125 @@ interface SignResult {
32
40
  interface AuthResult {
33
41
  address: string;
34
42
  keyId: string;
43
+ keyType: 'MANAGED' | 'EXTERNAL';
35
44
  }
36
45
  interface OpenKeyError {
37
- code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN';
46
+ code: 'USER_CANCELLED' | 'POPUP_BLOCKED' | 'TIMEOUT' | 'NO_KEY' | 'UNAUTHORIZED' | 'UNKNOWN' | 'STATE_MISMATCH';
38
47
  message: string;
39
48
  }
49
+ /** OAuth configuration for third-party apps */
50
+ interface OAuthConfig {
51
+ /** OAuth client_id (registered with OpenKey) */
52
+ clientId: string;
53
+ /** Redirect URI (must match registered URI) */
54
+ redirectUri: string;
55
+ /** State parameter for CSRF protection (auto-generated if not provided) */
56
+ state?: string;
57
+ }
58
+ /** Result from OAuth authorization */
59
+ interface OAuthResult {
60
+ /** Authorization code (exchange for tokens) */
61
+ code: string;
62
+ /** State parameter (verify matches request) */
63
+ state: string;
64
+ }
65
+ /** Response from token exchange */
66
+ interface OAuthTokenResponse {
67
+ access_token: string;
68
+ token_type: 'Bearer';
69
+ expires_in: number;
70
+ id_token: string;
71
+ scope: string;
72
+ refresh_token?: string;
73
+ }
40
74
  declare class OpenKey {
41
75
  private host;
42
76
  private appName;
43
77
  private usePopup;
78
+ private config;
44
79
  private popup;
45
80
  private iframe;
81
+ private lastAuth;
82
+ private discoveredProviders;
46
83
  constructor(config?: OpenKeyConfig);
47
84
  /**
48
85
  * Connect to OpenKey and get user's wallet address
49
86
  * Opens auth flow for user to select/create a key
50
87
  */
51
88
  connect(): Promise<AuthResult>;
89
+ /**
90
+ * Link an external wallet to the user's OpenKey account
91
+ * Opens link-wallet widget flow
92
+ */
93
+ linkWallet(): Promise<{
94
+ address: string;
95
+ keyId: string;
96
+ }>;
52
97
  /**
53
98
  * Sign a message with the user's OpenKey wallet
99
+ * For external keys, routes directly to the user's wallet provider
54
100
  */
55
101
  signMessage(request: SignMessageRequest): Promise<SignResult>;
56
102
  /**
57
103
  * Sign typed data (EIP-712) with the user's OpenKey wallet
104
+ * For external keys, routes directly to the user's wallet provider
58
105
  */
59
106
  signTypedData(request: SignTypedDataRequest): Promise<SignResult>;
107
+ /**
108
+ * Find a wallet provider that controls the given address.
109
+ * Checks: 1) app-provided externalProvider, 2) EIP-6963 discovered wallets, 3) window.ethereum
110
+ */
111
+ findWalletProvider(targetAddress: string): Promise<EIP1193Provider>;
112
+ private signWithExternalWallet;
113
+ private signTypedDataWithExternalWallet;
114
+ private signWithPopup;
115
+ private signTypedDataWithPopup;
116
+ private toHex;
60
117
  /**
61
118
  * Check if user is already authenticated
62
119
  */
63
120
  isConnected(): Promise<boolean>;
121
+ /**
122
+ * OAuth 2.1 methods for third-party app authentication
123
+ * Use this when your app has registered OAuth client credentials with OpenKey
124
+ */
125
+ oauth: {
126
+ /**
127
+ * Start OAuth authorization flow
128
+ * Opens popup/redirect to OpenKey authorization endpoint
129
+ * @returns Promise that resolves with authorization code after user consent
130
+ */
131
+ connect: (config: OAuthConfig) => Promise<OAuthResult>;
132
+ /**
133
+ * Exchange authorization code for tokens
134
+ * Call this after receiving the callback with authorization code
135
+ * @param code - Authorization code from callback URL
136
+ * @param config - Same config used for connect()
137
+ * @returns Promise that resolves with access_token, id_token, etc.
138
+ */
139
+ exchangeCode: (code: string, config: OAuthConfig) => Promise<OAuthTokenResponse>;
140
+ /**
141
+ * Verify state parameter from callback
142
+ * Call this before exchangeCode to prevent CSRF attacks
143
+ * @param receivedState - State parameter from callback URL
144
+ * @returns true if state matches, false otherwise
145
+ */
146
+ verifyState: (receivedState: string) => boolean;
147
+ /**
148
+ * Parse authorization response from callback URL
149
+ * @param url - Callback URL (defaults to current window.location.href)
150
+ * @returns Parsed code and state, or error
151
+ */
152
+ parseCallback: (url?: string) => {
153
+ code: string;
154
+ state: string;
155
+ } | {
156
+ error: string;
157
+ errorDescription?: string;
158
+ };
159
+ };
160
+ private openOAuthFlow;
161
+ private openOAuthPopup;
64
162
  private openFlow;
65
163
  private openPopup;
66
164
  private openIframe;
@@ -69,7 +167,24 @@ declare class OpenKey {
69
167
  */
70
168
  disconnect(): void;
71
169
  }
170
+ /**
171
+ * Unified EIP-1193 provider that wraps an OpenKey instance.
172
+ * Transparently routes signing to either OpenKey (managed keys) or the user's
173
+ * wallet (external keys).
174
+ */
175
+ declare class OpenKeyEIP1193Provider implements EIP1193Provider {
176
+ private openkey;
177
+ private address;
178
+ private keyId;
179
+ private keyType;
180
+ constructor(openkey: OpenKey, authResult: AuthResult);
181
+ request({ method, params }: {
182
+ method: string;
183
+ params?: any[];
184
+ }): Promise<any>;
185
+ private hexToString;
186
+ }
72
187
 
73
188
  declare function getOpenKey(config?: OpenKeyConfig): OpenKey;
74
189
 
75
- export { type AuthResult, OpenKey, type OpenKeyConfig, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
190
+ export { type AuthResult, type EIP1193Provider, type OAuthConfig, type OAuthResult, type OAuthTokenResponse, OpenKey, type OpenKeyConfig, OpenKeyEIP1193Provider, type OpenKeyError, type SignMessageRequest, type SignResult, type SignTypedDataRequest, OpenKey as default, getOpenKey };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- var h="https://openkey.so";var c=class{host;appName;usePopup;popup=null;iframe=null;constructor(e={}){this.host=e.host||h,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0}async connect(){return this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName})}async signMessage(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}async signTypedData(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}async openFlow(e,a){return new Promise((i,t)=>{let r=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(r,a,i,t):this.openIframe(r,a,i,t)})}openPopup(e,a,i,t){let r=window.screenX+(window.outerWidth-400)/2,d=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${r},top=${d},popup=true`),!this.popup){t({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let o=()=>{window.removeEventListener("message",p),clearInterval(g),clearTimeout(u)},g=setInterval(()=>{this.popup?.closed&&(o(),t({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{o(),this.popup?.close(),t({code:"TIMEOUT",message:"Request timed out"})},3e5),p=s=>{if(s.origin!==this.host)return;let n=s.data;if(n.type==="openkey:ready"){this.popup?.postMessage(a,this.host);return}if(n.type==="openkey:close"){o(),this.popup?.close(),t({code:"USER_CANCELLED",message:"User cancelled the request"});return}(n.type==="openkey:auth:response"||n.type==="openkey:sign:response"||n.type==="openkey:signTypedData:response")&&(o(),this.popup?.close(),n.success?n.type==="openkey:auth:response"?i({address:n.address,keyId:n.keyId}):i({signature:n.signature,address:n.address}):t(n.error))};window.addEventListener("message",p)}openIframe(e,a,i,t){let r=document.createElement("div");r.style.cssText=`
1
+ "use strict";var w=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var R=(a,e)=>{for(var t in e)w(a,t,{get:e[t],enumerable:!0})},S=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of U(e))!_.call(a,r)&&r!==t&&w(a,r,{get:()=>e[r],enumerable:!(s=O(e,r))||s.enumerable});return a};var D=a=>S(w({},"__esModule",{value:!0}),a);var W={};R(W,{OpenKey:()=>m,OpenKeyEIP1193Provider:()=>E,default:()=>M,getOpenKey:()=>H});module.exports=D(W);var x="https://openkey.so";var P="openkey_oauth";function k(a){return btoa(String.fromCharCode(...a)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function C(){let a=new Uint8Array(32);return crypto.getRandomValues(a),k(a)}async function L(a){let t=new TextEncoder().encode(a),s=await crypto.subtle.digest("SHA-256",t);return k(new Uint8Array(s))}function N(){let a=new Uint8Array(16);return crypto.getRandomValues(a),k(a)}var m=class{host;appName;usePopup;config;popup=null;iframe=null;lastAuth=null;discoveredProviders=[];constructor(e={}){this.config=e,this.host=e.host||x,this.appName=e.appName||window.location.hostname,this.usePopup=e.usePopup??!0,typeof window<"u"&&(this.discoveredProviders=[],window.addEventListener("eip6963:announceProvider",t=>{this.discoveredProviders.push(t.detail)}),window.dispatchEvent(new Event("eip6963:requestProvider")))}async connect(){let e=await this.openFlow("connect",{type:"openkey:auth:request",appName:this.appName});return this.lastAuth=e,e}async linkWallet(){return this.openFlow("link-wallet",{type:"openkey:link-wallet:request"})}async signMessage(e){var t;return((t=this.lastAuth)==null?void 0:t.keyType)==="EXTERNAL"?this.signWithExternalWallet(e):this.signWithPopup(e)}async signTypedData(e){var t;return((t=this.lastAuth)==null?void 0:t.keyType)==="EXTERNAL"?this.signTypedDataWithExternalWallet(e):this.signTypedDataWithPopup(e)}async findWalletProvider(e){if(this.config.externalProvider)return this.config.externalProvider;for(let{provider:t}of this.discoveredProviders)try{if((await t.request({method:"eth_accounts"})).some(r=>r.toLowerCase()===e.toLowerCase()))return t}catch{}if(typeof window<"u"&&window.ethereum)return window.ethereum;throw new Error("No wallet provider found for external key. Connect your wallet.")}async signWithExternalWallet(e){let t=await this.findWalletProvider(this.lastAuth.address),s=this.toHex(e.message);return{signature:await t.request({method:"personal_sign",params:[s,this.lastAuth.address]}),address:this.lastAuth.address}}async signTypedDataWithExternalWallet(e){return{signature:await(await this.findWalletProvider(this.lastAuth.address)).request({method:"eth_signTypedData_v4",params:[this.lastAuth.address,JSON.stringify(e)]}),address:this.lastAuth.address}}signWithPopup(e){return this.openFlow("sign",{type:"openkey:sign:request",message:e.message,keyId:e.keyId})}signTypedDataWithPopup(e){return this.openFlow("sign-typed-data",{type:"openkey:signTypedData:request",data:e})}toHex(e){return"0x"+Array.from(new TextEncoder().encode(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async isConnected(){try{let e=await fetch(`${this.host}/api/auth/session`,{credentials:"include"});return e.ok?!!(await e.json()).user:!1}catch{return!1}}oauth={connect:async e=>{let t=await C(),s=await L(t),r=e.state||N();sessionStorage.setItem(P,JSON.stringify({verifier:t,state:r}));let n=new URL(`${this.host}/api/auth/oauth2/authorize`);return n.searchParams.set("client_id",e.clientId),n.searchParams.set("redirect_uri",e.redirectUri),n.searchParams.set("response_type","code"),n.searchParams.set("scope","openid"),n.searchParams.set("state",r),n.searchParams.set("code_challenge",s),n.searchParams.set("code_challenge_method","S256"),this.openOAuthFlow(n.toString(),r,e.redirectUri)},exchangeCode:async(e,t)=>{let s=sessionStorage.getItem(P);if(!s)throw new Error("No PKCE verifier found. Start a new OAuth flow.");let{verifier:r}=JSON.parse(s),n=await fetch(`${this.host}/api/auth/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:r})});if(sessionStorage.removeItem(P),!n.ok){let c=await n.json();throw new Error(c.error_description||"Token exchange failed")}return n.json()},verifyState:e=>{let t=sessionStorage.getItem(P);if(!t)return!1;let{state:s}=JSON.parse(t);return s===e},parseCallback:e=>{let t=new URL(e||window.location.href),s=t.searchParams.get("error");if(s)return{error:s,errorDescription:t.searchParams.get("error_description")||void 0};let r=t.searchParams.get("code"),n=t.searchParams.get("state");return!r||!n?{error:"missing_params",errorDescription:"Missing code or state parameter"}:{code:r,state:n}}};openOAuthFlow(e,t,s){return new Promise((r,n)=>{this.usePopup?this.openOAuthPopup(e,t,s,r,n):window.location.href=e})}openOAuthPopup(e,t,s,r,n){let c=window.screenX+(window.outerWidth-400)/2,p=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey-oauth",`width=400,height=600,left=${c},top=${p},popup=true`),!this.popup){n({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups or use redirect mode."});return}let h=new URL(s).origin,u=setInterval(()=>{var i,o,l,y;try{if((i=this.popup)!=null&&i.closed){clearInterval(u),clearTimeout(d),n({code:"USER_CANCELLED",message:"User closed the popup"});return}let g=(l=(o=this.popup)==null?void 0:o.location)==null?void 0:l.href;if(g&&g.startsWith(h)){clearInterval(u),clearTimeout(d);let f=new URL(g),v=f.searchParams.get("code"),I=f.searchParams.get("state"),A=f.searchParams.get("error");if((y=this.popup)==null||y.close(),A){n({code:"UNAUTHORIZED",message:f.searchParams.get("error_description")||A});return}if(I!==t){n({code:"STATE_MISMATCH",message:"State mismatch - possible CSRF attack"});return}v?r({code:v,state:I}):n({code:"UNAUTHORIZED",message:"Authorization failed - no code returned"})}}catch{}},100),d=setTimeout(()=>{var i;clearInterval(u),(i=this.popup)==null||i.close(),n({code:"TIMEOUT",message:"OAuth flow timed out"})},3e5)}async openFlow(e,t){return new Promise((s,r)=>{let n=`${this.host}/widget/${e}?origin=${encodeURIComponent(window.location.origin)}`;this.usePopup?this.openPopup(n,t,s,r):this.openIframe(n,t,s,r)})}openPopup(e,t,s,r){let n=window.screenX+(window.outerWidth-400)/2,c=window.screenY+(window.outerHeight-600)/2;if(this.popup=window.open(e,"openkey",`width=400,height=600,left=${n},top=${c},popup=true`),!this.popup){r({code:"POPUP_BLOCKED",message:"Popup was blocked. Please allow popups for this site."});return}let p=()=>{window.removeEventListener("message",d),clearInterval(h),clearTimeout(u)},h=setInterval(()=>{var i;(i=this.popup)!=null&&i.closed&&(p(),r({code:"USER_CANCELLED",message:"User closed the popup"}))},500),u=setTimeout(()=>{var i;p(),(i=this.popup)==null||i.close(),r({code:"TIMEOUT",message:"Request timed out"})},3e5),d=i=>{var l,y,g;if(i.origin!==this.host)return;let o=i.data;if(o.type==="openkey:ready"){(l=this.popup)==null||l.postMessage(t,this.host);return}if(o.type==="openkey:close"){p(),(y=this.popup)==null||y.close(),r({code:"USER_CANCELLED",message:"User cancelled the request"});return}(o.type==="openkey:auth:response"||o.type==="openkey:sign:response"||o.type==="openkey:signTypedData:response"||o.type==="openkey:link-wallet:response")&&(p(),(g=this.popup)==null||g.close(),o.success?o.type==="openkey:auth:response"?s({address:o.address,keyId:o.keyId,keyType:o.keyType||"MANAGED"}):o.type==="openkey:link-wallet:response"?s({address:o.address,keyId:o.keyId}):s({signature:o.signature,address:o.address}):r(o.error))};window.addEventListener("message",d)}openIframe(e,t,s,r){let n=document.createElement("div");n.style.cssText=`
2
2
  position: fixed;
3
3
  inset: 0;
4
4
  background: rgba(0, 0, 0, 0.5);
@@ -6,7 +6,7 @@ var h="https://openkey.so";var c=class{host;appName;usePopup;popup=null;iframe=n
6
6
  align-items: center;
7
7
  justify-content: center;
8
8
  z-index: 999999;
9
- `;let d=document.createElement("div");d.style.cssText=`
9
+ `;let c=document.createElement("div");c.style.cssText=`
10
10
  width: 400px;
11
11
  height: 600px;
12
12
  background: #1a1a1a;
@@ -17,5 +17,5 @@ var h="https://openkey.so";var c=class{host;appName;usePopup;popup=null;iframe=n
17
17
  width: 100%;
18
18
  height: 100%;
19
19
  border: none;
20
- `,d.appendChild(this.iframe),r.appendChild(d),document.body.appendChild(r);let o=()=>{window.removeEventListener("message",u),clearTimeout(g),r.remove(),this.iframe=null};r.addEventListener("click",p=>{p.target===r&&(o(),t({code:"USER_CANCELLED",message:"User cancelled the request"}))});let g=setTimeout(()=>{o(),t({code:"TIMEOUT",message:"Request timed out"})},3e5),u=p=>{if(p.origin!==this.host)return;let s=p.data;if(s.type==="openkey:ready"){this.iframe?.contentWindow?.postMessage(a,this.host);return}if(s.type==="openkey:close"){o(),t({code:"USER_CANCELLED",message:"User cancelled the request"});return}(s.type==="openkey:auth:response"||s.type==="openkey:sign:response"||s.type==="openkey:signTypedData:response")&&(o(),s.success?s.type==="openkey:auth:response"?i({address:s.address,keyId:s.keyId}):i({signature:s.signature,address:s.address}):t(s.error))};window.addEventListener("message",u)}disconnect(){this.popup?.close(),this.popup=null,this.iframe?.parentElement?.parentElement?.remove(),this.iframe=null}},m=c,l=null;function T(y){return(!l||y)&&(l=new c(y)),l}export{c as OpenKey,m as default,T as getOpenKey};
20
+ `,c.appendChild(this.iframe),n.appendChild(c),document.body.appendChild(n);let p=()=>{window.removeEventListener("message",u),clearTimeout(h),n.remove(),this.iframe=null};n.addEventListener("click",d=>{d.target===n&&(p(),r({code:"USER_CANCELLED",message:"User cancelled the request"}))});let h=setTimeout(()=>{p(),r({code:"TIMEOUT",message:"Request timed out"})},3e5),u=d=>{var o,l;if(d.origin!==this.host)return;let i=d.data;if(i.type==="openkey:ready"){(l=(o=this.iframe)==null?void 0:o.contentWindow)==null||l.postMessage(t,this.host);return}if(i.type==="openkey:close"){p(),r({code:"USER_CANCELLED",message:"User cancelled the request"});return}(i.type==="openkey:auth:response"||i.type==="openkey:sign:response"||i.type==="openkey:signTypedData:response"||i.type==="openkey:link-wallet:response")&&(p(),i.success?i.type==="openkey:auth:response"?s({address:i.address,keyId:i.keyId,keyType:i.keyType||"MANAGED"}):i.type==="openkey:link-wallet:response"?s({address:i.address,keyId:i.keyId}):s({signature:i.signature,address:i.address}):r(i.error))};window.addEventListener("message",u)}disconnect(){var e,t,s,r;(e=this.popup)==null||e.close(),this.popup=null,(r=(s=(t=this.iframe)==null?void 0:t.parentElement)==null?void 0:s.parentElement)==null||r.remove(),this.iframe=null}},E=class{openkey;address;keyId;keyType;constructor(e,t){this.openkey=e,this.address=t.address,this.keyId=t.keyId,this.keyType=t.keyType}async request({method:e,params:t}){switch(e){case"eth_accounts":case"eth_requestAccounts":return[this.address];case"eth_chainId":return"0x1";case"personal_sign":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let s=this.hexToString(t[0]);return(await this.openkey.signMessage({message:s,keyId:this.keyId})).signature}case"eth_signTypedData_v4":{if(this.keyType==="EXTERNAL")return(await this.openkey.findWalletProvider(this.address)).request({method:e,params:t});let s=JSON.parse(t[1]);return(await this.openkey.signTypedData(s)).signature}default:throw new Error(`Unsupported method: ${e}`)}}hexToString(e){let t=new Uint8Array((e.startsWith("0x")?e.slice(2):e).match(/.{1,2}/g).map(s=>parseInt(s,16)));return new TextDecoder().decode(t)}},M=m,T=null;function H(a){return(!T||a)&&(T=new m(a)),T}0&&(module.exports={OpenKey,OpenKeyEIP1193Provider,getOpenKey});
21
21
  //# sourceMappingURL=index.js.map