Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@gemini-wallet/core

Package Overview
Dependencies
Maintainers
3
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gemini-wallet/core - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
dist/index.cjs

Sorry, the diff of this file is too big to display

+3
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");
});
});
});
+5
-24

@@ -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;

@@ -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"}

@@ -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"}
{
"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"
}

@@ -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);
}
});
});
});
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(() => {

});
}
}
};
}

@@ -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);
});
});
});

@@ -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";
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";
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 @@ }

@@ -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

@@ -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 @@

@@ -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);

@@ -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