@gemini-wallet/core
Advanced tools
Sorry, the diff of this file is too big to display
| export declare const SDK_BACKEND_URL: string; | ||
| export declare const SDK_VERSION = "$SDK_VERSION"; | ||
| //# sourceMappingURL=constants.d.ts.map |
| {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,EAAkC,MAAM,CAAC;AACrE,eAAO,MAAM,WAAW,iBAAiB,CAAC"} |
| import { errorCodes } from "@metamask/rpc-errors"; | ||
| import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; | ||
| import { type Address, type Hex } from "viem"; | ||
| import { DEFAULT_CHAIN_ID } from "../constants"; | ||
| import { GeminiStorage } from "../storage"; | ||
| import { type Chain, type GeminiProviderConfig } from "../types"; | ||
| import { hexStringFromNumber } from "../utils"; | ||
| // import { GeminiWallet } from "../wallets"; // Not used - using MockGeminiWallet instead | ||
| import { GeminiWalletProvider } from "./provider"; | ||
| const mockAddress = "0xAfEDA61dB9e162293b2eF2C2bC5A800b37Bb5E4a" as Address; | ||
| const mockTxHash = "0x5de3752c591ecc35d1046f3aca2eba1ba5bdcfb786639a8661e9ecb823675743" as Hex; | ||
| const mockSigHash = "0x020d671b80fbd20466d8cb65cef79a24e3bca3fdf82e9dd89d78e7a4c4c045b" as Hex; | ||
| // Mock dependencies - Create a more comprehensive mock of GeminiWallet | ||
| class MockGeminiWallet { | ||
| accounts: Address[] = []; | ||
| chain: Chain = { id: DEFAULT_CHAIN_ID }; | ||
| constructor(public config: GeminiProviderConfig) {} | ||
| connect() { | ||
| this.accounts = [mockAddress]; | ||
| return Promise.resolve(this.accounts); | ||
| } | ||
| disconnect() { | ||
| this.accounts = []; | ||
| return Promise.resolve(); | ||
| } | ||
| sendTransaction(params: any) { | ||
| if (params.from === mockAddress) { | ||
| return Promise.resolve({ hash: mockTxHash }); | ||
| } | ||
| return Promise.resolve({ error: "Invalid address" }); | ||
| } | ||
| signData(params: any) { | ||
| if (params.account === mockAddress) { | ||
| return Promise.resolve({ hash: mockSigHash }); | ||
| } | ||
| return Promise.resolve({ error: "Invalid address" }); | ||
| } | ||
| signTypedData(params: any) { | ||
| if (params.account === mockAddress) { | ||
| return Promise.resolve({ hash: mockSigHash }); | ||
| } | ||
| return Promise.resolve({ error: "Invalid address" }); | ||
| } | ||
| switchChain(params: { id: number }) { | ||
| if (params.id === 1 || params.id === 42161) { | ||
| this.chain = { id: params.id }; | ||
| return Promise.resolve(null); // Success | ||
| } | ||
| return Promise.resolve("Unsupported chain"); | ||
| } | ||
| } | ||
| // Mock the wallet module | ||
| mock.module("../wallets", () => ({ | ||
| GeminiWallet: MockGeminiWallet, | ||
| })); | ||
| describe("GeminiWalletProvider", () => { | ||
| let provider: GeminiWalletProvider; | ||
| let mockStorage: GeminiStorage; | ||
| let providerConfig: GeminiProviderConfig; | ||
| beforeEach(() => { | ||
| mockStorage = new GeminiStorage(); | ||
| providerConfig = { | ||
| appMetadata: { appName: "Test App" }, | ||
| chain: { id: DEFAULT_CHAIN_ID }, | ||
| onDisconnectCallback: mock(), | ||
| storage: mockStorage, | ||
| }; | ||
| provider = new GeminiWalletProvider(providerConfig); | ||
| }); | ||
| afterEach(() => { | ||
| mock.restore(); | ||
| }); | ||
| describe("constructor", () => { | ||
| it("should create a provider instance with config", () => { | ||
| expect(provider).toBeDefined(); | ||
| expect(provider).toBeInstanceOf(GeminiWalletProvider); | ||
| }); | ||
| it("should preserve user disconnect callback", async () => { | ||
| const disconnectCallback = mock(); | ||
| const customConfig = { ...providerConfig, onDisconnectCallback: disconnectCallback }; | ||
| const customProvider = new GeminiWalletProvider(customConfig); | ||
| // Trigger disconnect | ||
| await customProvider.disconnect(); | ||
| // User callback should be preserved | ||
| expect(disconnectCallback).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| describe("eth_requestAccounts", () => { | ||
| it("should connect wallet and return accounts", async () => { | ||
| const accounts = await provider.request<Address[]>({ | ||
| method: "eth_requestAccounts", | ||
| }); | ||
| expect(accounts).toEqual([mockAddress]); | ||
| }); | ||
| it("should emit accountsChanged event on connect", async () => { | ||
| const accountsChangedHandler = mock(); | ||
| provider.on("accountsChanged", accountsChangedHandler); | ||
| await provider.request({ | ||
| method: "eth_requestAccounts", | ||
| }); | ||
| expect(accountsChangedHandler).toHaveBeenCalledWith([mockAddress]); | ||
| }); | ||
| }); | ||
| describe("eth_accounts", () => { | ||
| it("should return empty array when not connected", async () => { | ||
| try { | ||
| await provider.request({ method: "eth_accounts" }); | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.provider.unauthorized); | ||
| } | ||
| }); | ||
| it("should return accounts when connected", async () => { | ||
| // Connect first | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const accounts = await provider.request<Address[]>({ | ||
| method: "eth_accounts", | ||
| }); | ||
| expect(accounts).toEqual([mockAddress]); | ||
| }); | ||
| }); | ||
| describe("eth_chainId", () => { | ||
| it("should return default chain ID when not connected", async () => { | ||
| const chainId = await provider.request<string>({ | ||
| method: "eth_chainId", | ||
| }); | ||
| expect(chainId).toBe(hexStringFromNumber(DEFAULT_CHAIN_ID)); | ||
| }); | ||
| it("should return current chain ID when connected", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const chainId = await provider.request<string>({ | ||
| method: "eth_chainId", | ||
| }); | ||
| expect(chainId).toBe(hexStringFromNumber(DEFAULT_CHAIN_ID)); | ||
| }); | ||
| }); | ||
| describe("net_version", () => { | ||
| it("should return default chain ID when not connected", async () => { | ||
| const netVersion = await provider.request<number>({ | ||
| method: "net_version", | ||
| }); | ||
| expect(netVersion).toBe(DEFAULT_CHAIN_ID); | ||
| }); | ||
| it("should return current chain ID when connected", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const netVersion = await provider.request<number>({ | ||
| method: "net_version", | ||
| }); | ||
| expect(netVersion).toBe(DEFAULT_CHAIN_ID); | ||
| }); | ||
| }); | ||
| describe("personal_sign", () => { | ||
| it("should throw when not connected", async () => { | ||
| try { | ||
| await provider.request({ | ||
| method: "personal_sign", | ||
| params: ["0x123456", mockAddress], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.provider.unauthorized); | ||
| } | ||
| }); | ||
| it("should sign message when connected", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const signature = await provider.request<Hex>({ | ||
| method: "personal_sign", | ||
| params: ["0x123456" as Hex, mockAddress], | ||
| }); | ||
| expect(signature).toBe(mockSigHash); | ||
| }); | ||
| it("should throw on signature error", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| try { | ||
| await provider.request({ | ||
| method: "personal_sign", | ||
| params: ["0x123456" as Hex, "0xinvalidaddress" as Address], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.rpc.transactionRejected); | ||
| } | ||
| }); | ||
| }); | ||
| describe("eth_sendTransaction", () => { | ||
| it("should throw when not connected", async () => { | ||
| try { | ||
| await provider.request({ | ||
| method: "eth_sendTransaction", | ||
| params: [{ from: mockAddress, to: mockAddress, value: "0x0" }], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.provider.unauthorized); | ||
| } | ||
| }); | ||
| it("should send transaction when connected", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const txHash = await provider.request<Hex>({ | ||
| method: "eth_sendTransaction", | ||
| params: [{ from: mockAddress, to: mockAddress, value: "0x100" }], | ||
| }); | ||
| expect(txHash).toBe(mockTxHash); | ||
| }); | ||
| it("should convert hex values to bigint", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const txHash = await provider.request<Hex>({ | ||
| method: "eth_sendTransaction", | ||
| params: [ | ||
| { | ||
| from: mockAddress, | ||
| gas: "0x5208", | ||
| gasPrice: "0x3b9aca00", | ||
| to: mockAddress, | ||
| value: "0x100", | ||
| }, | ||
| ], | ||
| }); | ||
| expect(txHash).toBe(mockTxHash); | ||
| }); | ||
| it("should throw on transaction error", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| try { | ||
| await provider.request({ | ||
| method: "eth_sendTransaction", | ||
| params: [{ from: "0xinvalidaddress" as Address, to: mockAddress, value: "0x0" }], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.rpc.transactionRejected); | ||
| } | ||
| }); | ||
| }); | ||
| describe("wallet_switchEthereumChain", () => { | ||
| it("should throw when not connected", async () => { | ||
| try { | ||
| await provider.request({ | ||
| method: "wallet_switchEthereumChain", | ||
| params: [{ chainId: "0x1" }], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.provider.unauthorized); | ||
| } | ||
| }); | ||
| it("should switch chain with standard EIP-3326 format", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const chainChangedHandler = mock(); | ||
| provider.on("chainChanged", chainChangedHandler); | ||
| await provider.request({ | ||
| method: "wallet_switchEthereumChain", | ||
| params: [{ chainId: "0x1" }], | ||
| }); | ||
| expect(chainChangedHandler).toHaveBeenCalledWith("0x1"); | ||
| }); | ||
| it("should switch chain with legacy format", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const chainChangedHandler = mock(); | ||
| provider.on("chainChanged", chainChangedHandler); | ||
| await provider.request({ | ||
| method: "wallet_switchEthereumChain", | ||
| params: { id: 1 } as any, | ||
| }); | ||
| expect(chainChangedHandler).toHaveBeenCalledWith("0x1"); | ||
| }); | ||
| it("should throw on unsupported chain", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| try { | ||
| await provider.request({ | ||
| method: "wallet_switchEthereumChain", | ||
| params: [{ chainId: "0x999" }], | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(4902); | ||
| expect(error.message).toContain("Unsupported chain"); | ||
| } | ||
| }); | ||
| it("should throw on invalid parameters", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| try { | ||
| await provider.request({ | ||
| method: "wallet_switchEthereumChain", | ||
| params: "invalid" as any, | ||
| }); | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.rpc.invalidParams); | ||
| } | ||
| }); | ||
| }); | ||
| describe("eth_signTypedData", () => { | ||
| it("should sign typed data when connected", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const signature = await provider.request<Hex>({ | ||
| method: "eth_signTypedData_v4", | ||
| params: [mockAddress, JSON.stringify({ domain: {}, message: {}, primaryType: "Test", types: {} })], | ||
| }); | ||
| expect(signature).toBe(mockSigHash); | ||
| }); | ||
| it("should throw when not connected", async () => { | ||
| try { | ||
| await provider.request({ | ||
| method: "eth_signTypedData_v4", | ||
| params: [mockAddress, JSON.stringify({ domain: {}, message: {}, primaryType: "Test", types: {} })], | ||
| }); | ||
| expect(true).toBe(false); | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.provider.unauthorized); | ||
| } | ||
| }); | ||
| }); | ||
| describe("disconnect", () => { | ||
| it("should clear accounts on disconnect", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| const accountsChangedHandler = mock(); | ||
| provider.on("accountsChanged", accountsChangedHandler); | ||
| await provider.disconnect(); | ||
| expect(accountsChangedHandler).toHaveBeenCalledWith([]); | ||
| }); | ||
| it("should trigger user disconnect callback", async () => { | ||
| const disconnectCallback = mock(); | ||
| const customConfig = { ...providerConfig, onDisconnectCallback: disconnectCallback }; | ||
| const customProvider = new GeminiWalletProvider(customConfig); | ||
| await customProvider.disconnect(); | ||
| expect(disconnectCallback).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
| describe("request validation", () => { | ||
| it("should throw on invalid method type", async () => { | ||
| try { | ||
| await provider.request({ method: 123 as any }); | ||
| expect(true).toBe(false); | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.rpc.invalidParams); | ||
| } | ||
| }); | ||
| it("should throw on empty method", async () => { | ||
| try { | ||
| await provider.request({ method: "" }); | ||
| expect(true).toBe(false); | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(errorCodes.rpc.invalidParams); | ||
| } | ||
| }); | ||
| }); | ||
| describe("unsupported methods", () => { | ||
| it("should return unsupported error for unknown methods", async () => { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| // Mock fetch to return method not found error | ||
| const originalFetch = (global as any).window.fetch; | ||
| (global as any).window.fetch = mock(() => | ||
| Promise.resolve({ | ||
| json: () => | ||
| Promise.resolve({ | ||
| error: { code: -32601, message: "Method not found" }, | ||
| }), | ||
| }), | ||
| ) as any; | ||
| try { | ||
| await provider.request({ method: "unsupported_method" }); | ||
| expect(true).toBe(false); | ||
| } catch (error: any) { | ||
| // RPC returns -32603 (internal error) for unknown methods | ||
| expect(error.code).toBe(-32603); | ||
| } finally { | ||
| (global as any).window.fetch = originalFetch; | ||
| } | ||
| }); | ||
| }); | ||
| }); |
| import { beforeEach, describe, expect, it, mock } from "bun:test"; | ||
| import { convertSendValuesToBigInt, fetchRpcRequest, validateRpcRequestArgs } from "./provider.utils"; | ||
| // Set up global window mock before tests | ||
| (global as any).window = { | ||
| fetch: () => Promise.resolve({ json: () => Promise.resolve({ result: "success" }) }), | ||
| }; | ||
| describe("Provider utils", () => { | ||
| describe("validateRpcRequestArgs()", () => { | ||
| it("should throw an error if args is null", () => { | ||
| expect(() => validateRpcRequestArgs(null)).toThrow("Expected a single, non-array, object argument."); | ||
| }); | ||
| it("should throw an error if args is not an object", () => { | ||
| expect(() => validateRpcRequestArgs(42)).toThrow("Expected a single, non-array, object argument."); | ||
| }); | ||
| it("should throw an error if args is an array", () => { | ||
| expect(() => validateRpcRequestArgs([])).toThrow("Expected a single, non-array, object argument."); | ||
| }); | ||
| it("should throw an error if method is missing", () => { | ||
| expect(() => validateRpcRequestArgs({})).toThrow("'args.method' must be a non-empty string."); | ||
| }); | ||
| it("should throw an error if method is not a string", () => { | ||
| expect(() => validateRpcRequestArgs({ method: 123 })).toThrow("'args.method' must be a non-empty string."); | ||
| }); | ||
| it("should throw an error if method is an empty string", () => { | ||
| expect(() => validateRpcRequestArgs({ method: "" })).toThrow("'args.method' must be a non-empty string."); | ||
| }); | ||
| it("should throw an error if params is not an object or array", () => { | ||
| expect(() => validateRpcRequestArgs({ method: "testMethod", params: 123 })).toThrow( | ||
| "'args.params' must be an object or array if provided.", | ||
| ); | ||
| }); | ||
| it("should not throw an error for a valid request with an array params", () => { | ||
| expect(() => validateRpcRequestArgs({ method: "testMethod", params: [] })).not.toThrow(); | ||
| }); | ||
| it("should not throw an error for a valid request with an object params", () => { | ||
| expect(() => validateRpcRequestArgs({ method: "testMethod", params: { key: "value" } })).not.toThrow(); | ||
| }); | ||
| it("should not throw an error for a valid request without params", () => { | ||
| expect(() => validateRpcRequestArgs({ method: "testMethod" })).not.toThrow(); | ||
| }); | ||
| }); | ||
| describe("fetchRpcRequest()", () => { | ||
| beforeEach(() => { | ||
| // Mock global fetch | ||
| (global as any).fetch = mock(() => | ||
| Promise.resolve({ | ||
| json: mock(() => Promise.resolve({ result: "success" })), | ||
| }), | ||
| ); | ||
| }); | ||
| it("should send a valid RPC request and return the result", async () => { | ||
| const mockResponse = { result: "success" }; | ||
| const mockJsonFn = mock(() => Promise.resolve(mockResponse)); | ||
| const mockFetchFn = mock(() => | ||
| Promise.resolve({ | ||
| json: mockJsonFn, | ||
| }), | ||
| ); | ||
| // Set up the window fetch mock | ||
| (global as any).window.fetch = mockFetchFn; | ||
| const request = { method: "testMethod", params: [] }; | ||
| const rpcUrl = "https://example.com"; | ||
| const result = await fetchRpcRequest(request, rpcUrl); | ||
| expect(mockFetchFn).toHaveBeenCalledWith( | ||
| rpcUrl, | ||
| expect.objectContaining({ | ||
| body: expect.stringContaining('"method":"testMethod"'), | ||
| headers: { "Content-Type": "application/json" }, | ||
| method: "POST", | ||
| mode: "cors", | ||
| }), | ||
| ); | ||
| expect(result).toEqual("success"); | ||
| }); | ||
| it("should throw an error if the response contains an error field", async () => { | ||
| const mockErrorResponse = { error: { message: "Something went wrong" } }; | ||
| const mockJsonFn = mock(() => Promise.resolve(mockErrorResponse)); | ||
| const mockFetchFn = mock(() => | ||
| Promise.resolve({ | ||
| json: mockJsonFn, | ||
| }), | ||
| ); | ||
| // Set up the window fetch mock | ||
| (global as any).window.fetch = mockFetchFn; | ||
| const request = { method: "testMethod", params: [] }; | ||
| const rpcUrl = "https://example.com"; | ||
| await expect(fetchRpcRequest(request, rpcUrl)).rejects.toEqual({ message: "Something went wrong" }); | ||
| }); | ||
| }); | ||
| describe("convertSendValuesToBigInt()", () => { | ||
| it("should convert hex value to BigInt", () => { | ||
| const tx = { value: "0x1234" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.value).toBe(BigInt("0x1234")); | ||
| }); | ||
| it("should convert hex gas to BigInt", () => { | ||
| const tx = { gas: "0x5678" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.gas).toBe(BigInt("0x5678")); | ||
| }); | ||
| it("should convert hex gasPrice to BigInt", () => { | ||
| const tx = { gasPrice: "0xabcd" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.gasPrice).toBe(BigInt("0xabcd")); | ||
| }); | ||
| it("should convert hex maxPriorityFeePerGas to BigInt", () => { | ||
| const tx = { maxPriorityFeePerGas: "0xef01" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.maxPriorityFeePerGas).toBe(BigInt("0xef01")); | ||
| }); | ||
| it("should convert hex maxFeePerGas to BigInt", () => { | ||
| const tx = { maxFeePerGas: "0x2345" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.maxFeePerGas).toBe(BigInt("0x2345")); | ||
| }); | ||
| it("should not modify fields that are already BigInt", () => { | ||
| const originalBigInt = BigInt("0x6789"); | ||
| const tx = { value: originalBigInt } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result.value).toBe(originalBigInt); | ||
| }); | ||
| it("should not modify fields that are not in the list to normalize", () => { | ||
| const tx = { | ||
| data: "0x123456" as `0x${string}`, | ||
| to: "0xabcdef" as `0x${string}`, | ||
| }; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result).toEqual(tx); | ||
| }); | ||
| it("should handle transaction with missing fields", () => { | ||
| const tx = { to: "0xabcdef" as `0x${string}` }; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result).toEqual(tx); | ||
| }); | ||
| it("should convert multiple fields in the same transaction", () => { | ||
| const tx = { | ||
| gas: "0x5678", | ||
| gasPrice: "0xabcd", | ||
| to: "0xdef456" as `0x${string}`, | ||
| value: "0x1234", | ||
| } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result).toEqual({ | ||
| gas: BigInt("0x5678"), | ||
| gasPrice: BigInt("0xabcd"), | ||
| to: "0xdef456", | ||
| value: BigInt("0x1234"), | ||
| }); | ||
| }); | ||
| it("should return a new object without modifying the original", () => { | ||
| const tx = { value: "0x1234" } as any; | ||
| const result = convertSendValuesToBigInt(tx); | ||
| expect(result).not.toBe(tx); | ||
| expect(tx.value).toBe("0x1234"); | ||
| }); | ||
| }); | ||
| }); |
| import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; | ||
| // Mock dependencies | ||
| mock.module("../utils", () => ({ | ||
| safeJsonStringify: mock(obj => JSON.stringify(obj)), | ||
| })); | ||
| import { safeJsonStringify } from "../utils"; | ||
| import { GeminiStorage } from "./storage"; | ||
| import { | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| STORAGE_PASSKEY_CREDENTIAL_KEY, | ||
| STORAGE_SETTINGS_KEY, | ||
| STORAGE_SMART_ACCOUNT_KEY, | ||
| STORAGE_WC_REQUESTS_KEY, | ||
| } from "./storageInterface"; | ||
| describe("GeminiStorage", () => { | ||
| // Mock localStorage | ||
| const mockLocalStorage = { | ||
| _storage: {} as Record<string, string>, | ||
| clear: mock(), | ||
| getItem: mock(() => null), | ||
| removeItem: mock(), | ||
| setItem: mock(), | ||
| }; | ||
| // Save original localStorage | ||
| const originalLocalStorage = global.localStorage; | ||
| beforeEach(() => { | ||
| // Reset mock call counts | ||
| mockLocalStorage.getItem.mockClear(); | ||
| mockLocalStorage.setItem.mockClear(); | ||
| mockLocalStorage.removeItem.mockClear(); | ||
| // Reset storage state | ||
| mockLocalStorage._storage = {}; | ||
| // Replace global localStorage with mock | ||
| Object.defineProperty(global, "localStorage", { | ||
| value: mockLocalStorage, | ||
| writable: true, | ||
| }); | ||
| }); | ||
| afterEach(() => { | ||
| // Restore original localStorage | ||
| Object.defineProperty(global, "localStorage", { | ||
| value: originalLocalStorage, | ||
| writable: true, | ||
| }); | ||
| }); | ||
| describe("constructor", () => { | ||
| it("should initialize with default scope and module", async () => { | ||
| const storage = new GeminiStorage(); | ||
| // Test the scopedKey method indirectly | ||
| await storage.setItem("test", "value"); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.test", "value"); | ||
| }); | ||
| }); | ||
| describe("storeObject", () => { | ||
| it("should store an object as JSON string", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const testObject = { foo: "bar", num: 123 }; | ||
| await storage.storeObject("testKey", testObject); | ||
| expect(safeJsonStringify).toHaveBeenCalledWith(testObject); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.testKey", JSON.stringify(testObject)); | ||
| }); | ||
| it("should handle complex objects with bigint values", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const testObject = { id: "test", value: 123n }; | ||
| // Mock safeJsonStringify for this specific test | ||
| (safeJsonStringify as any).mockReturnValueOnce('{"id":"test","value":"123n"}'); | ||
| await storage.storeObject("testKey", testObject); | ||
| expect(safeJsonStringify).toHaveBeenCalledWith(testObject); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.testKey", '{"id":"test","value":"123n"}'); | ||
| }); | ||
| }); | ||
| describe("loadObject", () => { | ||
| it("should load and parse a stored object", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const storedJson = '{"foo":"bar","num":123}'; | ||
| mockLocalStorage.getItem.mockReturnValueOnce(storedJson); | ||
| const result = await storage.loadObject("testKey", { default: true }); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.testKey"); | ||
| expect(result).toEqual({ foo: "bar", num: 123 }); | ||
| }); | ||
| it("should return fallback when item doesn't exist", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const fallback = { default: true }; | ||
| mockLocalStorage.getItem.mockReturnValueOnce(null); | ||
| const result = await storage.loadObject("nonExistentKey", fallback); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.nonExistentKey"); | ||
| expect(result).toEqual(fallback); | ||
| }); | ||
| it("should handle JSON parse errors and return fallback", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const fallback = { default: true }; | ||
| // Return invalid JSON | ||
| mockLocalStorage.getItem.mockReturnValueOnce("{invalid:json}"); | ||
| // Mock console.error to avoid test output pollution | ||
| const consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {}); | ||
| const result = await storage.loadObject("invalidJsonKey", fallback); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.invalidJsonKey"); | ||
| expect(result).toEqual(fallback); | ||
| // Verify error was logged | ||
| expect(consoleErrorSpy).toHaveBeenCalled(); | ||
| consoleErrorSpy.mockRestore(); | ||
| }); | ||
| }); | ||
| describe("setItem", () => { | ||
| it("should set item with scoped key", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.setItem("testKey", "testValue"); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.testKey", "testValue"); | ||
| }); | ||
| it("should handle empty string values", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.setItem("emptyKey", ""); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.emptyKey", ""); | ||
| }); | ||
| }); | ||
| describe("getItem", () => { | ||
| it("should get item with scoped key", async () => { | ||
| const storage = new GeminiStorage(); | ||
| mockLocalStorage.getItem.mockReturnValueOnce("testValue"); | ||
| const result = await storage.getItem("testKey"); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.testKey"); | ||
| expect(result).toBe("testValue"); | ||
| }); | ||
| it("should return null for non-existent items", async () => { | ||
| const storage = new GeminiStorage(); | ||
| mockLocalStorage.getItem.mockReturnValueOnce(null); | ||
| const result = await storage.getItem("nonExistentKey"); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.nonExistentKey"); | ||
| expect(result).toBeNull(); | ||
| }); | ||
| }); | ||
| describe("removeItem", () => { | ||
| it("should remove item with scoped key", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.removeItem("testKey"); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.testKey"); | ||
| }); | ||
| }); | ||
| describe("removeItems", () => { | ||
| it("should remove multiple items", async () => { | ||
| const storage = new GeminiStorage(); | ||
| const keys = ["key1", "key2", "key3"]; | ||
| await storage.removeItems(keys); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledTimes(3); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.key1"); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.key2"); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.key3"); | ||
| }); | ||
| it("should handle empty array", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.removeItems([]); | ||
| expect(mockLocalStorage.removeItem).not.toHaveBeenCalled(); | ||
| }); | ||
| it("should handle single item array", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.removeItems(["singleKey"]); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledTimes(1); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.singleKey"); | ||
| }); | ||
| }); | ||
| describe("storage constants", () => { | ||
| it("should export the correct storage key constants", () => { | ||
| expect(STORAGE_ETH_ACCOUNTS_KEY).toBe("eth-accounts"); | ||
| expect(STORAGE_ETH_ACTIVE_CHAIN_KEY).toBe("eth-active-chain"); | ||
| expect(STORAGE_PASSKEY_CREDENTIAL_KEY).toBe("passkey-credential"); | ||
| expect(STORAGE_SMART_ACCOUNT_KEY).toBe("smart-account"); | ||
| expect(STORAGE_SETTINGS_KEY).toBe("settings"); | ||
| expect(STORAGE_WC_REQUESTS_KEY).toBe("wc-requests"); | ||
| }); | ||
| }); | ||
| describe("integration with storage keys", () => { | ||
| it("should use correct scoped keys for predefined constants", async () => { | ||
| const storage = new GeminiStorage(); | ||
| await storage.setItem(STORAGE_ETH_ACCOUNTS_KEY, "accounts-data"); | ||
| expect(mockLocalStorage.setItem).toHaveBeenCalledWith("@gemini.wallet.eth-accounts", "accounts-data"); | ||
| await storage.getItem(STORAGE_PASSKEY_CREDENTIAL_KEY); | ||
| expect(mockLocalStorage.getItem).toHaveBeenCalledWith("@gemini.wallet.passkey-credential"); | ||
| await storage.removeItem(STORAGE_SETTINGS_KEY); | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("@gemini.wallet.settings"); | ||
| }); | ||
| }); | ||
| describe("memory storage fallback", () => { | ||
| let consoleWarnSpy: any; | ||
| beforeEach(() => { | ||
| // Mock console.warn to avoid test output pollution | ||
| consoleWarnSpy = spyOn(console, "warn").mockImplementation(() => {}); | ||
| }); | ||
| afterEach(() => { | ||
| consoleWarnSpy.mockRestore(); | ||
| }); | ||
| it("should fallback to memory storage when localStorage throws error", async () => { | ||
| const storage = new GeminiStorage(); | ||
| // Make localStorage methods throw errors | ||
| mockLocalStorage.setItem.mockImplementation(() => { | ||
| throw new Error("localStorage not available"); | ||
| }); | ||
| mockLocalStorage.getItem.mockImplementation(() => { | ||
| throw new Error("localStorage not available"); | ||
| }); | ||
| mockLocalStorage.removeItem.mockImplementation(() => { | ||
| throw new Error("localStorage not available"); | ||
| }); | ||
| // Test setItem fallback | ||
| await storage.setItem("testKey", "testValue"); | ||
| expect(consoleWarnSpy).toHaveBeenCalledWith( | ||
| "localStorage not available, using memory storage", | ||
| expect.any(Error), | ||
| ); | ||
| // Test getItem fallback - should return value from memory storage | ||
| const value = await storage.getItem("testKey"); | ||
| expect(value).toBe("testValue"); | ||
| // Test removeItem fallback | ||
| await storage.removeItem("testKey"); | ||
| const removedValue = await storage.getItem("testKey"); | ||
| expect(removedValue).toBeNull(); | ||
| }); | ||
| it("should use memory storage for removeItems when localStorage throws", async () => { | ||
| const storage = new GeminiStorage(); | ||
| // Make localStorage.removeItem throw error | ||
| mockLocalStorage.removeItem.mockImplementation(() => { | ||
| throw new Error("localStorage not available"); | ||
| }); | ||
| const keys = ["key1", "key2"]; | ||
| await storage.removeItems(keys); | ||
| expect(consoleWarnSpy).toHaveBeenCalledTimes(2); // Once for each key | ||
| expect(mockLocalStorage.removeItem).toHaveBeenCalledTimes(2); | ||
| }); | ||
| }); | ||
| }); |
| import { describe, expect, it } from "bun:test"; | ||
| import { base64ToHex, bufferToBase64URLString, decodeBase64, encodeBase64, utf8StringToBuffer } from "./base64"; | ||
| describe("Base64 utilities", () => { | ||
| describe("encodeBase64", () => { | ||
| it("should encode Uint8Array to base64url string", () => { | ||
| const input = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" | ||
| const result = encodeBase64(input); | ||
| expect(result).toBe("SGVsbG8"); // base64url encoded "Hello" (no padding) | ||
| }); | ||
| it("should handle empty array", () => { | ||
| const input = new Uint8Array([]); | ||
| const result = encodeBase64(input); | ||
| expect(result).toBe(""); | ||
| }); | ||
| it("should remove padding from base64 output", () => { | ||
| const input = new Uint8Array([72, 101]); // "He" | ||
| const result = encodeBase64(input); | ||
| expect(result).toBe("SGU"); // Should not have "=" padding | ||
| }); | ||
| it("should replace + with - and / with _ for base64url", () => { | ||
| // Create bytes that will produce + and / in base64 | ||
| const input = new Uint8Array([255, 255, 255]); // Will produce "///" in base64 | ||
| const result = encodeBase64(input); | ||
| expect(result).not.toContain("+"); | ||
| expect(result).not.toContain("/"); | ||
| expect(result).toBe("____"); // base64url version (with padding removed) | ||
| }); | ||
| it("should handle binary data correctly", () => { | ||
| const input = new Uint8Array([0, 1, 2, 3, 255, 254, 253]); | ||
| const result = encodeBase64(input); | ||
| expect(typeof result).toBe("string"); | ||
| expect(result.length).toBeGreaterThan(0); | ||
| // Verify it doesn't contain base64 special characters | ||
| expect(result).not.toMatch(/[+/=]/); | ||
| }); | ||
| it("should produce consistent results", () => { | ||
| const input = new Uint8Array([1, 2, 3, 4, 5]); | ||
| const result1 = encodeBase64(input); | ||
| const result2 = encodeBase64(input); | ||
| expect(result1).toBe(result2); | ||
| }); | ||
| }); | ||
| describe("decodeBase64", () => { | ||
| it("should decode base64url string to Uint8Array", () => { | ||
| const input = "SGVsbG8"; // base64url encoded "Hello" | ||
| const result = decodeBase64(input); | ||
| expect(Array.from(result)).toEqual([72, 101, 108, 108, 111]); // "Hello" in bytes | ||
| }); | ||
| it("should handle empty string", () => { | ||
| const result = decodeBase64(""); | ||
| expect(result).toBeInstanceOf(Uint8Array); | ||
| expect(result.length).toBe(0); | ||
| }); | ||
| it("should convert base64url characters back to base64", () => { | ||
| const input = "____"; // base64url version (4 underscores for proper padding) | ||
| const result = decodeBase64(input); | ||
| expect(Array.from(result)).toEqual([255, 255, 255]); | ||
| }); | ||
| it("should add padding when necessary", () => { | ||
| const input = "SGU"; // "He" without padding | ||
| const result = decodeBase64(input); | ||
| expect(Array.from(result)).toEqual([72, 101]); // "He" in bytes | ||
| }); | ||
| it("should handle strings that need different amounts of padding", () => { | ||
| // Test string needing no padding (length % 4 = 0) | ||
| const noPadding = "SGVsbG8K"; // "Hello\n" base64url | ||
| const result1 = decodeBase64(noPadding); | ||
| expect(Array.from(result1)).toEqual([72, 101, 108, 108, 111, 10]); | ||
| // Test string needing 2 padding characters (length % 4 = 2) | ||
| const twoPadding = "SGU"; // "He" | ||
| const result2 = decodeBase64(twoPadding); | ||
| expect(Array.from(result2)).toEqual([72, 101]); | ||
| // Test string needing 1 padding character (length % 4 = 3) | ||
| const onePadding = "SGVs"; // "Hel" | ||
| const result3 = decodeBase64(onePadding); | ||
| expect(Array.from(result3)).toEqual([72, 101, 108]); | ||
| }); | ||
| it("should be inverse of encodeBase64", () => { | ||
| const original = new Uint8Array([1, 2, 3, 4, 5, 255, 254, 0]); | ||
| const encoded = encodeBase64(original); | ||
| const decoded = decodeBase64(encoded); | ||
| expect(Array.from(decoded)).toEqual(Array.from(original)); | ||
| }); | ||
| }); | ||
| describe("bufferToBase64URLString", () => { | ||
| it("should convert ArrayBuffer to base64url string", () => { | ||
| const buffer = new ArrayBuffer(5); | ||
| const view = new Uint8Array(buffer); | ||
| view.set([72, 101, 108, 108, 111]); // "Hello" | ||
| const result = bufferToBase64URLString(buffer); | ||
| expect(result).toBe("SGVsbG8"); | ||
| }); | ||
| it("should convert Uint8Array to base64url string", () => { | ||
| const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" | ||
| const result = bufferToBase64URLString(uint8Array); | ||
| expect(result).toBe("SGVsbG8"); | ||
| }); | ||
| it("should handle empty ArrayBuffer", () => { | ||
| const buffer = new ArrayBuffer(0); | ||
| const result = bufferToBase64URLString(buffer); | ||
| expect(result).toBe(""); | ||
| }); | ||
| it("should handle empty Uint8Array", () => { | ||
| const uint8Array = new Uint8Array(0); | ||
| const result = bufferToBase64URLString(uint8Array); | ||
| expect(result).toBe(""); | ||
| }); | ||
| it("should handle large buffers", () => { | ||
| const size = 10000; | ||
| const buffer = new ArrayBuffer(size); | ||
| const view = new Uint8Array(buffer); | ||
| for (let i = 0; i < size; i++) { | ||
| view[i] = i % 256; | ||
| } | ||
| const result = bufferToBase64URLString(buffer); | ||
| expect(typeof result).toBe("string"); | ||
| expect(result.length).toBeGreaterThan(0); | ||
| expect(result).not.toMatch(/[+/=]/); // Should be base64url format | ||
| }); | ||
| }); | ||
| describe("utf8StringToBuffer", () => { | ||
| it("should convert ASCII string to Uint8Array", () => { | ||
| const input = "Hello"; | ||
| const result = utf8StringToBuffer(input); | ||
| expect(Array.from(result)).toEqual([72, 101, 108, 108, 111]); | ||
| }); | ||
| it("should handle empty string", () => { | ||
| const result = utf8StringToBuffer(""); | ||
| expect(result).toBeInstanceOf(Uint8Array); | ||
| expect(result.length).toBe(0); | ||
| }); | ||
| it("should handle UTF-8 characters correctly", () => { | ||
| const input = "Hello 世界"; // Mixed ASCII and Chinese characters | ||
| const result = utf8StringToBuffer(input); | ||
| expect(result).toBeInstanceOf(Uint8Array); | ||
| // Convert back to string to verify | ||
| const decoder = new TextDecoder(); | ||
| const decoded = decoder.decode(result); | ||
| expect(decoded).toBe(input); | ||
| }); | ||
| it("should handle emoji correctly", () => { | ||
| const input = "Hello 👋 World 🌍"; | ||
| const result = utf8StringToBuffer(input); | ||
| expect(result).toBeInstanceOf(Uint8Array); | ||
| // Convert back to string to verify | ||
| const decoder = new TextDecoder(); | ||
| const decoded = decoder.decode(result); | ||
| expect(decoded).toBe(input); | ||
| }); | ||
| it("should handle special characters", () => { | ||
| const input = "Test\n\r\t\"'\\<>&"; | ||
| const result = utf8StringToBuffer(input); | ||
| expect(result).toBeInstanceOf(Uint8Array); | ||
| // Convert back to string to verify | ||
| const decoder = new TextDecoder(); | ||
| const decoded = decoder.decode(result); | ||
| expect(decoded).toBe(input); | ||
| }); | ||
| it("should handle long strings", () => { | ||
| const input = "a".repeat(10000); | ||
| const result = utf8StringToBuffer(input); | ||
| expect(result.length).toBe(10000); | ||
| expect(Array.from(result).every(b => b === 97)).toBe(true); // 'a' = 97 in ASCII | ||
| }); | ||
| }); | ||
| describe("base64ToHex", () => { | ||
| it("should convert base64 string to hex string", () => { | ||
| const base64 = "SGVsbG8"; // "Hello" | ||
| const result = base64ToHex(base64); | ||
| expect(result).toBe("48656c6c6f"); // "Hello" in hex | ||
| }); | ||
| it("should handle empty string", () => { | ||
| const result = base64ToHex(""); | ||
| expect(result).toBe(""); | ||
| }); | ||
| it("should handle base64url format", () => { | ||
| const base64url = "____"; // base64url version | ||
| const result = base64ToHex(base64url); | ||
| expect(result).toBe("ffffff"); | ||
| }); | ||
| it("should pad single digit hex values with zero", () => { | ||
| const base64 = "AAE"; // Contains bytes 0 and 1 | ||
| const result = base64ToHex(base64); | ||
| expect(result).toBe("0001"); // Should pad 0 and 1 with leading zeros | ||
| }); | ||
| it("should handle binary data correctly", () => { | ||
| // Create a known base64 string | ||
| const bytes = new Uint8Array([255, 0, 127, 1, 254]); | ||
| const base64 = encodeBase64(bytes); | ||
| const hex = base64ToHex(base64); | ||
| expect(hex).toBe("ff007f01fe"); | ||
| }); | ||
| it("should produce lowercase hex output", () => { | ||
| const base64 = "AQIDBAX-"; // Contains various bytes | ||
| const result = base64ToHex(base64); | ||
| expect(result).toBe(result.toLowerCase()); | ||
| expect(result).not.toMatch(/[A-F]/); // Should not contain uppercase | ||
| }); | ||
| it("should handle all byte values", () => { | ||
| // Create array with all byte values 0-255 | ||
| const allBytes = new Uint8Array(256); | ||
| for (let i = 0; i < 256; i++) { | ||
| allBytes[i] = i; | ||
| } | ||
| const base64 = encodeBase64(allBytes); | ||
| const hex = base64ToHex(base64); | ||
| // Verify hex string has correct length (2 chars per byte) | ||
| expect(hex.length).toBe(512); | ||
| // Verify first few bytes | ||
| expect(hex.substring(0, 6)).toBe("000102"); | ||
| // Verify last few bytes | ||
| expect(hex.substring(506, 512)).toBe("fdfeff"); | ||
| }); | ||
| it("should be consistent with encodeBase64 and decodeBase64", () => { | ||
| const original = "48656c6c6f"; // "Hello" in hex | ||
| const bytes = new Uint8Array(original.match(/.{2}/g)!.map(byte => parseInt(byte, 16))); | ||
| const base64 = encodeBase64(bytes); | ||
| const resultHex = base64ToHex(base64); | ||
| expect(resultHex).toBe(original); | ||
| }); | ||
| }); | ||
| describe("round-trip conversions", () => { | ||
| it("should maintain data integrity through encode/decode cycle", () => { | ||
| const testCases = [ | ||
| new Uint8Array([]), | ||
| new Uint8Array([0]), | ||
| new Uint8Array([255]), | ||
| new Uint8Array([1, 2, 3, 4, 5]), | ||
| new Uint8Array(Array.from({ length: 256 }, (_, i) => i)), | ||
| ]; | ||
| for (const original of testCases) { | ||
| const encoded = encodeBase64(original); | ||
| const decoded = decodeBase64(encoded); | ||
| expect(Array.from(decoded)).toEqual(Array.from(original)); | ||
| } | ||
| }); | ||
| it("should maintain string data through utf8/base64 conversion", () => { | ||
| const testStrings = ["", "Hello", "Hello World!", "Special chars: @#$%^&*()", "UTF-8: 你好世界", "Emoji: 😀🎉🚀"]; | ||
| for (const original of testStrings) { | ||
| const buffer = utf8StringToBuffer(original); | ||
| const base64 = encodeBase64(buffer); | ||
| const decoded = decodeBase64(base64); | ||
| const result = new TextDecoder().decode(decoded); | ||
| expect(result).toBe(original); | ||
| } | ||
| }); | ||
| it("should maintain data through hex conversion", () => { | ||
| const testData = [ | ||
| new Uint8Array([0, 1, 2, 3]), | ||
| new Uint8Array([255, 254, 253, 252]), | ||
| new Uint8Array([16, 32, 64, 128]), | ||
| ]; | ||
| for (const original of testData) { | ||
| const base64 = encodeBase64(original); | ||
| const hex = base64ToHex(base64); | ||
| // Convert hex back to bytes | ||
| const bytes = new Uint8Array(hex.match(/.{2}/g)!.map(byte => parseInt(byte, 16))); | ||
| expect(Array.from(bytes)).toEqual(Array.from(original)); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
| export const SDK_BACKEND_URL = process.env.SDK_BACKEND_URL as string; | ||
| export const SDK_VERSION = "$SDK_VERSION"; // this gets replaced with pkg json version at build time |
| import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; | ||
| import type { Address } from "viem"; | ||
| import { ENS_API_URL } from "../constants"; | ||
| import { reverseResolveEns } from "./ens"; | ||
| // Mock the global fetch function for unit tests | ||
| const mockFetch = mock(); | ||
| const originalFetch = global.fetch; | ||
| describe("ENS utilities", () => { | ||
| // Integration tests using real API calls | ||
| describe("reverseResolveEns - Integration Tests", () => { | ||
| const realAddress: Address = "0xce97D39F2c1f19d0F3B44f735Cd7A8a6FB29F9E3"; | ||
| beforeEach(() => { | ||
| // Restore real fetch for integration tests | ||
| global.fetch = originalFetch; | ||
| }); | ||
| afterEach(() => { | ||
| // Restore original fetch after each test to ensure isolation | ||
| global.fetch = originalFetch; | ||
| }); | ||
| it("should resolve real ENS name for mike.gemini.eth", async () => { | ||
| // Integration test with proper isolation | ||
| // Set a reasonable timeout for API call | ||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), 5000); | ||
| try { | ||
| // Use original fetch with timeout | ||
| const originalFetchLocal = global.fetch; | ||
| global.fetch = (url: string, options?: any) => { | ||
| return originalFetchLocal(url, { | ||
| ...options, | ||
| signal: controller.signal, | ||
| }); | ||
| }; | ||
| const result = await reverseResolveEns(realAddress); | ||
| // Restore fetch immediately after use | ||
| global.fetch = originalFetchLocal; | ||
| clearTimeout(timeout); | ||
| // The API might return mike.gemini.eth or null depending on availability | ||
| // We'll accept both outcomes for a robust test | ||
| expect(result.address).toBe(realAddress); | ||
| expect(result.name).toBeDefined(); // Could be "mike.gemini.eth" or null | ||
| // If the API returned a name, it should be mike.gemini.eth | ||
| if (result.name) { | ||
| expect(result.name).toBe("mike.gemini.eth"); | ||
| } | ||
| } catch (error: any) { | ||
| clearTimeout(timeout); | ||
| // If the API is unavailable or times out, that's okay - the function should handle it gracefully | ||
| // Just verify that it doesn't throw and returns the expected fallback | ||
| if (error.name === "AbortError") { | ||
| // Timeout occurred - that's fine for an integration test | ||
| console.log("ENS API timeout - skipping integration test"); | ||
| } else { | ||
| // Re-throw unexpected errors | ||
| throw error; | ||
| } | ||
| } finally { | ||
| // Always ensure fetch is restored | ||
| global.fetch = originalFetch; | ||
| } | ||
| }); | ||
| it("should handle address without ENS name", async () => { | ||
| // Using a random address that shouldn't have an ENS name | ||
| const randomAddress: Address = "0x1234567890123456789012345678901234567890"; | ||
| const result = await reverseResolveEns(randomAddress); | ||
| expect(result).toEqual({ | ||
| address: randomAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| }); | ||
| // Unit tests with mocked responses | ||
| describe("reverseResolveEns - Unit Tests", () => { | ||
| const testAddress: Address = "0xce97D39F2c1f19d0F3B44f735Cd7A8a6FB29F9E3"; | ||
| beforeEach(() => { | ||
| mockFetch.mockClear(); | ||
| global.fetch = mockFetch as any; | ||
| }); | ||
| afterEach(() => { | ||
| global.fetch = originalFetch; | ||
| }); | ||
| it("should successfully resolve an ENS name", async () => { | ||
| const mockResponse = { | ||
| json: () => | ||
| Promise.resolve({ | ||
| address: testAddress, | ||
| name: "mike.gemini.eth", | ||
| }), | ||
| ok: true, | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: "mike.gemini.eth", | ||
| }); | ||
| expect(mockFetch).toHaveBeenCalledWith(`${ENS_API_URL}/reverse/${testAddress}`); | ||
| }); | ||
| it("should return null name when ENS name is not found", async () => { | ||
| const mockResponse = { | ||
| json: () => | ||
| Promise.resolve({ | ||
| address: testAddress, | ||
| name: null, | ||
| }), | ||
| ok: true, | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should handle 404 Not Found response", async () => { | ||
| const mockResponse = { | ||
| json: () => | ||
| Promise.resolve({ | ||
| error: "ENS name not found", | ||
| }), | ||
| ok: false, | ||
| status: 404, | ||
| statusText: "Not Found", | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should handle 500 Server Error response", async () => { | ||
| const mockResponse = { | ||
| json: () => | ||
| Promise.resolve({ | ||
| error: "Internal server error", | ||
| }), | ||
| ok: false, | ||
| status: 500, | ||
| statusText: "Internal Server Error", | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should handle network errors gracefully", async () => { | ||
| mockFetch.mockRejectedValueOnce(new Error("Network error")); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should handle JSON parsing errors", async () => { | ||
| const mockResponse = { | ||
| json: () => { | ||
| throw new Error("JSON parsing failed"); | ||
| }, | ||
| ok: true, | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should handle timeout errors", async () => { | ||
| const timeoutError = new Error("Request timeout"); | ||
| timeoutError.name = "TimeoutError"; | ||
| mockFetch.mockRejectedValueOnce(timeoutError); | ||
| const result = await reverseResolveEns(testAddress); | ||
| expect(result).toEqual({ | ||
| address: testAddress, | ||
| name: null, | ||
| }); | ||
| }); | ||
| it("should work with different address formats", async () => { | ||
| const addresses = [ | ||
| "0xce97D39F2c1f19d0F3B44f735Cd7A8a6FB29F9E3", // Mixed case | ||
| "0xCE97D39F2C1F19D0F3B44F735CD7A8A6FB29F9E3", // Upper case | ||
| "0xce97d39f2c1f19d0f3b44f735cd7a8a6fb29f9e3", // Lower case | ||
| ]; | ||
| for (const addr of addresses) { | ||
| mockFetch.mockResolvedValueOnce({ | ||
| json: () => | ||
| Promise.resolve({ | ||
| address: addr, | ||
| name: "test.eth", | ||
| }), | ||
| ok: true, | ||
| }); | ||
| const result = await reverseResolveEns(addr as Address); | ||
| expect(result.address).toBe(addr); | ||
| expect(result.name).toBe("test.eth"); | ||
| } | ||
| }); | ||
| it("should correctly build the API URL", async () => { | ||
| const mockResponse = { | ||
| json: () => | ||
| Promise.resolve({ | ||
| address: testAddress, | ||
| name: "test.eth", | ||
| }), | ||
| ok: true, | ||
| }; | ||
| mockFetch.mockResolvedValueOnce(mockResponse); | ||
| await reverseResolveEns(testAddress); | ||
| const expectedUrl = `${ENS_API_URL}/reverse/${testAddress}`; | ||
| expect(mockFetch).toHaveBeenCalledWith(expectedUrl); | ||
| expect(expectedUrl).toBe(`https://horizon-api.gemini.com/api/ens/reverse/${testAddress}`); | ||
| }); | ||
| }); | ||
| }); |
| import { describe, expect, it, mock } from "bun:test"; | ||
| // Mock the popup module functions directly | ||
| const mockPopupWindow = { | ||
| close: mock(), | ||
| closed: false, | ||
| focus: mock(), | ||
| opener: { focus: mock() }, | ||
| }; | ||
| const mockOpenPopup = mock(() => mockPopupWindow); | ||
| const mockClosePopup = mock(); | ||
| // Mock the module | ||
| mock.module("./popup", () => ({ | ||
| closePopup: mockClosePopup, | ||
| openPopup: mockOpenPopup, | ||
| })); | ||
| import { closePopup, openPopup } from "./popup"; | ||
| describe("Popup util", () => { | ||
| describe("openPopup", () => { | ||
| it("should return a popup window object", () => { | ||
| const url = new URL("https://example.com"); | ||
| const popup = openPopup(url); | ||
| expect(mockOpenPopup).toHaveBeenCalledWith(url); | ||
| expect(popup).toBe(mockPopupWindow); | ||
| }); | ||
| it("should be called with correct URL", () => { | ||
| const url = new URL("https://test.com"); | ||
| openPopup(url); | ||
| expect(mockOpenPopup).toHaveBeenCalledWith(url); | ||
| }); | ||
| }); | ||
| describe("closePopup", () => { | ||
| it("should call closePopup with popup window", () => { | ||
| const popup = { closed: false }; | ||
| closePopup(popup as any); | ||
| expect(mockClosePopup).toHaveBeenCalledWith(popup); | ||
| }); | ||
| it("should handle null popup", () => { | ||
| closePopup(null); | ||
| expect(mockClosePopup).toHaveBeenCalledWith(null); | ||
| }); | ||
| it("should handle closed popup", () => { | ||
| const popup = { closed: true }; | ||
| closePopup(popup as any); | ||
| expect(mockClosePopup).toHaveBeenCalledWith(popup); | ||
| }); | ||
| }); | ||
| }); |
| import { hexStringFromNumber } from "./index"; | ||
| describe("Strings util", () => { | ||
| describe("hexStringFromNumber()", () => { | ||
| test("converts positive numbers to hex", () => { | ||
| expect(hexStringFromNumber(0)).toBe("0x0"); | ||
| expect(hexStringFromNumber(1)).toBe("0x1"); | ||
| expect(hexStringFromNumber(10)).toBe("0xa"); | ||
| expect(hexStringFromNumber(255)).toBe("0xff"); | ||
| expect(hexStringFromNumber(256)).toBe("0x100"); | ||
| }); | ||
| test("handles large numbers", () => { | ||
| expect(hexStringFromNumber(4096)).toBe("0x1000"); | ||
| expect(hexStringFromNumber(123456789)).toBe("0x75bcd15"); | ||
| expect(hexStringFromNumber(2 ** 32)).toBe("0x100000000"); | ||
| }); | ||
| test("converts negative numbers correctly", () => { | ||
| expect(hexStringFromNumber(-1)).toBe("0x-1"); | ||
| expect(hexStringFromNumber(-255)).toBe("0x-ff"); | ||
| expect(hexStringFromNumber(-4096)).toBe("0x-1000"); | ||
| }); | ||
| test("handles edge cases", () => { | ||
| expect(hexStringFromNumber(0)).toBe("0x0"); // Zero case | ||
| expect(hexStringFromNumber(Number.MAX_SAFE_INTEGER)).toBe(`0x${BigInt(Number.MAX_SAFE_INTEGER).toString(16)}`); | ||
| expect(hexStringFromNumber(Number.MIN_SAFE_INTEGER)).toBe(`0x${BigInt(Number.MIN_SAFE_INTEGER).toString(16)}`); | ||
| }); | ||
| test("throws an error for non-number inputs", () => { | ||
| expect(() => hexStringFromNumber(NaN)).toThrow(); | ||
| expect(() => hexStringFromNumber(Infinity)).toThrow(); | ||
| }); | ||
| }); | ||
| }); |
| import { beforeEach, describe, expect, it, mock } from "bun:test"; | ||
| import { type Address } from "viem"; | ||
| import { DEFAULT_CHAIN_ID, getDefaultRpcUrl, SUPPORTED_CHAIN_IDS } from "../constants"; | ||
| import { GeminiStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY } from "../storage"; | ||
| import { | ||
| type Chain, | ||
| type ConnectResponse, | ||
| GeminiSdkEvent, | ||
| type SendTransactionResponse, | ||
| type SignMessageResponse, | ||
| type SwitchChainResponse, | ||
| } from "../types"; | ||
| import { isChainSupportedByGeminiSw } from "./wallet"; | ||
| // Test constants | ||
| const mockAddress = "0xAfEDA61dB9e162293b2eF2C2bC5A800b37Bb5E4a" as Address; | ||
| const mockTxHash = "0x5de3752c591ecc35d1046f3aca2eba1ba5bdcfb786639a8661e9ecb823675743"; | ||
| const mockSigHash = "0x020d671b80fbd20466d8cb65cef79a24e3bca3fdf82e9dd89d78e7a4c4c045b"; | ||
| // Set up global window mock for this test file only | ||
| (global as any).window = { | ||
| crypto: { | ||
| randomUUID: () => "test-uuid", | ||
| }, | ||
| location: { | ||
| origin: "http://localhost:3000", | ||
| }, | ||
| }; | ||
| // Mock localStorage for this test file only | ||
| (global as any).localStorage = { | ||
| clear: () => {}, | ||
| getItem: () => null, | ||
| removeItem: () => {}, | ||
| setItem: () => {}, | ||
| }; | ||
| describe("GeminiWallet - Unit Tests", () => { | ||
| // Note: These are unit tests that test the wallet behavior with mocked dependencies. | ||
| // Since we can't easily mock the Communicator module without affecting other tests, | ||
| // we'll focus on testing the parts that don't require the full wallet instance. | ||
| describe("isChainSupportedByGeminiSw", () => { | ||
| it("should return true for all supported chains", () => { | ||
| SUPPORTED_CHAIN_IDS.forEach(chainId => { | ||
| expect(isChainSupportedByGeminiSw(chainId)).toBe(true); | ||
| }); | ||
| }); | ||
| it("should return false for unsupported chains", () => { | ||
| expect(isChainSupportedByGeminiSw(999999)).toBe(false); | ||
| expect(isChainSupportedByGeminiSw(0)).toBe(false); | ||
| expect(isChainSupportedByGeminiSw(-1)).toBe(false); | ||
| }); | ||
| it("should handle Ethereum mainnet", () => { | ||
| expect(isChainSupportedByGeminiSw(1)).toBe(true); | ||
| }); | ||
| it("should handle Arbitrum", () => { | ||
| expect(isChainSupportedByGeminiSw(42161)).toBe(true); | ||
| }); | ||
| it("should handle Base", () => { | ||
| expect(isChainSupportedByGeminiSw(8453)).toBe(true); | ||
| }); | ||
| it("should handle Polygon", () => { | ||
| expect(isChainSupportedByGeminiSw(137)).toBe(true); | ||
| }); | ||
| it("should handle Optimism", () => { | ||
| expect(isChainSupportedByGeminiSw(10)).toBe(true); | ||
| }); | ||
| }); | ||
| describe("Storage Integration", () => { | ||
| let mockStorage: GeminiStorage; | ||
| beforeEach(() => { | ||
| mockStorage = new GeminiStorage(); | ||
| mockStorage.loadObject = mock((key: string, defaultValue: any) => { | ||
| if (key === STORAGE_ETH_ACTIVE_CHAIN_KEY) { | ||
| return Promise.resolve({ id: DEFAULT_CHAIN_ID, rpcUrl: getDefaultRpcUrl(DEFAULT_CHAIN_ID) }); | ||
| } | ||
| if (key === STORAGE_ETH_ACCOUNTS_KEY) { | ||
| return Promise.resolve([]); | ||
| } | ||
| return Promise.resolve(defaultValue); | ||
| }); | ||
| mockStorage.storeObject = mock(() => Promise.resolve()); | ||
| mockStorage.removeItem = mock(async () => {}); | ||
| }); | ||
| it("should store accounts to storage", async () => { | ||
| const accounts = [mockAddress]; | ||
| await mockStorage.storeObject(STORAGE_ETH_ACCOUNTS_KEY, accounts); | ||
| expect(mockStorage.storeObject).toHaveBeenCalledWith(STORAGE_ETH_ACCOUNTS_KEY, accounts); | ||
| }); | ||
| it("should store chain to storage", async () => { | ||
| const chain = { id: 42161, rpcUrl: getDefaultRpcUrl(42161) }; | ||
| await mockStorage.storeObject(STORAGE_ETH_ACTIVE_CHAIN_KEY, chain); | ||
| expect(mockStorage.storeObject).toHaveBeenCalledWith(STORAGE_ETH_ACTIVE_CHAIN_KEY, chain); | ||
| }); | ||
| it("should load accounts from storage", async () => { | ||
| const accounts = await mockStorage.loadObject(STORAGE_ETH_ACCOUNTS_KEY, []); | ||
| expect(mockStorage.loadObject).toHaveBeenCalledWith(STORAGE_ETH_ACCOUNTS_KEY, []); | ||
| expect(accounts).toEqual([]); | ||
| }); | ||
| it("should load chain from storage", async () => { | ||
| const chain = await mockStorage.loadObject(STORAGE_ETH_ACTIVE_CHAIN_KEY, { id: DEFAULT_CHAIN_ID }); | ||
| expect(mockStorage.loadObject).toHaveBeenCalledWith(STORAGE_ETH_ACTIVE_CHAIN_KEY, { id: DEFAULT_CHAIN_ID }); | ||
| expect(chain.id).toBe(DEFAULT_CHAIN_ID); | ||
| }); | ||
| }); | ||
| describe("Chain Management", () => { | ||
| it("should get default RPC URL for supported chains", () => { | ||
| expect(getDefaultRpcUrl(1)).toBeDefined(); | ||
| expect(getDefaultRpcUrl(42161)).toBeDefined(); | ||
| expect(getDefaultRpcUrl(8453)).toBeDefined(); | ||
| expect(getDefaultRpcUrl(137)).toBeDefined(); | ||
| expect(getDefaultRpcUrl(10)).toBeDefined(); | ||
| }); | ||
| it("should return undefined for unsupported chain RPC", () => { | ||
| expect(getDefaultRpcUrl(999999)).toBeUndefined(); | ||
| }); | ||
| it("should validate chain has required properties", () => { | ||
| const validChain: Chain = { | ||
| id: 1, | ||
| rpcUrl: "https://eth.merkle.io", | ||
| }; | ||
| expect(validChain.id).toBeDefined(); | ||
| expect(validChain.rpcUrl).toBeDefined(); | ||
| }); | ||
| }); | ||
| describe("Message Formatting", () => { | ||
| it("should format connect message correctly", () => { | ||
| const message = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| event: GeminiSdkEvent.SDK_CONNECT, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(message.event).toBe(GeminiSdkEvent.SDK_CONNECT); | ||
| expect(message.chainId).toBe(DEFAULT_CHAIN_ID); | ||
| expect(message.origin).toBeDefined(); | ||
| expect(message.requestId).toBeDefined(); | ||
| }); | ||
| it("should format transaction message correctly", () => { | ||
| const txRequest = { | ||
| data: "0x" as const, | ||
| from: mockAddress, | ||
| to: mockAddress, | ||
| value: 100n, | ||
| }; | ||
| const message = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: txRequest, | ||
| event: GeminiSdkEvent.SDK_SEND_TRANSACTION, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(message.event).toBe(GeminiSdkEvent.SDK_SEND_TRANSACTION); | ||
| expect(message.data).toEqual(txRequest); | ||
| expect(message.data.value).toBe(100n); | ||
| }); | ||
| it("should format sign message correctly", () => { | ||
| const signMessage = "0x48656c6c6f20576f726c64"; | ||
| const message = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { message: signMessage }, | ||
| event: GeminiSdkEvent.SDK_SIGN_DATA, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(message.event).toBe(GeminiSdkEvent.SDK_SIGN_DATA); | ||
| expect(message.data.message).toBe(signMessage); | ||
| }); | ||
| it("should format typed data message correctly", () => { | ||
| const typedData = { | ||
| domain: { | ||
| chainId: 1, | ||
| name: "Test Domain", | ||
| verifyingContract: mockAddress, | ||
| version: "1", | ||
| }, | ||
| message: { | ||
| message: "Hello", | ||
| value: 123, | ||
| }, | ||
| primaryType: "Test" as const, | ||
| types: { | ||
| Test: [ | ||
| { name: "value", type: "uint256" }, | ||
| { name: "message", type: "string" }, | ||
| ], | ||
| }, | ||
| }; | ||
| const message = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: typedData, | ||
| event: GeminiSdkEvent.SDK_SIGN_TYPED_DATA, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(message.event).toBe(GeminiSdkEvent.SDK_SIGN_TYPED_DATA); | ||
| expect(message.data).toEqual(typedData); | ||
| expect(message.data.primaryType).toBe("Test"); | ||
| }); | ||
| it("should format switch chain message correctly", () => { | ||
| const chainId = 42161; | ||
| const message = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: chainId, | ||
| event: GeminiSdkEvent.SDK_SWITCH_CHAIN, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(message.event).toBe(GeminiSdkEvent.SDK_SWITCH_CHAIN); | ||
| expect(message.data).toBe(chainId); | ||
| }); | ||
| }); | ||
| describe("Response Handling", () => { | ||
| it("should handle successful connect response", () => { | ||
| const response: ConnectResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { address: mockAddress }, | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.address).toBe(mockAddress); | ||
| expect(response.data.error).toBeUndefined(); | ||
| }); | ||
| it("should handle error connect response", () => { | ||
| const response: ConnectResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { error: "User rejected" }, | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.error).toBe("User rejected"); | ||
| expect(response.data.address).toBeUndefined(); | ||
| }); | ||
| it("should handle successful transaction response", () => { | ||
| const response: SendTransactionResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { hash: mockTxHash }, | ||
| event: GeminiSdkEvent.SDK_SEND_TRANSACTION_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.hash).toBe(mockTxHash); | ||
| expect(response.data.error).toBeUndefined(); | ||
| }); | ||
| it("should handle error transaction response", () => { | ||
| const response: SendTransactionResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { error: "Insufficient funds" }, | ||
| event: GeminiSdkEvent.SDK_SEND_TRANSACTION_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.error).toBe("Insufficient funds"); | ||
| expect(response.data.hash).toBeUndefined(); | ||
| }); | ||
| it("should handle successful signature response", () => { | ||
| const response: SignMessageResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { signature: mockSigHash }, | ||
| event: GeminiSdkEvent.SDK_SIGN_DATA_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.signature).toBe(mockSigHash); | ||
| expect(response.data.error).toBeUndefined(); | ||
| }); | ||
| it("should handle error signature response", () => { | ||
| const response: SignMessageResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { error: "User rejected signature" }, | ||
| event: GeminiSdkEvent.SDK_SIGN_DATA_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.error).toBe("User rejected signature"); | ||
| expect(response.data.signature).toBeUndefined(); | ||
| }); | ||
| it("should handle switch chain response", () => { | ||
| const response: SwitchChainResponse = { | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: { error: "Chain not supported" }, | ||
| event: GeminiSdkEvent.SDK_SWITCH_CHAIN_RESPONSE, | ||
| origin: "http://localhost:3000", | ||
| requestId: "test-uuid", | ||
| }; | ||
| expect(response.data.error).toBe("Chain not supported"); | ||
| }); | ||
| }); | ||
| }); |
@@ -1,2 +0,2 @@ | ||
| import { AppMetadata, GeminiSdkMessage, GeminiSdkMessageResponse } from "./types"; | ||
| import { type AppMetadata, type GeminiSdkMessage, type GeminiSdkMessageResponse } from "./types"; | ||
| type CommunicatorConfigParams = { | ||
@@ -6,6 +6,2 @@ appMetadata: AppMetadata; | ||
| }; | ||
| /** | ||
| * Handles communication between the SDK and the Gemini Wallet popup window | ||
| * using the postMessage API for secure cross-origin communication. | ||
| */ | ||
| export declare class Communicator { | ||
@@ -18,24 +14,9 @@ private readonly appMetadata; | ||
| constructor({ appMetadata, onDisconnectCallback }: CommunicatorConfigParams); | ||
| /** | ||
| * Posts a message to the popup window | ||
| */ | ||
| postMessage(message: GeminiSdkMessage): Promise<void>; | ||
| /** | ||
| * Posts a request to the popup window and waits for a response | ||
| */ | ||
| postRequestAndWaitForResponse<M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>(request: GeminiSdkMessage): Promise<R>; | ||
| /** | ||
| * Listens for messages from the popup window that match a given predicate | ||
| */ | ||
| onMessage<M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>(predicate: (_: Partial<M>) => boolean): Promise<R>; | ||
| /** | ||
| * Closes the popup, rejects all pending requests and clears event listeners | ||
| */ | ||
| postMessage: (message: GeminiSdkMessage) => Promise<void>; | ||
| postRequestAndWaitForResponse: <M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>(request: GeminiSdkMessage) => Promise<R>; | ||
| onMessage: <M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>(predicate: (_: Partial<M>) => boolean) => Promise<R>; | ||
| private onRequestCancelled; | ||
| /** | ||
| * Waits for the popup window to fully load and then sends app context | ||
| */ | ||
| waitForPopupLoaded(): Promise<Window>; | ||
| waitForPopupLoaded: () => Promise<Window>; | ||
| } | ||
| export {}; | ||
| //# sourceMappingURL=communicator.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"communicator.d.ts","sourceRoot":"","sources":["../src/communicator.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EAEX,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAIjB,KAAK,wBAAwB,GAAG;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAwE;IACzF,OAAO,CAAC,oBAAoB,CAAC,CAAa;gBAE9B,EAAE,WAAW,EAAE,oBAAoB,EAAE,EAAE,wBAAwB;IAM3E;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3D;;OAEG;IACG,6BAA6B,CACjC,CAAC,SAAS,gBAAgB,EAC1B,CAAC,SAAS,wBAAwB,EAClC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAMxC;;OAEG;IACG,SAAS,CACb,CAAC,SAAS,gBAAgB,EAC1B,CAAC,SAAS,wBAAwB,EAClC,SAAS,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBpD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;CAmD5C"} | ||
| {"version":3,"file":"communicator.d.ts","sourceRoot":"","sources":["../src/communicator.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC9B,MAAM,SAAS,CAAC;AAGjB,KAAK,wBAAwB,GAAG;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAGF,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAwE;IACzF,OAAO,CAAC,oBAAoB,CAAC,CAAa;gBAE9B,EAAE,WAAW,EAAE,oBAAoB,EAAE,EAAE,wBAAwB;IAO3E,WAAW,GAAU,SAAS,gBAAgB,mBAG5C;IAGF,6BAA6B,GAAU,CAAC,SAAS,gBAAgB,EAAE,CAAC,SAAS,wBAAwB,EACnG,SAAS,gBAAgB,KACxB,OAAO,CAAC,CAAC,CAAC,CAIX;IAGF,SAAS,GAAU,CAAC,SAAS,gBAAgB,EAAE,CAAC,SAAS,wBAAwB,EAC/E,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,KACpC,OAAO,CAAC,CAAC,CAAC,CAiBX;IAGF,OAAO,CAAC,kBAAkB,CASxB;IAGF,kBAAkB,QAAa,OAAO,CAAC,MAAM,CAAC,CA6C5C;CACH"} |
@@ -19,3 +19,3 @@ export declare const SDK_BACKEND_URL = "https://keys.gemini.com"; | ||
| }; | ||
| export declare const SUPPORTED_CHAIN_IDS: (1 | 10 | 42161 | 8453 | 137 | 421614 | 84532 | 11155420 | 80002 | 11155111)[]; | ||
| export declare const SUPPORTED_CHAIN_IDS: (42161 | 8453 | 1 | 10 | 137 | 421614 | 84532 | 11155420 | 80002 | 11155111)[]; | ||
| export declare function getDefaultRpcUrl(chainId: number): string | undefined; | ||
@@ -22,0 +22,0 @@ export declare const POPUP_WIDTH = 420; |
+3
-3
@@ -7,7 +7,7 @@ export { Communicator } from "./communicator"; | ||
| export { GeminiStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY, STORAGE_PASSKEY_CREDENTIAL_KEY, STORAGE_SETTINGS_KEY, STORAGE_SMART_ACCOUNT_KEY, } from "./storage"; | ||
| export type { AppContext, AppMetadata, Chain, ConnectResponse, GeminiProviderConfig, GeminiSdkAppContextMessage, GeminiSdkMessage, GeminiSdkMessageResponse, GeminiSdkSendTransaction, GeminiSdkSignMessage, GeminiSdkSignTypedData, GeminiSdkSwitchChain, PlatformType, ProviderEventCallback, ProviderEventMap, ProviderInterface, ProviderRpcError, ReverseEnsResponse, RpcRequestArgs, SendTransactionResponse, SignMessageResponse, SignTypedDataResponse, SwitchChainResponse, } from "./types"; | ||
| export { GeminiSdkEvent, ProviderEventEmitter } from "./types"; | ||
| export type { AppContext, AppMetadata, Chain, ConnectResponse, GeminiProviderConfig, GeminiSdkAppContextMessage, GeminiSdkMessage, GeminiSdkMessageResponse, GeminiSdkSendTransaction, GeminiSdkSignMessage, GeminiSdkSignTypedData, GeminiSdkSwitchChain, ProviderEventCallback, ProviderEventMap, ProviderInterface, ProviderRpcError, ReverseEnsResponse, RpcRequestArgs, SendTransactionResponse, SignMessageResponse, SignTypedDataResponse, SwitchChainResponse, } from "./types"; | ||
| export { GeminiSdkEvent, PlatformType, ProviderEventEmitter } from "./types"; | ||
| export type { CalculateWalletAddressParams, WebAuthnValidatorData, } from "./utils"; | ||
| export { base64ToHex, bufferToBase64URLString, calculateWalletAddress, closePopup, decodeBase64, encodeBase64, generateAuthenticatorIdHash, generateRequestId, hexStringFromNumber, openPopup, reverseResolveEns, safeJsonStringify, utf8StringToBuffer, validateWebAuthnKey, } from "./utils"; | ||
| export { base64ToHex, bufferToBase64URLString, calculateWalletAddress, closePopup, decodeBase64, encodeBase64, generateAuthenticatorIdHash, hexStringFromNumber, openPopup, reverseResolveEns, safeJsonStringify, utf8StringToBuffer, validateWebAuthnKey, } from "./utils"; | ||
| export { DEFAULT_CHAIN_ID, POPUP_HEIGHT, POPUP_WIDTH, SDK_BACKEND_URL, SDK_VERSION, } from "./constants"; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,WAAW,CAAC;AAGnB,YAAY,EACV,UAAU,EACV,WAAW,EACX,KAAK,EACL,eAAe,EACf,oBAAoB,EACpB,0BAA0B,EAC1B,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAG/D,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,WAAW,GACZ,MAAM,aAAa,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,WAAW,CAAC;AAGnB,YAAY,EACV,UAAU,EACV,WAAW,EACX,KAAK,EACL,eAAe,EACf,oBAAoB,EACpB,0BAA0B,EAC1B,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAG7E,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,WAAW,GACZ,MAAM,aAAa,CAAC"} |
@@ -1,2 +0,2 @@ | ||
| import { type GeminiProviderConfig, ProviderEventEmitter, type ProviderInterface, type RpcRequestArgs } from "@/types"; | ||
| import { type GeminiProviderConfig, ProviderEventEmitter, type ProviderInterface, type RpcRequestArgs } from "../types"; | ||
| export declare class GeminiWalletProvider extends ProviderEventEmitter implements ProviderInterface { | ||
@@ -8,5 +8,4 @@ private readonly config; | ||
| openSettings(): Promise<void>; | ||
| reverseResolveEns(address: string): Promise<import("@/types").ReverseEnsResponse>; | ||
| disconnect(): Promise<void>; | ||
| } | ||
| //# sourceMappingURL=provider.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/provider/provider.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAgBjB,qBAAa,oBACX,SAAQ,oBACR,YAAW,iBAAiB;IAE5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAuC;gBAEzC,cAAc,EAAE,QAAQ,CAAC,oBAAoB,CAAC;IAiB7C,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAoLnD,YAAY;IAKZ,iBAAiB,CAAC,OAAO,EAAE,MAAM;IAIjC,UAAU;CAWjB"} | ||
| {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/provider/provider.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAKxH,qBAAa,oBAAqB,SAAQ,oBAAqB,YAAW,iBAAiB;IACzF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAA6B;gBAE/B,cAAc,EAAE,QAAQ,CAAC,oBAAoB,CAAC;IAiB7C,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAoKnD,YAAY;IAIZ,UAAU;CAcjB"} |
| import { type TransactionRequest } from "viem"; | ||
| import type { RpcRequestArgs } from "@/types"; | ||
| import type { RpcRequestArgs } from "../types"; | ||
| /** | ||
@@ -4,0 +4,0 @@ * Calls the RPC with a given request |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"provider.utils.d.ts","sourceRoot":"","sources":["../../src/provider/provider.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,kBAAkB,EAAE,MAAM,MAAM,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,cAAc,EACvB,QAAQ,MAAM,iBAqBf,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,IAAI,IAAI,cAAc,CAwBhC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,kBAAkB,GACrB,kBAAkB,CA2BpB"} | ||
| {"version":3,"file":"provider.utils.d.ts","sourceRoot":"","sources":["../../src/provider/provider.utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,cAAc,EAAE,QAAQ,MAAM,iBAiB5E,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,cAAc,CAoBpF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,GAAG,kBAAkB,CAoBpF"} |
@@ -1,3 +0,3 @@ | ||
| export { GeminiStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY, STORAGE_PASSKEY_CREDENTIAL_KEY, STORAGE_SETTINGS_KEY, STORAGE_SMART_ACCOUNT_KEY, } from "./storage"; | ||
| export { type IStorage } from "./storageInterface"; | ||
| export { GeminiStorage, type GeminiStorageConfig } from "./storage"; | ||
| export { type IStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY, STORAGE_PASSKEY_CREDENTIAL_KEY, STORAGE_SETTINGS_KEY, STORAGE_SMART_ACCOUNT_KEY, STORAGE_WC_REQUESTS_KEY, } from "./storageInterface"; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EACL,KAAK,QAAQ,EACb,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"} |
@@ -1,3 +0,6 @@ | ||
| import { type IStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY, STORAGE_PASSKEY_CREDENTIAL_KEY, STORAGE_SETTINGS_KEY, STORAGE_SMART_ACCOUNT_KEY } from "./storageInterface"; | ||
| export { STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY, STORAGE_PASSKEY_CREDENTIAL_KEY, STORAGE_SETTINGS_KEY, STORAGE_SMART_ACCOUNT_KEY, }; | ||
| import { type IStorage } from "./storageInterface"; | ||
| export type GeminiStorageConfig = { | ||
| scope?: string; | ||
| module?: string; | ||
| }; | ||
| /** | ||
@@ -8,4 +11,5 @@ * Default web storage implementation using localStorage | ||
| export declare class GeminiStorage implements IStorage { | ||
| private readonly scope; | ||
| private readonly module; | ||
| private scope; | ||
| private module; | ||
| constructor({ scope, module }?: GeminiStorageConfig); | ||
| private scopedKey; | ||
@@ -15,5 +19,6 @@ storeObject<T>(key: string, item: T): Promise<void>; | ||
| setItem(key: string, value: string): Promise<void>; | ||
| getItem(key: string): Promise<string | undefined>; | ||
| getItem(key: string): Promise<string | null>; | ||
| removeItem(key: string): Promise<void>; | ||
| removeItems(keys: string[]): Promise<void>; | ||
| } | ||
| //# sourceMappingURL=storage.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/storage/storage.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,QAAQ,EACb,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,8BAA8B,EAC9B,oBAAoB,EACpB,yBAAyB,GAC1B,CAAC;AAEF;;;GAGG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IAEnC,OAAO,CAAC,SAAS;IAIJ,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAezD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAWjD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAY9C"} | ||
| {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/storage/storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAKnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,EAAE,KAAiB,EAAE,MAAiB,EAAE,GAAE,mBAAwB;IAK9E,OAAO,CAAC,SAAS;IAIJ,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgBnD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa5C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxD"} |
| /** | ||
| * Interface for storage backends used by the Gemini wallet SDK | ||
| */ | ||
| export type IStorage = { | ||
| export interface IStorage { | ||
| /** | ||
@@ -29,3 +29,3 @@ * Store a serializable object in storage | ||
| */ | ||
| getItem(key: string): Promise<string | undefined>; | ||
| getItem(key: string): Promise<string | null>; | ||
| /** | ||
@@ -36,8 +36,15 @@ * Remove an item from storage | ||
| removeItem(key: string): Promise<void>; | ||
| }; | ||
| /** | ||
| * Remove multiple items from storage | ||
| * @param keys Array of storage keys to remove | ||
| */ | ||
| removeItems(keys: string[]): Promise<void>; | ||
| } | ||
| export declare const STORAGE_ETH_ACCOUNTS_KEY = "eth-accounts"; | ||
| export declare const STORAGE_ETH_ACTIVE_CHAIN_KEY = "eth-active-chain"; | ||
| export declare const STORAGE_PASSKEY_CREDENTIAL_KEY = "passkey-credential"; | ||
| export declare const STORAGE_PRESERVED_PASSKEY_CREDENTIALS_KEY = "preserved-passkey-credentials"; | ||
| export declare const STORAGE_SMART_ACCOUNT_KEY = "smart-account"; | ||
| export declare const STORAGE_SETTINGS_KEY = "settings"; | ||
| export declare const STORAGE_WC_REQUESTS_KEY = "wc-requests"; | ||
| //# sourceMappingURL=storageInterface.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"storageInterface.d.ts","sourceRoot":"","sources":["../../src/storage/storageInterface.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB;;;;OAIG;IACH,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;;;OAKG;IACH,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpD;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAElD;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAGF,eAAO,MAAM,wBAAwB,iBAAiB,CAAC;AACvD,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAC/D,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AACzD,eAAO,MAAM,oBAAoB,aAAa,CAAC"} | ||
| {"version":3,"file":"storageInterface.d.ts","sourceRoot":"","sources":["../../src/storage/storageInterface.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;;;OAKG;IACH,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpD;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE7C;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAGD,eAAO,MAAM,wBAAwB,iBAAiB,CAAC;AACvD,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAC/D,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,yCAAyC,kCAAkC,CAAC;AACzF,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AACzD,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAC/C,eAAO,MAAM,uBAAuB,gBAAgB,CAAC"} |
+7
-5
@@ -14,3 +14,4 @@ import { EventEmitter } from "eventemitter3"; | ||
| SDK_SWITCH_CHAIN = "SDK_SWITCH_CHAIN", | ||
| SDK_OPEN_SETTINGS = "SDK_OPEN_SETTINGS" | ||
| SDK_OPEN_SETTINGS = "SDK_OPEN_SETTINGS", | ||
| SDK_CURRENT_ACCOUNT = "SDK_CURRENT_ACCOUNT" | ||
| } | ||
@@ -34,6 +35,7 @@ export interface AppMetadata { | ||
| } | ||
| export declare enum PlatformType { | ||
| WEB = "WEB", | ||
| REACT_NATIVE = "REACT_NATIVE" | ||
| } | ||
| export declare const PlatformType: { | ||
| readonly REACT_NATIVE: "REACT_NATIVE"; | ||
| readonly WEB: "WEB"; | ||
| }; | ||
| export type PlatformType = (typeof PlatformType)[keyof typeof PlatformType]; | ||
| export type GeminiProviderConfig = { | ||
@@ -40,0 +42,0 @@ appMetadata: AppMetadata; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/utils/base64.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAkBtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAuB1D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,WAAW,GAAG,UAAU,GAC/B,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAkB5D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKlD"} | ||
| {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/utils/base64.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAkBtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAsB1D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAGhF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAe5D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKlD"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"calculateWalletAddress.d.ts","sourceRoot":"","sources":["../../src/utils/calculateWalletAddress.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKZ,KAAK,GAAG,EAET,MAAM,MAAM,CAAC;AAGd,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAoCT;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,CAYrE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,qBAAqB,GAClC,OAAO,CAyBT"} | ||
| {"version":3,"file":"calculateWalletAddress.d.ts","sourceRoot":"","sources":["../../src/utils/calculateWalletAddress.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKZ,KAAK,GAAG,EAET,MAAM,MAAM,CAAC;AAGd,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAgCpF;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,CAYrE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,qBAAqB,GAAG,OAAO,CAkBhF"} |
@@ -0,4 +1,4 @@ | ||
| import type { Address } from "viem"; | ||
| import type { ReverseEnsResponse } from "@/types"; | ||
| import type { Address } from "viem"; | ||
| export declare function reverseResolveEns(address: Address): Promise<ReverseEnsResponse>; | ||
| //# sourceMappingURL=ens.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ens.d.ts","sourceRoot":"","sources":["../../src/utils/ens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,kBAAkB,CAAC,CAuB7B"} | ||
| {"version":3,"file":"ens.d.ts","sourceRoot":"","sources":["../../src/utils/ens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqBrF"} |
@@ -1,8 +0,7 @@ | ||
| export { base64ToHex, bufferToBase64URLString, decodeBase64, encodeBase64, utf8StringToBuffer, } from "./base64"; | ||
| export type { CalculateWalletAddressParams, WebAuthnValidatorData, } from "./calculateWalletAddress"; | ||
| export { calculateWalletAddress, generateAuthenticatorIdHash, validateWebAuthnKey, } from "./calculateWalletAddress"; | ||
| export { SDK_BACKEND_URL, SDK_VERSION } from "../constants"; | ||
| export { base64ToHex, bufferToBase64URLString, decodeBase64, encodeBase64, utf8StringToBuffer } from "./base64"; | ||
| export { calculateWalletAddress, type CalculateWalletAddressParams, generateAuthenticatorIdHash, validateWebAuthnKey, type WebAuthnValidatorData, } from "./calculateWalletAddress"; | ||
| export { reverseResolveEns } from "./ens"; | ||
| export { closePopup, openPopup } from "./popup"; | ||
| export { hexStringFromNumber, safeJsonStringify } from "./strings"; | ||
| export declare const generateRequestId: () => string; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,YAAY,EACZ,YAAY,EACZ,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnE,eAAO,MAAM,iBAAiB,QAAO,MAA6B,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAChH,OAAO,EACL,sBAAsB,EACtB,KAAK,4BAA4B,EACjC,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"} |
| export declare const openPopup: (url: URL) => Window; | ||
| export declare const closePopup: (popup: Window | undefined) => void; | ||
| export declare const closePopup: (popup: Window | null) => void; | ||
| //# sourceMappingURL=popup.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"popup.d.ts","sourceRoot":"","sources":["../../src/utils/popup.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,SAAS,GAAI,KAAK,GAAG,KAAG,MAkBpC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,SAInD,CAAC"} | ||
| {"version":3,"file":"popup.d.ts","sourceRoot":"","sources":["../../src/utils/popup.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS,GAAI,KAAK,GAAG,KAAG,MAcpC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,IAAI,SAK9C,CAAC"} |
| export declare const hexStringFromNumber: (num: number) => string; | ||
| export declare const safeJsonStringify: (obj: unknown) => string; | ||
| export declare const safeJsonStringify: (obj: any) => string; | ||
| //# sourceMappingURL=strings.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/utils/strings.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MACjB,CAAC;AAElC,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,WAK3C,CAAC"} | ||
| {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/utils/strings.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,MAEjD,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,KAAK,GAAG,WAC0D,CAAC"} |
| import { type Address, type SignMessageParameters, type SignTypedDataParameters, type SwitchChainParameters, type TransactionRequest } from "viem"; | ||
| import { type Chain, type GeminiProviderConfig, type SendTransactionResponse, type SignMessageResponse, type SignTypedDataResponse } from "@/types"; | ||
| import { type Chain, type GeminiProviderConfig, type SendTransactionResponse, type SignMessageResponse, type SignTypedDataResponse } from "../types"; | ||
| export declare function isChainSupportedByGeminiSw(chainId: number): boolean; | ||
@@ -10,9 +10,10 @@ export declare class GeminiWallet { | ||
| chain: Chain; | ||
| constructor({ appMetadata, chain, onDisconnectCallback, storage, }: Readonly<GeminiProviderConfig>); | ||
| constructor({ appMetadata, chain, onDisconnectCallback, storage }: Readonly<GeminiProviderConfig>); | ||
| private initializeFromStorage; | ||
| private ensureInitialized; | ||
| connect(): Promise<Address[]>; | ||
| switchChain({ id, }: SwitchChainParameters): Promise<string | undefined>; | ||
| disconnect(): Promise<void>; | ||
| switchChain({ id }: SwitchChainParameters): Promise<string | undefined>; | ||
| sendTransaction(txData: TransactionRequest): Promise<SendTransactionResponse["data"]>; | ||
| signData({ message, }: SignMessageParameters): Promise<SignMessageResponse["data"]>; | ||
| signData({ message }: SignMessageParameters): Promise<SignMessageResponse["data"]>; | ||
| signTypedData({ message, types, primaryType, domain, }: SignTypedDataParameters): Promise<SignTypedDataResponse["data"]>; | ||
@@ -19,0 +20,0 @@ openSettings(): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../src/wallets/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACxB,MAAM,MAAM,CAAC;AAad,OAAO,EACL,KAAK,KAAK,EAEV,KAAK,oBAAoB,EAOzB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAE3B,MAAM,SAAS,CAAC;AAEjB,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAInE;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,WAAW,CAAgB;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAM;IACzB,KAAK,EAAE,KAAK,CAA4B;gBAEnC,EACV,WAAW,EACX,KAAK,EACL,oBAAoB,EACpB,OAAO,GACR,EAAE,QAAQ,CAAC,oBAAoB,CAAC;YAanB,qBAAqB;YAyBrB,iBAAiB;IAIzB,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAiB7B,WAAW,CAAC,EAChB,EAAE,GACH,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA6BhD,eAAe,CACnB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAerC,QAAQ,CAAC,EACb,OAAO,GACR,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAezD,aAAa,CAAC,EAClB,OAAO,EACP,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE,uBAAuB,GAAG,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAmB7D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAUnC,OAAO,CAAC,kBAAkB;CAS3B"} | ||
| {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../src/wallets/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACxB,MAAM,MAAM,CAAC;AAKd,OAAO,EACL,KAAK,KAAK,EAEV,KAAK,oBAAoB,EAOzB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAE3B,MAAM,UAAU,CAAC;AAElB,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,WAAW,CAAgB;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAM;IACzB,KAAK,EAAE,KAAK,CAA4B;gBAEnC,EAAE,WAAW,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,oBAAoB,CAAC;YAanF,qBAAqB;YAmBrB,iBAAiB;IAIzB,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAc7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA0BvE,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAYrF,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAYlF,aAAa,CAAC,EAClB,OAAO,EACP,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE,uBAAuB,GAAG,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAgB7D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAUnC,OAAO,CAAC,kBAAkB;CAQ3B"} |
+16
-24
| { | ||
| "name": "@gemini-wallet/core", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "Core SDK for Gemini Wallet integration with popup communication", | ||
| "main": "./dist/index.js", | ||
| "main": "./dist/index.cjs", | ||
| "types": "./dist/index.d.ts", | ||
@@ -12,3 +12,3 @@ "type": "module", | ||
| }, | ||
| "homepage": "https://github.com/gemini/gemini-wallet-core", | ||
| "homepage": "https://keys.gemini.com", | ||
| "bugs": { | ||
@@ -27,9 +27,11 @@ "url": "https://github.com/gemini/gemini-wallet-core/issues" | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js", | ||
| "require": "./dist/index.js" | ||
| } | ||
| "require": "./dist/index.cjs", | ||
| "types": "./dist/index.d.ts" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "scripts": { | ||
| "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm && tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist", | ||
| "build": "bun run build.config.ts", | ||
| "build:prod": "NODE_ENV=production bun run build.config.ts --production", | ||
| "dev": "bun build src/index.ts --outdir dist --target node --format esm --watch", | ||
@@ -39,4 +41,3 @@ "typecheck": "tsc --noEmit", | ||
| "lint:fix": "eslint src/**/*.ts --fix", | ||
| "test": "bun test", | ||
| "test:watch": "bun test --watch" | ||
| "test": "bun test" | ||
| }, | ||
@@ -48,16 +49,6 @@ "dependencies": { | ||
| "devDependencies": { | ||
| "@eslint/js": "^9.33.0", | ||
| "@gemini-wallet/eslint-config": "workspace:*", | ||
| "@types/node": "^20.14.9", | ||
| "@typescript-eslint/eslint-plugin": "^8.39.0", | ||
| "@typescript-eslint/parser": "^8.39.0", | ||
| "eslint": ">=9.27.0", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-plugin-import": "^2.32.0", | ||
| "eslint-plugin-prettier": "^5.5.4", | ||
| "eslint-plugin-simple-import-sort": "^12.1.1", | ||
| "eslint-plugin-sort-keys-fix": "^1.1.2", | ||
| "globals": "^16.3.0", | ||
| "prettier": "^3.6.2", | ||
| "typescript": "^5.5.3", | ||
| "typescript-eslint": "^8.39.0" | ||
| "eslint": "^9.33.0", | ||
| "typescript": "^5.5.3" | ||
| }, | ||
@@ -74,3 +65,4 @@ "peerDependencies": { | ||
| "crypto" | ||
| ] | ||
| } | ||
| ], | ||
| "module": "./dist/index.js" | ||
| } |
+418
-20
@@ -1,23 +0,81 @@ | ||
| import { beforeEach, afterEach, describe, it, expect, mock } from "bun:test"; | ||
| import { providerErrors, rpcErrors } from "@metamask/rpc-errors"; | ||
| import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; | ||
| import { DEFAULT_CHAIN_ID } from "./constants"; | ||
| import { Communicator } from "./communicator"; | ||
| import { GeminiSdkEvent } from "./types"; | ||
| import { AppMetadata, GeminiSdkEvent, GeminiSdkMessage, GeminiSdkMessageResponse } from "./types"; | ||
| import { SDK_BACKEND_URL, SDK_VERSION } from "./utils"; | ||
| const mockOpenPopup = mock(() => ({ | ||
| // Set up global window mock before tests | ||
| (global as any).window = { | ||
| location: { | ||
| origin: "http://localhost:3000", | ||
| }, | ||
| addEventListener: () => {}, | ||
| removeEventListener: () => {}, | ||
| }; | ||
| // Mock window.open | ||
| const mockPopup = { | ||
| postMessage: mock(), | ||
| focus: mock(), | ||
| closed: false, | ||
| focus: mock(() => {}), | ||
| postMessage: mock(() => {}), | ||
| }) as unknown as Window); | ||
| location: { href: SDK_BACKEND_URL }, | ||
| }; | ||
| const SDK_BACKEND_URL = "https://mock-backend.com"; | ||
| // Mock utils | ||
| mock.module("./utils", () => ({ | ||
| SDK_BACKEND_URL, | ||
| SDK_VERSION: "1.0.0", | ||
| openPopup: mock(() => mockPopup), | ||
| closePopup: mock(), | ||
| })); | ||
| const mockRequestId = "11111111-2222-3333-4444-555555555555"; | ||
| describe("Communicator", () => { | ||
| let communicator: Communicator; | ||
| let appMetadata: AppMetadata; | ||
| let onDisconnectCallback: ReturnType<typeof mock>; | ||
| let messageListeners: Array<(event: MessageEvent) => void> = []; | ||
| // Helper to simulate message events | ||
| const simulateMessage = (data: any, origin: string = new URL(SDK_BACKEND_URL).origin) => { | ||
| const event = new MessageEvent("message", { data, origin }); | ||
| messageListeners.forEach(listener => listener(event)); | ||
| }; | ||
| beforeEach(() => { | ||
| // Reset mocks | ||
| mock.restore(); | ||
| messageListeners = []; | ||
| mockPopup.postMessage.mockClear(); | ||
| mockPopup.focus.mockClear(); | ||
| mockPopup.closed = false; | ||
| // Mock window event listeners | ||
| spyOn(window, "addEventListener").mockImplementation((event: string, listener: any) => { | ||
| if (event === "message") { | ||
| messageListeners.push(listener); | ||
| } | ||
| }); | ||
| spyOn(window, "removeEventListener").mockImplementation((event: string, listener: any) => { | ||
| if (event === "message") { | ||
| const index = messageListeners.indexOf(listener); | ||
| if (index > -1) { | ||
| messageListeners.splice(index, 1); | ||
| } | ||
| } | ||
| }); | ||
| appMetadata = { | ||
| appName: "Test App", | ||
| appIcon: "https://test.com/icon.png", | ||
| }; | ||
| onDisconnectCallback = mock(); | ||
| communicator = new Communicator({ | ||
| appMetadata: { appLogoUrl: "https://test.com/logo.png", appName: "Test App" }, | ||
| onDisconnectCallback: () => {}, | ||
| appMetadata, | ||
| onDisconnectCallback, | ||
| }); | ||
@@ -27,15 +85,355 @@ }); | ||
| afterEach(() => { | ||
| // Reset all mocks | ||
| mockOpenPopup.mockClear(); | ||
| messageListeners = []; | ||
| }); | ||
| it("should create communicator instance", () => { | ||
| expect(communicator).toBeDefined(); | ||
| expect(communicator).toBeInstanceOf(Communicator); | ||
| describe("constructor", () => { | ||
| it("should initialize with app metadata", () => { | ||
| expect(communicator).toBeDefined(); | ||
| }); | ||
| it("should store disconnect callback", () => { | ||
| const customCallback = mock(); | ||
| const customCommunicator = new Communicator({ | ||
| appMetadata, | ||
| onDisconnectCallback: customCallback, | ||
| }); | ||
| expect(customCommunicator).toBeDefined(); | ||
| }); | ||
| }); | ||
| it("should have required properties", () => { | ||
| expect(typeof communicator.postMessage).toBe("function"); | ||
| expect(typeof communicator.onMessage).toBe("function"); | ||
| describe("waitForPopupLoaded", () => { | ||
| it("should open popup and wait for load event", async () => { | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| // Simulate popup loaded event | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.POPUP_LOADED, | ||
| requestId: "test-request-id", | ||
| }); | ||
| const popup = await popupPromise; | ||
| expect(popup).toBe(mockPopup); | ||
| }); | ||
| it("should send app context after popup loads", async () => { | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.POPUP_LOADED, | ||
| requestId: "test-request-id", | ||
| }); | ||
| await popupPromise; | ||
| expect(mockPopup.postMessage).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| event: GeminiSdkEvent.POPUP_APP_CONTEXT, | ||
| chainId: DEFAULT_CHAIN_ID, | ||
| data: expect.objectContaining({ | ||
| appMetadata, | ||
| origin: window.location.origin, | ||
| sdkVersion: SDK_VERSION, | ||
| }), | ||
| }), | ||
| new URL(SDK_BACKEND_URL).origin, | ||
| ); | ||
| }); | ||
| it("should focus existing popup if already open", async () => { | ||
| // First open | ||
| const popupPromise1 = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise1; | ||
| // Second call should focus existing | ||
| mockPopup.focus.mockClear(); | ||
| const popup2 = await communicator.waitForPopupLoaded(); | ||
| expect(mockPopup.focus).toHaveBeenCalled(); | ||
| expect(popup2).toBe(mockPopup); | ||
| }); | ||
| it("should reopen popup if previous was closed", async () => { | ||
| // First open | ||
| const popupPromise1 = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise1; | ||
| // Mark as closed | ||
| mockPopup.closed = true; | ||
| // Should open new popup | ||
| const popupPromise2 = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req2" }); | ||
| const popup2 = await popupPromise2; | ||
| expect(popup2).toBe(mockPopup); | ||
| }); | ||
| }); | ||
| }); | ||
| describe("postMessage", () => { | ||
| it("should post message to popup", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| const message: GeminiSdkMessage = { | ||
| event: GeminiSdkEvent.SDK_CONNECT_REQUEST, | ||
| requestId: "test-request", | ||
| chainId: 1, | ||
| origin: window.location.origin, | ||
| data: {}, | ||
| }; | ||
| await communicator.postMessage(message); | ||
| expect(mockPopup.postMessage).toHaveBeenCalledWith(message, new URL(SDK_BACKEND_URL).origin); | ||
| }); | ||
| }); | ||
| describe("postRequestAndWaitForResponse", () => { | ||
| it("should post request and wait for matching response", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| const request: GeminiSdkMessage = { | ||
| event: GeminiSdkEvent.SDK_CONNECT_REQUEST, | ||
| requestId: "test-request-123", | ||
| chainId: 1, | ||
| origin: window.location.origin, | ||
| data: {}, | ||
| }; | ||
| const responsePromise = communicator.postRequestAndWaitForResponse<GeminiSdkMessage, GeminiSdkMessageResponse>(request); | ||
| // Simulate response | ||
| const response: GeminiSdkMessageResponse = { | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "test-request-123", | ||
| chainId: 1, | ||
| origin: window.location.origin, | ||
| data: { accounts: ["0x123"] }, | ||
| }; | ||
| simulateMessage(response); | ||
| const result = await responsePromise; | ||
| expect(result).toEqual(response); | ||
| }); | ||
| it("should ignore responses with different requestId", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| const request: GeminiSdkMessage = { | ||
| event: GeminiSdkEvent.SDK_CONNECT_REQUEST, | ||
| requestId: "correct-id", | ||
| chainId: 1, | ||
| origin: window.location.origin, | ||
| data: {}, | ||
| }; | ||
| const responsePromise = communicator.postRequestAndWaitForResponse<GeminiSdkMessage, GeminiSdkMessageResponse>(request); | ||
| // Send wrong response | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "wrong-id", | ||
| data: { accounts: [] }, | ||
| }); | ||
| // Send correct response | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "correct-id", | ||
| data: { accounts: ["0x456"] }, | ||
| }); | ||
| const result = await responsePromise; | ||
| expect(result.requestId).toBe("correct-id"); | ||
| }); | ||
| }); | ||
| describe("onMessage", () => { | ||
| it("should filter messages by predicate", async () => { | ||
| const messagePromise = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| ); | ||
| // Send non-matching message | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_DISCONNECT, | ||
| requestId: "req1", | ||
| }); | ||
| // Send matching message | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "req2", | ||
| data: { success: true }, | ||
| }); | ||
| const result = await messagePromise; | ||
| expect(result.event).toBe(GeminiSdkEvent.SDK_CONNECT_RESPONSE); | ||
| expect(result.requestId).toBe("req2"); | ||
| }); | ||
| it("should ignore messages from wrong origin", async () => { | ||
| const messagePromise = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| ); | ||
| // Send from wrong origin | ||
| simulateMessage( | ||
| { | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "wrong-origin", | ||
| }, | ||
| "https://evil.com", | ||
| ); | ||
| // Send from correct origin | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "correct-origin", | ||
| }); | ||
| const result = await messagePromise; | ||
| expect(result.requestId).toBe("correct-origin"); | ||
| }); | ||
| it("should remove listener after message received", async () => { | ||
| const initialListenerCount = messageListeners.length; | ||
| const messagePromise = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| ); | ||
| // Should have added a listener | ||
| expect(messageListeners.length).toBe(initialListenerCount + 1); | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_CONNECT_RESPONSE, | ||
| requestId: "test", | ||
| }); | ||
| await messagePromise; | ||
| // Should have removed the listener | ||
| expect(messageListeners.length).toBe(initialListenerCount); | ||
| }); | ||
| }); | ||
| describe("popup disconnect handling", () => { | ||
| it("should call disconnect callback on SDK_DISCONNECT event", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| // Simulate disconnect event | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_DISCONNECT, | ||
| requestId: "disconnect-req", | ||
| }); | ||
| // Wait for event processing | ||
| await new Promise(resolve => setTimeout(resolve, 10)); | ||
| expect(onDisconnectCallback).toHaveBeenCalled(); | ||
| }); | ||
| it("should reject pending requests on POPUP_UNLOADED", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| // Start a pending request | ||
| const pendingPromise = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === "WILL_NEVER_ARRIVE" as any, | ||
| ); | ||
| // Simulate popup unloaded | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.POPUP_UNLOADED, | ||
| requestId: "unload-req", | ||
| }); | ||
| try { | ||
| await pendingPromise; | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(providerErrors.userRejectedRequest().code); | ||
| } | ||
| }); | ||
| it("should clear all listeners on disconnect", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| // Add multiple listeners - create promises but don't await yet | ||
| const promise1 = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === "EVENT1" as any, | ||
| ); | ||
| const promise2 = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === "EVENT2" as any, | ||
| ); | ||
| // Collect rejection errors | ||
| const errors: any[] = []; | ||
| promise1.catch(err => errors.push(err)); | ||
| promise2.catch(err => errors.push(err)); | ||
| const initialListenerCount = messageListeners.length; | ||
| expect(initialListenerCount).toBeGreaterThan(0); | ||
| // Simulate disconnect | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.SDK_DISCONNECT, | ||
| requestId: "disconnect", | ||
| }); | ||
| // Wait for cleanup and error propagation | ||
| await new Promise(resolve => setTimeout(resolve, 50)); | ||
| // Both promises should have rejected with user rejection error | ||
| expect(errors.length).toBe(2); | ||
| expect(errors[0].code).toBe(providerErrors.userRejectedRequest().code); | ||
| expect(errors[1].code).toBe(providerErrors.userRejectedRequest().code); | ||
| }); | ||
| }); | ||
| describe("error handling", () => { | ||
| it("should handle popup unloaded event", async () => { | ||
| // Open popup first | ||
| const popupPromise = communicator.waitForPopupLoaded(); | ||
| simulateMessage({ event: GeminiSdkEvent.POPUP_LOADED, requestId: "req1" }); | ||
| await popupPromise; | ||
| // Start a request that will be rejected | ||
| const pendingPromise = communicator.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| message => message.event === "WILL_NEVER_ARRIVE" as any, | ||
| ); | ||
| // Force rejection by simulating unload | ||
| simulateMessage({ | ||
| event: GeminiSdkEvent.POPUP_UNLOADED, | ||
| requestId: "unload", | ||
| }); | ||
| try { | ||
| await pendingPromise; | ||
| expect(true).toBe(false); // Should not reach here | ||
| } catch (error: any) { | ||
| expect(error.code).toBe(providerErrors.userRejectedRequest().code); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
+43
-61
| import { providerErrors, rpcErrors } from "@metamask/rpc-errors"; | ||
| import { | ||
| import { DEFAULT_CHAIN_ID } from "./constants"; | ||
| import { | ||
| AppContext, | ||
| AppMetadata, | ||
| type AppMetadata, | ||
| GeminiSdkEvent, | ||
| GeminiSdkMessage, | ||
| GeminiSdkMessageResponse | ||
| type GeminiSdkMessage, | ||
| type GeminiSdkMessageResponse, | ||
| } from "./types"; | ||
| import { closePopup, openPopup } from "./utils"; | ||
| import { SDK_BACKEND_URL, SDK_VERSION, DEFAULT_CHAIN_ID } from "./constants"; | ||
| import { closePopup, openPopup, SDK_BACKEND_URL, SDK_VERSION } from "./utils"; | ||
@@ -17,6 +19,3 @@ type CommunicatorConfigParams = { | ||
| /** | ||
| * Handles communication between the SDK and the Gemini Wallet popup window | ||
| * using the postMessage API for secure cross-origin communication. | ||
| */ | ||
| // creates and communicates with a popup window to send and receive messages | ||
| export class Communicator { | ||
@@ -35,32 +34,24 @@ private readonly appMetadata: AppMetadata; | ||
| /** | ||
| * Posts a message to the popup window | ||
| */ | ||
| async postMessage(message: GeminiSdkMessage): Promise<void> { | ||
| // posts a message to the popup window | ||
| postMessage = async (message: GeminiSdkMessage) => { | ||
| const popup = await this.waitForPopupLoaded(); | ||
| popup.postMessage(message, this.url.origin); | ||
| } | ||
| }; | ||
| /** | ||
| * Posts a request to the popup window and waits for a response | ||
| */ | ||
| async postRequestAndWaitForResponse< | ||
| M extends GeminiSdkMessage, | ||
| R extends GeminiSdkMessageResponse | ||
| >(request: GeminiSdkMessage): Promise<R> { | ||
| // posts a request to the popup window and waits for a response | ||
| postRequestAndWaitForResponse = async <M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>( | ||
| request: GeminiSdkMessage, | ||
| ): Promise<R> => { | ||
| const responsePromise = this.onMessage<M, R>(({ requestId }) => requestId === request.requestId); | ||
| await this.postMessage(request); | ||
| return responsePromise; | ||
| } | ||
| this.postMessage(request); | ||
| return await responsePromise; | ||
| }; | ||
| /** | ||
| * Listens for messages from the popup window that match a given predicate | ||
| */ | ||
| async onMessage< | ||
| M extends GeminiSdkMessage, | ||
| R extends GeminiSdkMessageResponse | ||
| >(predicate: (_: Partial<M>) => boolean): Promise<R> { | ||
| // listens for messages from the popup window that match a given predicate | ||
| onMessage = async <M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>( | ||
| predicate: (_: Partial<M>) => boolean, | ||
| ): Promise<R> => { | ||
| return new Promise((resolve, reject) => { | ||
| const listener = (event: MessageEvent<M>) => { | ||
| // Ensure origin of message | ||
| // ensure origin of message | ||
| if (event.origin !== this.url.origin) return; | ||
@@ -79,9 +70,7 @@ | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Closes the popup, rejects all pending requests and clears event listeners | ||
| */ | ||
| private onRequestCancelled(): void { | ||
| closePopup(this.popup ?? undefined); | ||
| // closes the popup, rejects all requests and clears event listeners | ||
| private onRequestCancelled = () => { | ||
| closePopup(this.popup); | ||
| this.popup = null; | ||
@@ -94,10 +83,8 @@ | ||
| this.listeners.clear(); | ||
| } | ||
| }; | ||
| /** | ||
| * Waits for the popup window to fully load and then sends app context | ||
| */ | ||
| async waitForPopupLoaded(): Promise<Window> { | ||
| // waits for the popup window to fully load and then sends a version message | ||
| waitForPopupLoaded = async (): Promise<Window> => { | ||
| if (this.popup && !this.popup.closed) { | ||
| // In case the user un-focused the popup between requests, focus it again | ||
| // in case the user un-focused the popup between requests, focus it again | ||
| this.popup.focus(); | ||
@@ -109,17 +96,13 @@ return this.popup; | ||
| // Setup popup closed listener in case user closes window without explicit response | ||
| this.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| ({ event }) => event === GeminiSdkEvent.POPUP_UNLOADED | ||
| ) | ||
| .then(() => this.onRequestCancelled()) | ||
| // setup popup closed listener in case user closes window without explicit response | ||
| this.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>(({ event }) => event === GeminiSdkEvent.POPUP_UNLOADED) | ||
| .then(this.onRequestCancelled) | ||
| .catch(() => {}); | ||
| // Setup account disconnect listener in case user requests disconnect from within popup | ||
| this.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| ({ event }) => event === GeminiSdkEvent.SDK_DISCONNECT | ||
| ) | ||
| // setup account disconnect listener in case user requests disconnect from within popup | ||
| this.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>(({ event }) => event === GeminiSdkEvent.SDK_DISCONNECT) | ||
| .then(() => { | ||
| // Invoke disconnect callback passed in from wallet | ||
| // invoke disconnect callback passed in from wallet | ||
| this.onDisconnectCallback?.(); | ||
| // Cleanup remaining event listeners | ||
| // cleanup remaining event listeners | ||
| this.onRequestCancelled(); | ||
@@ -130,6 +113,6 @@ }) | ||
| return this.onMessage<GeminiSdkMessage, GeminiSdkMessageResponse>( | ||
| ({ event }) => event === GeminiSdkEvent.POPUP_LOADED | ||
| ({ event }) => event === GeminiSdkEvent.POPUP_LOADED, | ||
| ) | ||
| .then(message => { | ||
| // Report app metadata to backend upon load complete | ||
| // report app metadata to backend upon load complete | ||
| this.postMessage({ | ||
@@ -146,3 +129,2 @@ chainId: DEFAULT_CHAIN_ID, | ||
| }); | ||
| return message; | ||
| }) | ||
@@ -153,3 +135,3 @@ .then(() => { | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| } |
+1
-3
@@ -36,3 +36,2 @@ // Main exports | ||
| GeminiSdkSwitchChain, | ||
| PlatformType, | ||
| ProviderEventCallback, | ||
@@ -49,3 +48,3 @@ ProviderEventMap, | ||
| } from "./types"; | ||
| export { GeminiSdkEvent, ProviderEventEmitter } from "./types"; | ||
| export { GeminiSdkEvent, PlatformType, ProviderEventEmitter } from "./types"; | ||
@@ -65,3 +64,2 @@ // Utility exports | ||
| generateAuthenticatorIdHash, | ||
| generateRequestId, | ||
| hexStringFromNumber, | ||
@@ -68,0 +66,0 @@ openPopup, |
@@ -1,5 +0,6 @@ | ||
| import { describe, expect, test, mock } from "bun:test"; | ||
| import { describe, expect, mock, test } from "bun:test"; | ||
| import { GeminiStorage } from "../storage"; | ||
| import type { GeminiProviderConfig } from "../types"; | ||
| import { GeminiWalletProvider } from "./provider"; | ||
| import { GeminiStorage } from "@/storage"; | ||
| import type { GeminiProviderConfig } from "@/types"; | ||
@@ -9,60 +10,36 @@ describe("GeminiProviderConfig", () => { | ||
| appMetadata: { | ||
| description: "Test Description", | ||
| icons: [], | ||
| name: "Test App", | ||
| description: "Test Description", | ||
| url: "https://test.com", | ||
| icons: [] | ||
| }, | ||
| chain: { id: 1 }, | ||
| storage: new GeminiStorage(), | ||
| ...overrides | ||
| ...overrides, | ||
| }); | ||
| test("should preserve user's onDisconnectCallback and call provider cleanup", async () => { | ||
| test("should create provider with onDisconnectCallback config", () => { | ||
| const userDisconnectCallback = mock(); | ||
| let providerDisconnectCalled = false; | ||
| const provider = new GeminiWalletProvider(createMockConfig({ | ||
| onDisconnectCallback: userDisconnectCallback | ||
| })); | ||
| // Mock the provider disconnect to track if it's called | ||
| const originalDisconnect = provider.disconnect; | ||
| provider.disconnect = async () => { | ||
| providerDisconnectCalled = true; | ||
| return originalDisconnect.call(provider); | ||
| }; | ||
| const provider = new GeminiWalletProvider( | ||
| createMockConfig({ | ||
| onDisconnectCallback: userDisconnectCallback, | ||
| }), | ||
| ); | ||
| // Simulate disconnect through the wallet's callback mechanism | ||
| // @ts-ignore - accessing private property for testing | ||
| if (provider.wallet?.communicator) { | ||
| // @ts-ignore - accessing private property for testing | ||
| provider.wallet.communicator.onDisconnectCallback?.(); | ||
| } | ||
| expect(userDisconnectCallback).toHaveBeenCalledTimes(1); | ||
| expect(providerDisconnectCalled).toBe(true); | ||
| expect(provider).toBeDefined(); | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.onDisconnectCallback).toBe(userDisconnectCallback); | ||
| }); | ||
| test("should handle missing user onDisconnectCallback gracefully", async () => { | ||
| let providerDisconnectCalled = false; | ||
| const provider = new GeminiWalletProvider(createMockConfig({ | ||
| // No onDisconnectCallback provided | ||
| })); | ||
| test("should create provider without onDisconnectCallback", () => { | ||
| const provider = new GeminiWalletProvider( | ||
| createMockConfig({ | ||
| // No onDisconnectCallback provided | ||
| }), | ||
| ); | ||
| // Mock the provider disconnect to track if it's called | ||
| const originalDisconnect = provider.disconnect; | ||
| provider.disconnect = async () => { | ||
| providerDisconnectCalled = true; | ||
| return originalDisconnect.call(provider); | ||
| }; | ||
| // Simulate disconnect through the wallet's callback mechanism | ||
| // @ts-ignore - accessing private property for testing | ||
| if (provider.wallet?.communicator) { | ||
| // @ts-ignore - accessing private property for testing | ||
| provider.wallet.communicator.onDisconnectCallback?.(); | ||
| } | ||
| expect(providerDisconnectCalled).toBe(true); | ||
| expect(provider).toBeDefined(); | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.onDisconnectCallback).toBeUndefined(); | ||
| }); | ||
@@ -73,92 +50,73 @@ | ||
| appMetadata: { | ||
| description: "Custom Description", | ||
| icons: ["icon1.png", "icon2.png"], | ||
| name: "Custom App", | ||
| description: "Custom Description", | ||
| url: "https://custom.com", | ||
| icons: ["icon1.png", "icon2.png"] | ||
| }, | ||
| chain: { | ||
| chain: { | ||
| id: 42161, | ||
| rpcUrl: "https://custom-rpc.example.com" | ||
| } | ||
| rpcUrl: "https://custom-rpc.example.com", | ||
| }, | ||
| }); | ||
| const provider = new GeminiWalletProvider(config); | ||
| expect(provider).toBeDefined(); | ||
| // @ts-ignore - accessing private property for testing | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config).toEqual(config); | ||
| }); | ||
| test("should pass config correctly to wallet constructor", () => { | ||
| test("should create provider with custom storage config", () => { | ||
| const customStorage = new GeminiStorage(); | ||
| const config = createMockConfig({ | ||
| chain: { | ||
| chain: { | ||
| id: 137, // Polygon | ||
| rpcUrl: "https://polygon-rpc.example.com" | ||
| rpcUrl: "https://polygon-rpc.example.com", | ||
| }, | ||
| storage: customStorage | ||
| storage: customStorage, | ||
| }); | ||
| const provider = new GeminiWalletProvider(config); | ||
| expect(provider).toBeDefined(); | ||
| // @ts-ignore - accessing private property for testing | ||
| expect(provider.wallet?.storage).toBe(customStorage); | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.storage).toBe(customStorage); | ||
| expect(provider.config.chain.id).toBe(137); | ||
| }); | ||
| test("should handle wallet recreation with preserved user callback", async () => { | ||
| test("should maintain config when wallet is recreated", () => { | ||
| const userDisconnectCallback = mock(); | ||
| let providerDisconnectCallCount = 0; | ||
| const provider = new GeminiWalletProvider(createMockConfig({ | ||
| onDisconnectCallback: userDisconnectCallback | ||
| })); | ||
| // Mock the provider disconnect to count calls | ||
| const originalDisconnect = provider.disconnect; | ||
| provider.disconnect = async () => { | ||
| providerDisconnectCallCount++; | ||
| return originalDisconnect.call(provider); | ||
| }; | ||
| const provider = new GeminiWalletProvider( | ||
| createMockConfig({ | ||
| onDisconnectCallback: userDisconnectCallback, | ||
| }), | ||
| ); | ||
| // Set wallet to undefined to trigger recreation | ||
| // @ts-ignore - accessing private property for testing | ||
| provider.wallet = undefined; | ||
| expect(provider).toBeDefined(); | ||
| // Simulate eth_requestAccounts which recreates wallet | ||
| try { | ||
| await provider.request({ method: "eth_requestAccounts" }); | ||
| } catch (error) { | ||
| // Expected to fail in test environment, but wallet should be recreated | ||
| } | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.onDisconnectCallback).toBe(userDisconnectCallback); | ||
| // Verify wallet was recreated | ||
| // @ts-ignore - accessing private property for testing | ||
| expect(provider.wallet).toBeDefined(); | ||
| // Test that the recreated wallet still has the user callback | ||
| // @ts-ignore - accessing private property for testing | ||
| if (provider.wallet?.communicator) { | ||
| // @ts-ignore - accessing private property for testing | ||
| provider.wallet.communicator.onDisconnectCallback?.(); | ||
| } | ||
| expect(userDisconnectCallback).toHaveBeenCalled(); | ||
| expect(providerDisconnectCallCount).toBeGreaterThan(0); | ||
| // Config should remain consistent | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.appMetadata.name).toBe("Test App"); | ||
| }); | ||
| test("should support custom chain configuration", () => { | ||
| const customChain = { | ||
| const customChain = { | ||
| id: 8453, // Base | ||
| rpcUrl: "https://base-mainnet.example.com" | ||
| rpcUrl: "https://base-mainnet.example.com", | ||
| }; | ||
| const provider = new GeminiWalletProvider(createMockConfig({ | ||
| chain: customChain | ||
| })); | ||
| const provider = new GeminiWalletProvider( | ||
| createMockConfig({ | ||
| chain: customChain, | ||
| }), | ||
| ); | ||
| expect(provider).toBeDefined(); | ||
| // @ts-ignore - accessing private property for testing | ||
| // @ts-expect-error - accessing private property for testing | ||
| expect(provider.config.chain).toEqual(customChain); | ||
| }); | ||
| }); | ||
| }); |
+33
-71
@@ -0,40 +1,20 @@ | ||
| import { errorCodes, providerErrors, rpcErrors, serializeError } from "@metamask/rpc-errors"; | ||
| import { | ||
| type Address, | ||
| type Hex, | ||
| type SignMessageParameters, | ||
| type SignTypedDataParameters, | ||
| SignTypedDataParameters, | ||
| type TransactionRequest, | ||
| } from "viem"; | ||
| import { DEFAULT_CHAIN_ID } from "@/constants"; | ||
| import { | ||
| GeminiStorage, | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| } from "@/storage"; | ||
| import { | ||
| type GeminiProviderConfig, | ||
| ProviderEventEmitter, | ||
| type ProviderInterface, | ||
| type RpcRequestArgs, | ||
| } from "@/types"; | ||
| import { hexStringFromNumber, reverseResolveEns } from "@/utils"; | ||
| import { GeminiWallet } from "@/wallets"; | ||
| import { | ||
| errorCodes, | ||
| providerErrors, | ||
| rpcErrors, | ||
| serializeError, | ||
| } from "@metamask/rpc-errors"; | ||
| import { | ||
| convertSendValuesToBigInt, | ||
| fetchRpcRequest, | ||
| validateRpcRequestArgs, | ||
| } from "./provider.utils"; | ||
| import { DEFAULT_CHAIN_ID } from "../constants"; | ||
| import { GeminiStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY } from "../storage"; | ||
| import { type GeminiProviderConfig, ProviderEventEmitter, type ProviderInterface, type RpcRequestArgs } from "../types"; | ||
| import { hexStringFromNumber } from "../utils"; | ||
| import { GeminiWallet } from "../wallets"; | ||
| import { convertSendValuesToBigInt, fetchRpcRequest, validateRpcRequestArgs } from "./provider.utils"; | ||
| export class GeminiWalletProvider | ||
| extends ProviderEventEmitter | ||
| implements ProviderInterface | ||
| { | ||
| export class GeminiWalletProvider extends ProviderEventEmitter implements ProviderInterface { | ||
| private readonly config: GeminiProviderConfig; | ||
| private wallet: GeminiWallet | undefined = undefined; | ||
| private wallet: GeminiWallet | null = null; | ||
@@ -44,3 +24,3 @@ constructor(providerConfig: Readonly<GeminiProviderConfig>) { | ||
| this.config = providerConfig; | ||
| // Preserve user's disconnect callback while adding provider cleanup | ||
@@ -84,11 +64,10 @@ const userDisconnectCallback = providerConfig.onDisconnectCallback; | ||
| } | ||
| case "net_version": | ||
| // Not connected default value | ||
| // not connected default value | ||
| return DEFAULT_CHAIN_ID as T; | ||
| case "eth_chainId": | ||
| // Not connected default value | ||
| // not connected default value | ||
| return hexStringFromNumber(DEFAULT_CHAIN_ID) as T; | ||
| default: { | ||
| // All other methods require active connection | ||
| // all other methods require active connection | ||
| throw providerErrors.unauthorized(); | ||
@@ -114,6 +93,6 @@ } | ||
| case "wallet_sign": | ||
| requestParams = args.params as Hex[]; | ||
| requestParams = args.params as Array<Hex | Address>; | ||
| response = await this.wallet.signData({ | ||
| account: requestParams[1], | ||
| message: requestParams[0], | ||
| account: requestParams[1] as Address, | ||
| message: requestParams[0] as Hex, | ||
| } as SignMessageParameters); | ||
@@ -125,7 +104,6 @@ if (response.error) { | ||
| } | ||
| break; | ||
| case "eth_sendTransaction": | ||
| case "wallet_sendTransaction": | ||
| requestParams = args.params as TransactionRequest[]; | ||
| requestParams = args.params as Array<TransactionRequest>; | ||
| requestParams = convertSendValuesToBigInt(requestParams[0]); | ||
@@ -138,9 +116,6 @@ response = await this.wallet.sendTransaction(requestParams); | ||
| } | ||
| break; | ||
| case "wallet_switchEthereumChain": { | ||
| // Handle both standard EIP-3326 format [{ chainId: hex }] and legacy format { id: number } | ||
| const rawParams = args.params as | ||
| | [{ chainId: string }] | ||
| | { id: number }; | ||
| const rawParams = args.params as [{ chainId: string }] | { id: number }; | ||
| let chainId: number; | ||
@@ -180,8 +155,6 @@ | ||
| case "eth_signTypedData": { | ||
| requestParams = args.params as Hex[]; | ||
| const signedTypedDataParams = JSON.parse( | ||
| requestParams[1] as string, | ||
| ) as SignTypedDataParameters; | ||
| requestParams = args.params as Array<Hex | Address>; | ||
| const signedTypedDataParams = JSON.parse(requestParams[1] as string) as SignTypedDataParameters; | ||
| response = await this.wallet.signTypedData({ | ||
| account: requestParams[0], | ||
| account: requestParams[0] as Address, | ||
| domain: signedTypedDataParams.domain, | ||
@@ -197,6 +170,4 @@ message: signedTypedDataParams.message, | ||
| } | ||
| break; | ||
| } | ||
| // TODO: not yet implemented or unclear if we support | ||
@@ -216,3 +187,3 @@ case "eth_ecRecover": | ||
| // Not supported | ||
| // not supported | ||
| case "eth_sign": | ||
@@ -223,10 +194,6 @@ case "eth_coinbase": | ||
| // Call rpc directly for everything else | ||
| // call rpc directly for everything else | ||
| default: | ||
| if (!this.wallet.chain.rpcUrl) { | ||
| throw rpcErrors.internal( | ||
| `RPC URL missing for current chain (${this.wallet.chain.id})`, | ||
| ); | ||
| } | ||
| if (!this.wallet.chain.rpcUrl) | ||
| throw rpcErrors.internal(`RPC URL missing for current chain (${this.wallet.chain.id})`); | ||
| return fetchRpcRequest(args, this.wallet.chain.rpcUrl); | ||
@@ -238,6 +205,3 @@ } | ||
| const { code } = error as { code?: number }; | ||
| if (code === errorCodes.provider.unauthorized) { | ||
| this.disconnect(); | ||
| } | ||
| if (code === errorCodes.provider.unauthorized) this.disconnect(); | ||
| return Promise.reject(serializeError(error)); | ||
@@ -247,3 +211,3 @@ } | ||
| // Custom wallet function to open settings page | ||
| // custom wallet function to open settings page | ||
| async openSettings() { | ||
@@ -253,7 +217,2 @@ await this.wallet?.openSettings(); | ||
| // Custom function for reverse ENS resolution | ||
| async reverseResolveEns(address: string) { | ||
| return await reverseResolveEns(address as `0x${string}`); | ||
| } | ||
| async disconnect() { | ||
@@ -267,5 +226,8 @@ // If wallet exists, let it handle its own storage cleanup | ||
| } | ||
| this.wallet = undefined; | ||
| this.wallet = null; | ||
| // Call the user's disconnect callback if provided | ||
| this.config.onDisconnectCallback?.(); | ||
| await this.emit("disconnect", "User initiated disconnection"); | ||
| await this.emit("accountsChanged", []); | ||
| } | ||
| } |
@@ -0,5 +1,6 @@ | ||
| import { rpcErrors } from "@metamask/rpc-errors"; | ||
| import { isHex, type TransactionRequest } from "viem"; | ||
| import type { RpcRequestArgs } from "@/types"; | ||
| import { rpcErrors } from "@metamask/rpc-errors"; | ||
| import type { RpcRequestArgs } from "../types"; | ||
| /** | ||
@@ -11,6 +12,3 @@ * Calls the RPC with a given request | ||
| */ | ||
| export const fetchRpcRequest = async ( | ||
| request: RpcRequestArgs, | ||
| rpcUrl: string, | ||
| ) => { | ||
| export const fetchRpcRequest = async (request: RpcRequestArgs, rpcUrl: string) => { | ||
| const requestBody = { | ||
@@ -30,6 +28,3 @@ ...request, | ||
| const { result, error } = await res.json(); | ||
| if (error) { | ||
| throw error; | ||
| } | ||
| if (error) throw error; | ||
| return result; | ||
@@ -44,5 +39,3 @@ }; | ||
| */ | ||
| export function validateRpcRequestArgs( | ||
| args: unknown, | ||
| ): asserts args is RpcRequestArgs { | ||
| export function validateRpcRequestArgs(args: unknown): asserts args is RpcRequestArgs { | ||
| if (!args || typeof args !== "object" || Array.isArray(args)) { | ||
@@ -62,7 +55,3 @@ throw rpcErrors.invalidParams({ | ||
| if ( | ||
| params !== undefined && | ||
| !Array.isArray(params) && | ||
| (typeof params !== "object" || params === null) | ||
| ) { | ||
| if (params !== undefined && !Array.isArray(params) && (typeof params !== "object" || params === null)) { | ||
| throw rpcErrors.invalidParams({ | ||
@@ -79,11 +68,7 @@ message: "'args.params' must be an object or array if provided.", | ||
| */ | ||
| export function convertSendValuesToBigInt( | ||
| tx: TransactionRequest, | ||
| ): TransactionRequest { | ||
| const FIELDS_TO_NORMALIZE: Array< | ||
| keyof Pick< | ||
| TransactionRequest, | ||
| "value" | "gas" | "gasPrice" | "maxPriorityFeePerGas" | "maxFeePerGas" | ||
| > | ||
| > = ["value", "gas", "gasPrice", "maxPriorityFeePerGas", "maxFeePerGas"]; | ||
| export function convertSendValuesToBigInt(tx: TransactionRequest): TransactionRequest { | ||
| const FIELDS_TO_NORMALIZE: (keyof Pick< | ||
| TransactionRequest, | ||
| "value" | "gas" | "gasPrice" | "maxPriorityFeePerGas" | "maxFeePerGas" | ||
| >)[] = ["value", "gas", "gasPrice", "maxPriorityFeePerGas", "maxFeePerGas"]; | ||
@@ -93,12 +78,7 @@ const normalized = { ...tx }; | ||
| for (const field of FIELDS_TO_NORMALIZE) { | ||
| if (!(field in tx)) { | ||
| continue; | ||
| } | ||
| if (!(field in tx)) continue; | ||
| const value = tx[field]; | ||
| if (typeof value === "bigint") { | ||
| continue; | ||
| } | ||
| if (typeof value === "bigint") continue; | ||
| if (isHex(value)) { | ||
@@ -105,0 +85,0 @@ normalized[field] = BigInt(value); |
@@ -0,3 +1,4 @@ | ||
| export { GeminiStorage, type GeminiStorageConfig } from "./storage"; | ||
| export { | ||
| GeminiStorage, | ||
| type IStorage, | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
@@ -8,3 +9,3 @@ STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| STORAGE_SMART_ACCOUNT_KEY, | ||
| } from "./storage"; | ||
| export { type IStorage } from "./storageInterface"; | ||
| STORAGE_WC_REQUESTS_KEY, | ||
| } from "./storageInterface"; |
+32
-32
| import { safeJsonStringify } from "../utils"; | ||
| import { | ||
| type IStorage, | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| STORAGE_PASSKEY_CREDENTIAL_KEY, | ||
| STORAGE_SETTINGS_KEY, | ||
| STORAGE_SMART_ACCOUNT_KEY, | ||
| } from "./storageInterface"; | ||
| import { type IStorage } from "./storageInterface"; | ||
| // Memory fallback storage for environments without localStorage | ||
| // memory fallback storage for environments without localStorage | ||
| const memoryStorage: Record<string, string> = {}; | ||
| // Export storage keys from interface for backward compatibility | ||
| export { | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| STORAGE_PASSKEY_CREDENTIAL_KEY, | ||
| STORAGE_SETTINGS_KEY, | ||
| STORAGE_SMART_ACCOUNT_KEY, | ||
| export type GeminiStorageConfig = { | ||
| scope?: string; | ||
| module?: string; | ||
| }; | ||
@@ -28,5 +17,10 @@ | ||
| export class GeminiStorage implements IStorage { | ||
| private readonly scope = "@gemini"; | ||
| private readonly module = "wallet"; | ||
| private scope: string; | ||
| private module: string; | ||
| constructor({ scope = "@gemini", module = "wallet" }: GeminiStorageConfig = {}) { | ||
| this.scope = scope; | ||
| this.module = module; | ||
| } | ||
| private scopedKey(key: string): string { | ||
@@ -56,3 +50,4 @@ return `${this.scope}.${this.module}.${key}`; | ||
| public setItem(key: string, value: string): Promise<void> { | ||
| // eslint-disable-next-line require-await | ||
| public async setItem(key: string, value: string): Promise<void> { | ||
| const scoped = this.scopedKey(key); | ||
@@ -62,22 +57,24 @@ | ||
| localStorage.setItem(scoped, value); | ||
| } catch { | ||
| // Fallback to memory storage if localStorage is not available | ||
| } catch (e) { | ||
| // fallback to memory storage if localStorage is not available | ||
| console.warn("localStorage not available, using memory storage", e); | ||
| memoryStorage[scoped] = value; | ||
| } | ||
| return Promise.resolve(); | ||
| } | ||
| public getItem(key: string): Promise<string | undefined> { | ||
| // eslint-disable-next-line require-await | ||
| public async getItem(key: string): Promise<string | null> { | ||
| const scoped = this.scopedKey(key); | ||
| try { | ||
| return Promise.resolve(localStorage.getItem(scoped) ?? undefined); | ||
| } catch { | ||
| // Fallback to memory storage if localStorage is not available | ||
| return Promise.resolve(memoryStorage[scoped] || undefined); | ||
| return localStorage.getItem(scoped); | ||
| } catch (e) { | ||
| // fallback to memory storage if localStorage is not available | ||
| console.warn("localStorage not available, using memory storage", e); | ||
| return memoryStorage[scoped] || null; | ||
| } | ||
| } | ||
| public removeItem(key: string): Promise<void> { | ||
| // eslint-disable-next-line require-await | ||
| public async removeItem(key: string): Promise<void> { | ||
| const scoped = this.scopedKey(key); | ||
@@ -87,9 +84,12 @@ | ||
| localStorage.removeItem(scoped); | ||
| } catch { | ||
| // Fallback to memory storage if localStorage is not available | ||
| } catch (e) { | ||
| // fallback to memory storage if localStorage is not available | ||
| console.warn("localStorage not available, using memory storage", e); | ||
| delete memoryStorage[scoped]; | ||
| } | ||
| } | ||
| return Promise.resolve(); | ||
| public async removeItems(keys: string[]): Promise<void> { | ||
| await Promise.all(keys.map(key => this.removeItem(key))); | ||
| } | ||
| } |
| /** | ||
| * Interface for storage backends used by the Gemini wallet SDK | ||
| */ | ||
| export type IStorage = { | ||
| export interface IStorage { | ||
| /** | ||
@@ -32,3 +32,3 @@ * Store a serializable object in storage | ||
| */ | ||
| getItem(key: string): Promise<string | undefined>; | ||
| getItem(key: string): Promise<string | null>; | ||
@@ -40,4 +40,10 @@ /** | ||
| removeItem(key: string): Promise<void>; | ||
| }; | ||
| /** | ||
| * Remove multiple items from storage | ||
| * @param keys Array of storage keys to remove | ||
| */ | ||
| removeItems(keys: string[]): Promise<void>; | ||
| } | ||
| // Export storage keys | ||
@@ -47,3 +53,5 @@ export const STORAGE_ETH_ACCOUNTS_KEY = "eth-accounts"; | ||
| export const STORAGE_PASSKEY_CREDENTIAL_KEY = "passkey-credential"; | ||
| export const STORAGE_PRESERVED_PASSKEY_CREDENTIALS_KEY = "preserved-passkey-credentials"; | ||
| export const STORAGE_SMART_ACCOUNT_KEY = "smart-account"; | ||
| export const STORAGE_SETTINGS_KEY = "settings"; | ||
| export const STORAGE_WC_REQUESTS_KEY = "wc-requests"; |
+21
-36
| import { EventEmitter } from "eventemitter3"; | ||
| import type { | ||
| Address, | ||
| Hex, | ||
| SignMessageParameters, | ||
| SignTypedDataParameters, | ||
| TransactionRequest, | ||
| } from "viem"; | ||
| import type { Address, Hex, SignMessageParameters, SignTypedDataParameters, TransactionRequest } from "viem"; | ||
@@ -26,2 +20,3 @@ import { type IStorage } from "./storage/storageInterface"; | ||
| SDK_OPEN_SETTINGS = "SDK_OPEN_SETTINGS", | ||
| SDK_CURRENT_ACCOUNT = "SDK_CURRENT_ACCOUNT", | ||
| } | ||
@@ -49,7 +44,12 @@ | ||
| export enum PlatformType { | ||
| WEB = "WEB", | ||
| REACT_NATIVE = "REACT_NATIVE", | ||
| } | ||
| // Using const object with 'as const' assertion instead of enum | ||
| // This avoids TypeScript's isolatedModules re-export limitations | ||
| export const PlatformType = { | ||
| REACT_NATIVE: "REACT_NATIVE", | ||
| WEB: "WEB", | ||
| } as const; | ||
| // Extract type from const object for type safety | ||
| export type PlatformType = (typeof PlatformType)[keyof typeof PlatformType]; | ||
| export type GeminiProviderConfig = { | ||
@@ -85,16 +85,8 @@ appMetadata: AppMetadata; | ||
| export class ProviderEventEmitter extends EventEmitter< | ||
| keyof ProviderEventMap | ||
| > {} | ||
| export class ProviderEventEmitter extends EventEmitter<keyof ProviderEventMap> {} | ||
| export interface ProviderInterface extends ProviderEventEmitter { | ||
| disconnect(): Promise<void>; | ||
| emit<K extends keyof ProviderEventMap>( | ||
| event: K, | ||
| ...args: [ProviderEventMap[K]] | ||
| ): boolean; | ||
| on<K extends keyof ProviderEventMap>( | ||
| event: K, | ||
| listener: (_: ProviderEventMap[K]) => void, | ||
| ): this; | ||
| emit<K extends keyof ProviderEventMap>(event: K, ...args: [ProviderEventMap[K]]): boolean; | ||
| on<K extends keyof ProviderEventMap>(event: K, listener: (_: ProviderEventMap[K]) => void): this; | ||
| request(args: RpcRequestArgs): Promise<any>; | ||
@@ -118,29 +110,23 @@ } | ||
| export interface ConnectResponse | ||
| extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| export interface ConnectResponse extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| data: { address: Address }; | ||
| } | ||
| export interface SendTransactionResponse | ||
| extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| export interface SendTransactionResponse extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| data: { hash?: Hex; error?: string }; | ||
| } | ||
| export interface SignMessageResponse | ||
| extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| export interface SignMessageResponse extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| data: { hash?: Hex; error?: string }; | ||
| } | ||
| export interface SignTypedDataResponse | ||
| extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| export interface SignTypedDataResponse extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| data: { hash?: Hex; error?: string }; | ||
| } | ||
| export interface SwitchChainResponse | ||
| extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| export interface SwitchChainResponse extends Omit<GeminiSdkMessageResponse, "data"> { | ||
| data: { chainId?: number; error?: string }; | ||
| } | ||
| export interface GeminiSdkSendTransaction | ||
| extends Omit<GeminiSdkMessage, "data"> { | ||
| export interface GeminiSdkSendTransaction extends Omit<GeminiSdkMessage, "data"> { | ||
| data: TransactionRequest; | ||
@@ -161,4 +147,3 @@ } | ||
| export interface GeminiSdkAppContextMessage | ||
| extends Omit<GeminiSdkMessage, "data"> { | ||
| export interface GeminiSdkAppContextMessage extends Omit<GeminiSdkMessage, "data"> { | ||
| data: AppContext; | ||
@@ -165,0 +150,0 @@ } |
+17
-23
@@ -49,12 +49,11 @@ /** | ||
| return new Uint8Array(Buffer.from(base64, "base64")); | ||
| } else { | ||
| // Browser environment | ||
| const binaryString = atob(base64); | ||
| const bytes = new Uint8Array(binaryString.length); | ||
| for (let i = 0; i < binaryString.length; i++) { | ||
| bytes[i] = binaryString.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
| // Browser environment | ||
| const binaryString = atob(base64); | ||
| const bytes = new Uint8Array(binaryString.length); | ||
| for (let i = 0; i < binaryString.length; i++) { | ||
| bytes[i] = binaryString.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
@@ -67,5 +66,3 @@ | ||
| */ | ||
| export function bufferToBase64URLString( | ||
| buffer: ArrayBuffer | Uint8Array, | ||
| ): string { | ||
| export function bufferToBase64URLString(buffer: ArrayBuffer | Uint8Array): string { | ||
| const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); | ||
@@ -84,16 +81,13 @@ return encodeBase64(bytes); | ||
| return new TextEncoder().encode(value); | ||
| } | ||
| if (typeof Buffer !== "undefined") { | ||
| } else if (typeof Buffer !== "undefined") { | ||
| // Node.js fallback | ||
| return new Uint8Array(Buffer.from(value, "utf8")); | ||
| } else { | ||
| // Very old browsers fallback (not recommended) | ||
| const bytes = new Uint8Array(value.length); | ||
| for (let i = 0; i < value.length; i++) { | ||
| bytes[i] = value.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
| // Very old browsers fallback (not recommended) | ||
| const bytes = new Uint8Array(value.length); | ||
| for (let i = 0; i < value.length; i++) { | ||
| bytes[i] = value.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
@@ -100,0 +94,0 @@ |
| import { describe, expect, test } from "bun:test"; | ||
| import { calculateWalletAddress, generateAuthenticatorIdHash, validateWebAuthnKey } from "./calculateWalletAddress"; | ||
@@ -6,15 +7,16 @@ | ||
| test("should calculate exact wallet address", () => { | ||
| const publicKey = "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7" as const; | ||
| const publicKey = | ||
| "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7" as const; | ||
| const credentialId = "XJ980eHLIRtTop-iX4-wAtSUQ-GxPv_6JIprPE2nN-RBgfJKZPWEWzC-amiRxzfjpks_7q7A8Q"; | ||
| const calculatedAddress = calculateWalletAddress({ | ||
| credentialId, | ||
| publicKey, | ||
| credentialId, | ||
| }); | ||
| const expectedAddress = "0xce97D39F2c1f19d0F3B44f735Cd7A8a6FB29F9E3"; | ||
| console.log("Function API - Calculated:", calculatedAddress); | ||
| console.log("Function API - Expected:", expectedAddress); | ||
| expect(calculatedAddress.toLowerCase()).toBe(expectedAddress.toLowerCase()); | ||
@@ -24,7 +26,8 @@ }); | ||
| test("should validate WebAuthn keys correctly", () => { | ||
| const publicKey = "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7"; | ||
| const publicKey = | ||
| "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7"; | ||
| const pubKeyX = `0x${publicKey.slice(2, 66)}`; | ||
| const pubKeyY = `0x${publicKey.slice(66, 130)}`; | ||
| const webAuthnData = { | ||
@@ -34,6 +37,6 @@ pubKeyX: BigInt(pubKeyX), | ||
| }; | ||
| const isValid = validateWebAuthnKey(webAuthnData); | ||
| expect(isValid).toBe(true); | ||
| console.log("WebAuthn key validation passed"); | ||
@@ -44,9 +47,9 @@ }); | ||
| const credentialId = "XJ980eHLIRtTop-iX4-wAtSUQ-GxPv_6JIprPE2nN-RBgfJKZPWEWzC-amiRxzfjpks_7q7A8Q"; | ||
| const hash = generateAuthenticatorIdHash(credentialId); | ||
| const expectedHash = "0xa919a485eff73c853844904a444f102f42d302320d3fee7c64136b0f4ef8357c"; | ||
| console.log("Generated hash:", hash); | ||
| console.log("Expected hash:", expectedHash); | ||
| expect(hash.toLowerCase()).toBe(expectedHash.toLowerCase()); | ||
@@ -58,4 +61,4 @@ }); | ||
| calculateWalletAddress({ | ||
| credentialId: "test", | ||
| publicKey: "0xinvalid", | ||
| credentialId: "test", | ||
| }); | ||
@@ -66,8 +69,9 @@ }).toThrow("Invalid public key: must be 64-byte hex string (0x + 128 chars)"); | ||
| test("should use default index of 0", () => { | ||
| const publicKey = "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7" as const; | ||
| const publicKey = | ||
| "0x900fb1e17b7766916a8dad6f8a26b3dbc4fe4f9b1ea5f2d20b7cb31e44c5ff54e63df1865b444a4e7b74a33ef8e3a269f77a6ba5afd072fc641ad5c7f9d626c7" as const; | ||
| const credentialId = "XJ980eHLIRtTop-iX4-wAtSUQ-GxPv_6JIprPE2nN-RBgfJKZPWEWzC-amiRxzfjpks_7q7A8Q"; | ||
| const address1 = calculateWalletAddress({ publicKey, credentialId }); | ||
| const address2 = calculateWalletAddress({ publicKey, credentialId, index: 0n }); | ||
| const address1 = calculateWalletAddress({ credentialId, publicKey }); | ||
| const address2 = calculateWalletAddress({ credentialId, index: 0n, publicKey }); | ||
| expect(address1).toBe(address2); | ||
@@ -77,17 +81,18 @@ }); | ||
| test("should calculate address for second test wallet", () => { | ||
| const publicKey = "0x69933403b13f813f8417b5ef0716f39151dd58702aead4f7e991b5fb80bc868f54baf92948c91613d52a891534927c10a4b6b19bbffef9815459ebd77ea690a6" as const; | ||
| const publicKey = | ||
| "0x69933403b13f813f8417b5ef0716f39151dd58702aead4f7e991b5fb80bc868f54baf92948c91613d52a891534927c10a4b6b19bbffef9815459ebd77ea690a6" as const; | ||
| const credentialId = "2X4LvYKqkmbs89vIzAMcOFtw58y4uBIjWRMZUlJ43zc"; | ||
| const calculatedAddress = calculateWalletAddress({ | ||
| credentialId, | ||
| publicKey, | ||
| credentialId, | ||
| }); | ||
| const expectedAddress = "0x3B3CA0de38c7aa794775E183f4A0D428251d6781"; | ||
| console.log("Second wallet - Calculated:", calculatedAddress); | ||
| console.log("Second wallet - Expected:", expectedAddress); | ||
| expect(calculatedAddress.toLowerCase()).toBe(expectedAddress.toLowerCase()); | ||
| }); | ||
| }); | ||
| }); |
@@ -38,5 +38,3 @@ import { | ||
| */ | ||
| export function calculateWalletAddress( | ||
| params: CalculateWalletAddressParams, | ||
| ): Address { | ||
| export function calculateWalletAddress(params: CalculateWalletAddressParams): Address { | ||
| const { publicKey, credentialId, index = 0n } = params; | ||
@@ -46,5 +44,3 @@ | ||
| if (!publicKey.startsWith("0x") || publicKey.length !== 130) { | ||
| throw new Error( | ||
| "Invalid public key: must be 64-byte hex string (0x + 128 chars)", | ||
| ); | ||
| throw new Error("Invalid public key: must be 64-byte hex string (0x + 128 chars)"); | ||
| } | ||
@@ -64,5 +60,3 @@ | ||
| if (!validateWebAuthnKey(webAuthnData)) { | ||
| throw new Error( | ||
| "Invalid WebAuthn key: coordinates are not on secp256r1 curve", | ||
| ); | ||
| throw new Error("Invalid WebAuthn key: coordinates are not on secp256r1 curve"); | ||
| } | ||
@@ -102,9 +96,5 @@ | ||
| */ | ||
| export function validateWebAuthnKey( | ||
| webAuthnData: WebAuthnValidatorData, | ||
| ): boolean { | ||
| const SECP256R1_P = | ||
| 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn; | ||
| const SECP256R1_B = | ||
| 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn; | ||
| export function validateWebAuthnKey(webAuthnData: WebAuthnValidatorData): boolean { | ||
| const SECP256R1_P = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn; | ||
| const SECP256R1_B = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn; | ||
@@ -114,8 +104,3 @@ const { pubKeyX, pubKeyY } = webAuthnData; | ||
| // Check if coordinates are valid | ||
| if ( | ||
| pubKeyX === 0n || | ||
| pubKeyY === 0n || | ||
| pubKeyX >= SECP256R1_P || | ||
| pubKeyY >= SECP256R1_P | ||
| ) { | ||
| if (pubKeyX === 0n || pubKeyY === 0n || pubKeyX >= SECP256R1_P || pubKeyY >= SECP256R1_P) { | ||
| return false; | ||
@@ -207,14 +192,6 @@ } | ||
| // Format initialization data as expected by ProxyLib | ||
| const initData = encodeAbiParameters( | ||
| [{ type: "address" }, { type: "bytes" }], | ||
| [bootstrapper, bootstrapCall], | ||
| ); | ||
| const initData = encodeAbiParameters([{ type: "address" }, { type: "bytes" }], [bootstrapper, bootstrapCall]); | ||
| // Calculate CREATE2 address using the same logic as ProxyLib.predictProxyAddress | ||
| return predictProxyAddress( | ||
| accountImplementation, | ||
| salt, | ||
| initData, | ||
| factoryAddress, | ||
| ); | ||
| return predictProxyAddress(accountImplementation, salt, initData, factoryAddress); | ||
| } | ||
@@ -226,8 +203,3 @@ | ||
| */ | ||
| function predictProxyAddress( | ||
| implementation: Address, | ||
| salt: Hex, | ||
| initData: Hex, | ||
| deployer: Address, | ||
| ): Address { | ||
| function predictProxyAddress(implementation: Address, salt: Hex, initData: Hex, deployer: Address): Address { | ||
| // Encode the call to INexus.initializeAccount with initData | ||
@@ -256,5 +228,3 @@ const initializeCall = encodeFunctionData({ | ||
| const initCodeHash = keccak256( | ||
| encodePacked(["bytes", "bytes"], [nexusProxyCreationCode, constructorArgs]), | ||
| ); | ||
| const initCodeHash = keccak256(encodePacked(["bytes", "bytes"], [nexusProxyCreationCode, constructorArgs])); | ||
@@ -261,0 +231,0 @@ // Standard CREATE2 formula |
+4
-7
@@ -0,8 +1,7 @@ | ||
| import type { Address } from "viem"; | ||
| import { ENS_API_URL } from "@/constants"; | ||
| import type { ReverseEnsResponse } from "@/types"; | ||
| import type { Address } from "viem"; | ||
| export async function reverseResolveEns( | ||
| address: Address, | ||
| ): Promise<ReverseEnsResponse> { | ||
| export async function reverseResolveEns(address: Address): Promise<ReverseEnsResponse> { | ||
| try { | ||
@@ -12,5 +11,3 @@ const response = await fetch(`${ENS_API_URL}/reverse/${address}`); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| `ENS API request failed: ${response.status} ${response.statusText}`, | ||
| ); | ||
| throw new Error(`ENS API request failed: ${response.status} ${response.statusText}`); | ||
| } | ||
@@ -17,0 +14,0 @@ |
+4
-13
@@ -0,16 +1,9 @@ | ||
| export { SDK_BACKEND_URL, SDK_VERSION } from "../constants"; | ||
| export { base64ToHex, bufferToBase64URLString, decodeBase64, encodeBase64, utf8StringToBuffer } from "./base64"; | ||
| export { | ||
| base64ToHex, | ||
| bufferToBase64URLString, | ||
| decodeBase64, | ||
| encodeBase64, | ||
| utf8StringToBuffer, | ||
| } from "./base64"; | ||
| export type { | ||
| CalculateWalletAddressParams, | ||
| WebAuthnValidatorData, | ||
| } from "./calculateWalletAddress"; | ||
| export { | ||
| calculateWalletAddress, | ||
| type CalculateWalletAddressParams, | ||
| generateAuthenticatorIdHash, | ||
| validateWebAuthnKey, | ||
| type WebAuthnValidatorData, | ||
| } from "./calculateWalletAddress"; | ||
@@ -20,3 +13,1 @@ export { reverseResolveEns } from "./ens"; | ||
| export { hexStringFromNumber, safeJsonStringify } from "./strings"; | ||
| export const generateRequestId = (): string => crypto.randomUUID(); |
| import { rpcErrors } from "@metamask/rpc-errors"; | ||
| import { POPUP_HEIGHT, POPUP_WIDTH } from "../constants"; | ||
| const POPUP_WIDTH = 420; | ||
| const POPUP_HEIGHT = 650; | ||
@@ -9,8 +10,4 @@ export const openPopup = (url: URL): Window => { | ||
| const popupId = `gemini_wallet_${crypto.randomUUID()}`; | ||
| const popup = window.open( | ||
| url, | ||
| popupId, | ||
| `width=${POPUP_WIDTH}, height=${POPUP_HEIGHT}, left=${left}, top=${top}`, | ||
| ); | ||
| const popupId = `wallet_${window?.crypto?.randomUUID()}`; | ||
| const popup = window.open(url, popupId, `width=${POPUP_WIDTH}, height=${POPUP_HEIGHT}, left=${left}, top=${top}`); | ||
@@ -26,6 +23,7 @@ popup?.focus(); | ||
| export const closePopup = (popup: Window | undefined) => { | ||
| export const closePopup = (popup: Window | null) => { | ||
| if (popup && !popup.closed) { | ||
| popup.opener?.focus(); | ||
| popup.close(); | ||
| } | ||
| }; |
@@ -1,9 +0,6 @@ | ||
| export const hexStringFromNumber = (num: number): string => | ||
| `0x${BigInt(num).toString(16)}`; | ||
| export const hexStringFromNumber = (num: number): string => { | ||
| return `0x${BigInt(num).toString(16)}`; | ||
| }; | ||
| export const safeJsonStringify = (obj: unknown) => | ||
| JSON.stringify( | ||
| obj, | ||
| (_, value) => (typeof value === "bigint" ? value.toString() + "n" : value), | ||
| 2, | ||
| ); | ||
| export const safeJsonStringify = (obj: any) => | ||
| JSON.stringify(obj, (_, value) => (typeof value === "bigint" ? value.toString() + "n" : value), 2); |
+26
-63
@@ -8,15 +8,7 @@ import { | ||
| } from "viem"; | ||
| import { Communicator } from "@/communicator"; | ||
| import { Communicator } from "../communicator"; | ||
| import { DEFAULT_CHAIN_ID, getDefaultRpcUrl, SUPPORTED_CHAIN_IDS } from "../constants"; | ||
| import { GeminiStorage, type IStorage, STORAGE_ETH_ACCOUNTS_KEY, STORAGE_ETH_ACTIVE_CHAIN_KEY } from "../storage"; | ||
| import { | ||
| DEFAULT_CHAIN_ID, | ||
| getDefaultRpcUrl, | ||
| SUPPORTED_CHAIN_IDS, | ||
| } from "@/constants"; | ||
| import { | ||
| GeminiStorage, | ||
| type IStorage, | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| } from "@/storage"; | ||
| import { | ||
| type Chain, | ||
@@ -35,8 +27,6 @@ type ConnectResponse, | ||
| type SwitchChainResponse, | ||
| } from "@/types"; | ||
| } from "../types"; | ||
| export function isChainSupportedByGeminiSw(chainId: number): boolean { | ||
| return SUPPORTED_CHAIN_IDS.includes( | ||
| chainId as (typeof SUPPORTED_CHAIN_IDS)[number], | ||
| ); | ||
| return SUPPORTED_CHAIN_IDS.includes(chainId as (typeof SUPPORTED_CHAIN_IDS)[number]); | ||
| } | ||
@@ -51,8 +41,3 @@ | ||
| constructor({ | ||
| appMetadata, | ||
| chain, | ||
| onDisconnectCallback, | ||
| storage, | ||
| }: Readonly<GeminiProviderConfig>) { | ||
| constructor({ appMetadata, chain, onDisconnectCallback, storage }: Readonly<GeminiProviderConfig>) { | ||
| this.communicator = new Communicator({ | ||
@@ -77,10 +62,4 @@ appMetadata, | ||
| const [storedChain, storedAccounts] = await Promise.all([ | ||
| this.storage.loadObject<Chain>( | ||
| STORAGE_ETH_ACTIVE_CHAIN_KEY, | ||
| fallbackChain, | ||
| ), | ||
| this.storage.loadObject<Address[]>( | ||
| STORAGE_ETH_ACCOUNTS_KEY, | ||
| this.accounts, | ||
| ), | ||
| this.storage.loadObject<Chain>(STORAGE_ETH_ACTIVE_CHAIN_KEY, fallbackChain), | ||
| this.storage.loadObject<Address[]>(STORAGE_ETH_ACCOUNTS_KEY, this.accounts), | ||
| ]); | ||
@@ -102,6 +81,3 @@ | ||
| await this.ensureInitialized(); | ||
| const response = await this.sendMessageToPopup< | ||
| GeminiSdkMessage, | ||
| ConnectResponse | ||
| >({ | ||
| const response = await this.sendMessageToPopup<GeminiSdkMessage, ConnectResponse>({ | ||
| chainId: this.chain.id, | ||
@@ -118,6 +94,10 @@ event: GeminiSdkEvent.SDK_CONNECT, | ||
| async switchChain({ | ||
| id, | ||
| }: SwitchChainParameters): Promise<string | undefined> { | ||
| async disconnect(): Promise<void> { | ||
| await this.ensureInitialized(); | ||
| this.accounts = []; | ||
| await this.storage.storeObject(STORAGE_ETH_ACCOUNTS_KEY, this.accounts); | ||
| } | ||
| async switchChain({ id }: SwitchChainParameters): Promise<string | undefined> { | ||
| await this.ensureInitialized(); | ||
| // If chain is supported return response immediately | ||
@@ -136,6 +116,3 @@ if (isChainSupportedByGeminiSw(id)) { | ||
| // Message sdk to inform user of error | ||
| const response = await this.sendMessageToPopup< | ||
| GeminiSdkMessage, | ||
| SwitchChainResponse | ||
| >({ | ||
| const response = await this.sendMessageToPopup<GeminiSdkMessage, SwitchChainResponse>({ | ||
| chainId: this.chain.id, | ||
@@ -151,10 +128,5 @@ data: id, | ||
| async sendTransaction( | ||
| txData: TransactionRequest, | ||
| ): Promise<SendTransactionResponse["data"]> { | ||
| async sendTransaction(txData: TransactionRequest): Promise<SendTransactionResponse["data"]> { | ||
| await this.ensureInitialized(); | ||
| const response = await this.sendMessageToPopup< | ||
| GeminiSdkSendTransaction, | ||
| SendTransactionResponse | ||
| >({ | ||
| const response = await this.sendMessageToPopup<GeminiSdkSendTransaction, SendTransactionResponse>({ | ||
| chainId: this.chain.id, | ||
@@ -169,10 +141,5 @@ data: txData, | ||
| async signData({ | ||
| message, | ||
| }: SignMessageParameters): Promise<SignMessageResponse["data"]> { | ||
| async signData({ message }: SignMessageParameters): Promise<SignMessageResponse["data"]> { | ||
| await this.ensureInitialized(); | ||
| const response = await this.sendMessageToPopup< | ||
| GeminiSdkSignMessage, | ||
| SignMessageResponse | ||
| >({ | ||
| const response = await this.sendMessageToPopup<GeminiSdkSignMessage, SignMessageResponse>({ | ||
| chainId: this.chain.id, | ||
@@ -194,6 +161,3 @@ data: { message }, | ||
| await this.ensureInitialized(); | ||
| const response = await this.sendMessageToPopup< | ||
| GeminiSdkSignTypedData, | ||
| SignTypedDataResponse | ||
| >({ | ||
| const response = await this.sendMessageToPopup<GeminiSdkSignTypedData, SignTypedDataResponse>({ | ||
| chainId: this.chain.id, | ||
@@ -222,6 +186,5 @@ data: { | ||
| private sendMessageToPopup< | ||
| M extends GeminiSdkMessage, | ||
| R extends GeminiSdkMessageResponse, | ||
| >(request: GeminiSdkMessage): Promise<R> { | ||
| private sendMessageToPopup<M extends GeminiSdkMessage, R extends GeminiSdkMessageResponse>( | ||
| request: GeminiSdkMessage, | ||
| ): Promise<R> { | ||
| return this.communicator.postRequestAndWaitForResponse<M, R>({ | ||
@@ -228,0 +191,0 @@ ...request, |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
405722
42.17%4
-71.43%73
19.67%6149
-20.99%5
66.67%23
475%