From cda09330abf90df3eded7f4f5c3f588393ee4f44 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Mon, 5 Jan 2026 11:41:14 +0800 Subject: [PATCH 1/8] feat(ecosystem): add multi-chain Provider compatibility layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add EIP-1193 compatible window.ethereum Provider for EVM chains (ETH, BSC) - Add TronLink compatible window.tronWeb/tronLink Provider for TRON - Add chain ID mapping utilities (hex chainId <-> KeyApp internal ID) - Create evm.ts and tron.ts handlers for host-side message processing - Extend bridge.ts to support eth_request and tron_request protocols - Add ChainSwitchConfirmJob sheet for wallet_switchEthereumChain - Enhance WalletPickerJob to resolve various chain ID formats - Update Forge miniapp to use window.ethereum for EVM connections This fixes the '暂无支持bsc的钱包' error by implementing standard dApp protocols that miniapps can use instead of bio_selectAccount with mismatched chain IDs. Closes #forge-bsc-wallet-issue --- miniapps/forge/src/App.tsx | 65 +++- miniapps/forge/src/lib/chain.ts | 45 +++ miniapps/forge/src/vite-env.d.ts | 3 + packages/bio-sdk/src/chain-id.test.ts | 92 +++++ packages/bio-sdk/src/chain-id.ts | 91 +++++ packages/bio-sdk/src/ethereum-provider.ts | 295 +++++++++++++++ packages/bio-sdk/src/index.ts | 46 ++- packages/bio-sdk/src/tron-provider.ts | 310 +++++++++++++++ src/services/ecosystem/bridge.ts | 135 +++++-- src/services/ecosystem/handlers/context.ts | 51 +++ src/services/ecosystem/handlers/evm.ts | 354 ++++++++++++++++++ src/services/ecosystem/handlers/index.ts | 32 +- src/services/ecosystem/handlers/tron.ts | 214 +++++++++++ src/services/ecosystem/provider.ts | 65 +++- .../sheets/ChainSwitchConfirmJob.tsx | 169 +++++++++ .../activities/sheets/WalletPickerJob.tsx | 37 +- src/stackflow/activities/sheets/index.ts | 1 + src/stackflow/stackflow.ts | 3 + 18 files changed, 1948 insertions(+), 60 deletions(-) create mode 100644 miniapps/forge/src/lib/chain.ts create mode 100644 packages/bio-sdk/src/chain-id.test.ts create mode 100644 packages/bio-sdk/src/chain-id.ts create mode 100644 packages/bio-sdk/src/ethereum-provider.ts create mode 100644 packages/bio-sdk/src/tron-provider.ts create mode 100644 src/services/ecosystem/handlers/evm.ts create mode 100644 src/services/ecosystem/handlers/tron.ts create mode 100644 src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx diff --git a/miniapps/forge/src/App.tsx b/miniapps/forge/src/App.tsx index 0a4d7db6d..618cf8951 100644 --- a/miniapps/forge/src/App.tsx +++ b/miniapps/forge/src/App.tsx @@ -1,6 +1,7 @@ import { useState, useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import type { BioAccount } from '@biochain/bio-sdk' +import { getChainType, getEvmChainIdFromApi } from '@/lib/chain' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Input } from '@/components/ui/input' @@ -77,20 +78,70 @@ export default function App() { setError('Bio SDK 未初始化') return } + if (!selectedOption) { + setError('请选择兑换选项') + return + } setLoading(true) setError(null) try { - // Select external chain account (for payment) - const extAcc = await window.bio.request({ - method: 'bio_selectAccount', - params: [{ chain: selectedOption?.externalChain?.toLowerCase() }], - }) + const externalChain = selectedOption.externalChain + const chainType = getChainType(externalChain) + + let extAcc: BioAccount + + if (chainType === 'evm') { + // Use window.ethereum for EVM chains (ETH, BSC) + if (!window.ethereum) { + throw new Error('Ethereum provider not available') + } + const evmChainId = getEvmChainIdFromApi(externalChain) + if (evmChainId) { + // Switch to the correct chain first + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: evmChainId }], + }) + } + // Request accounts + const accounts = await window.ethereum.request({ + method: 'eth_requestAccounts', + }) + if (!accounts || accounts.length === 0) { + throw new Error('No accounts returned') + } + extAcc = { + address: accounts[0], + chain: externalChain.toLowerCase(), + publicKey: '', // EVM doesn't expose public key directly + } + } else if (chainType === 'tron') { + // Use window.tronLink for TRON + if (!window.tronLink) { + throw new Error('TronLink provider not available') + } + const result = await window.tronLink.request({ method: 'tron_requestAccounts' }) + if (!result || result.code !== 200) { + throw new Error('TRON connection failed') + } + extAcc = { + address: result.data.base58, + chain: 'tron', + publicKey: '', + } + } else { + // Use bio_selectAccount for BioChain + extAcc = await window.bio.request({ + method: 'bio_selectAccount', + params: [{ chain: externalChain.toLowerCase() }], + }) + } setExternalAccount(extAcc) - // Select internal chain account (for receiving) + // Select internal chain account (for receiving) - always use bio const intAcc = await window.bio.request({ method: 'bio_selectAccount', - params: [{ chain: selectedOption?.internalChain }], + params: [{ chain: selectedOption.internalChain }], }) setInternalAccount(intAcc) diff --git a/miniapps/forge/src/lib/chain.ts b/miniapps/forge/src/lib/chain.ts new file mode 100644 index 000000000..478d8de41 --- /dev/null +++ b/miniapps/forge/src/lib/chain.ts @@ -0,0 +1,45 @@ +/** + * Chain utilities for Forge miniapp + */ + +import { toHexChainId, EVM_CHAIN_IDS, API_CHAIN_TO_KEYAPP } from '@biochain/bio-sdk' + +/** Chain types */ +export type ChainType = 'evm' | 'tron' | 'bio' + +/** Map API chain names to EVM hex chainId */ +export const API_CHAIN_TO_HEX: Record = { + ETH: toHexChainId(EVM_CHAIN_IDS.ethereum), + BSC: toHexChainId(EVM_CHAIN_IDS.binance), +} + +/** + * Get chain type from API chain name + */ +export function getChainType(apiChainName: string): ChainType { + const upper = apiChainName.toUpperCase() + if (upper === 'ETH' || upper === 'BSC') return 'evm' + if (upper === 'TRON') return 'tron' + return 'bio' +} + +/** + * Get EVM hex chainId from API chain name + */ +export function getEvmChainIdFromApi(apiChainName: string): string | null { + return API_CHAIN_TO_HEX[apiChainName.toUpperCase()] ?? null +} + +/** + * Check if chain is EVM compatible + */ +export function isEvmChain(apiChainName: string): boolean { + return getChainType(apiChainName) === 'evm' +} + +/** + * Check if chain is TRON + */ +export function isTronChain(apiChainName: string): boolean { + return getChainType(apiChainName) === 'tron' +} diff --git a/miniapps/forge/src/vite-env.d.ts b/miniapps/forge/src/vite-env.d.ts index 4e934f5dc..640aba57e 100644 --- a/miniapps/forge/src/vite-env.d.ts +++ b/miniapps/forge/src/vite-env.d.ts @@ -7,3 +7,6 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv } + +// Bio SDK types are auto-declared by importing @biochain/bio-sdk +// This ensures window.bio, window.ethereum, window.tronLink are available diff --git a/packages/bio-sdk/src/chain-id.test.ts b/packages/bio-sdk/src/chain-id.test.ts new file mode 100644 index 000000000..940966984 --- /dev/null +++ b/packages/bio-sdk/src/chain-id.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect } from 'vitest' +import { + toHexChainId, + parseHexChainId, + getKeyAppChainId, + getEvmChainId, + isEvmChain, + normalizeChainId, + EVM_CHAIN_IDS, +} from './chain-id' + +describe('chain-id utilities', () => { + describe('toHexChainId', () => { + it('should convert decimal to hex', () => { + expect(toHexChainId(1)).toBe('0x1') + expect(toHexChainId(56)).toBe('0x38') + expect(toHexChainId(137)).toBe('0x89') + }) + }) + + describe('parseHexChainId', () => { + it('should parse hex to decimal', () => { + expect(parseHexChainId('0x1')).toBe(1) + expect(parseHexChainId('0x38')).toBe(56) + expect(parseHexChainId('0x89')).toBe(137) + }) + + it('should throw for invalid hex', () => { + expect(() => parseHexChainId('38')).toThrow('Invalid hex chain ID') + expect(() => parseHexChainId('invalid')).toThrow('Invalid hex chain ID') + }) + }) + + describe('getKeyAppChainId', () => { + it('should map EVM hex chainId to KeyApp ID', () => { + expect(getKeyAppChainId('0x1')).toBe('ethereum') + expect(getKeyAppChainId('0x38')).toBe('binance') + }) + + it('should return null for unknown chainId', () => { + expect(getKeyAppChainId('0x999')).toBeNull() + }) + }) + + describe('getEvmChainId', () => { + it('should map KeyApp ID to EVM hex chainId', () => { + expect(getEvmChainId('ethereum')).toBe('0x1') + expect(getEvmChainId('binance')).toBe('0x38') + }) + + it('should return null for non-EVM chains', () => { + expect(getEvmChainId('bfmeta')).toBeNull() + expect(getEvmChainId('tron')).toBeNull() + }) + }) + + describe('isEvmChain', () => { + it('should return true for EVM chains', () => { + expect(isEvmChain('ethereum')).toBe(true) + expect(isEvmChain('binance')).toBe(true) + }) + + it('should return false for non-EVM chains', () => { + expect(isEvmChain('tron')).toBe(false) + expect(isEvmChain('bfmeta')).toBe(false) + }) + }) + + describe('normalizeChainId', () => { + it('should normalize API chain names to KeyApp IDs', () => { + expect(normalizeChainId('BSC')).toBe('binance') + expect(normalizeChainId('ETH')).toBe('ethereum') + expect(normalizeChainId('TRON')).toBe('tron') + }) + + it('should handle lowercase variants', () => { + expect(normalizeChainId('bsc')).toBe('binance') + expect(normalizeChainId('eth')).toBe('ethereum') + }) + + it('should return lowercase for unknown chains', () => { + expect(normalizeChainId('UNKNOWN')).toBe('unknown') + }) + }) + + describe('EVM_CHAIN_IDS', () => { + it('should contain expected chains', () => { + expect(EVM_CHAIN_IDS.ethereum).toBe(1) + expect(EVM_CHAIN_IDS.binance).toBe(56) + }) + }) +}) diff --git a/packages/bio-sdk/src/chain-id.ts b/packages/bio-sdk/src/chain-id.ts new file mode 100644 index 000000000..d9daa485b --- /dev/null +++ b/packages/bio-sdk/src/chain-id.ts @@ -0,0 +1,91 @@ +/** + * Chain ID utilities + * Maps between KeyApp internal chain IDs and standard chain IDs + */ + +/** EVM Chain ID mapping (decimal) */ +export const EVM_CHAIN_IDS: Record = { + ethereum: 1, + binance: 56, + // Future chains + // polygon: 137, + // arbitrum: 42161, + // optimism: 10, +} as const + +/** Reverse mapping: EVM chainId -> KeyApp chain ID */ +export const EVM_CHAIN_ID_TO_KEYAPP: Record = Object.fromEntries( + Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key]) +) + +/** API chain name to KeyApp chain ID mapping */ +export const API_CHAIN_TO_KEYAPP: Record = { + ETH: 'ethereum', + BSC: 'binance', + TRON: 'tron', + // Lowercase variants + eth: 'ethereum', + bsc: 'binance', + tron: 'tron', +} as const + +/** KeyApp chain ID to display name */ +export const CHAIN_DISPLAY_NAMES: Record = { + ethereum: 'Ethereum', + binance: 'BNB Smart Chain', + tron: 'Tron', + bfmeta: 'BFMeta', + bfchain: 'BFChain', +} as const + +/** + * Convert decimal chain ID to hex string (EIP-155 format) + * @example toHexChainId(56) => '0x38' + */ +export function toHexChainId(chainId: number): string { + return `0x${chainId.toString(16)}` +} + +/** + * Parse hex chain ID to decimal + * @example parseHexChainId('0x38') => 56 + */ +export function parseHexChainId(hexChainId: string): number { + if (!hexChainId.startsWith('0x')) { + throw new Error(`Invalid hex chain ID: ${hexChainId}`) + } + return parseInt(hexChainId, 16) +} + +/** + * Get KeyApp chain ID from EVM hex chain ID + * @example getKeyAppChainId('0x38') => 'binance' + */ +export function getKeyAppChainId(hexChainId: string): string | null { + const decimal = parseHexChainId(hexChainId) + return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null +} + +/** + * Get EVM hex chain ID from KeyApp chain ID + * @example getEvmChainId('binance') => '0x38' + */ +export function getEvmChainId(keyAppChainId: string): string | null { + const decimal = EVM_CHAIN_IDS[keyAppChainId] + return decimal ? toHexChainId(decimal) : null +} + +/** + * Check if a chain is EVM compatible + */ +export function isEvmChain(chainId: string): boolean { + return chainId in EVM_CHAIN_IDS +} + +/** + * Normalize API chain name to KeyApp chain ID + * @example normalizeChainId('BSC') => 'binance' + */ +export function normalizeChainId(chainName: string): string { + return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase() +} diff --git a/packages/bio-sdk/src/ethereum-provider.ts b/packages/bio-sdk/src/ethereum-provider.ts new file mode 100644 index 000000000..eec8d4e44 --- /dev/null +++ b/packages/bio-sdk/src/ethereum-provider.ts @@ -0,0 +1,295 @@ +/** + * Ethereum Provider (EIP-1193 Compatible) + * + * Provides window.ethereum for EVM-compatible dApps. + * Communicates with KeyApp host via postMessage. + */ + +import { EventEmitter } from './events' +import { BioErrorCodes, createProviderError, type ProviderRpcError } from './types' +import { toHexChainId, parseHexChainId, getKeyAppChainId, EVM_CHAIN_IDS } from './chain-id' + +/** EIP-1193 Request Arguments */ +export interface EthRequestArguments { + method: string + params?: unknown[] | Record +} + +/** EIP-1193 Provider Connect Info */ +export interface ProviderConnectInfo { + chainId: string +} + +/** EIP-1193 Provider Message */ +export interface ProviderMessage { + type: string + data: unknown +} + +/** Transaction request (eth_sendTransaction) */ +export interface TransactionRequest { + from: string + to?: string + value?: string + data?: string + gas?: string + gasPrice?: string + maxFeePerGas?: string + maxPriorityFeePerGas?: string + nonce?: string +} + +/** Message sent to host */ +interface RequestMessage { + type: 'eth_request' + id: string + method: string + params?: unknown[] +} + +/** Response from host */ +interface ResponseMessage { + type: 'eth_response' + id: string + success: boolean + result?: unknown + error?: { code: number; message: string; data?: unknown } +} + +/** Event from host */ +interface EventMessage { + type: 'eth_event' + event: string + args: unknown[] +} + +type HostMessage = ResponseMessage | EventMessage + +/** + * EIP-1193 Ethereum Provider Implementation + */ +export class EthereumProvider { + private events = new EventEmitter() + private pendingRequests = new Map void + reject: (error: Error) => void + }>() + private requestIdCounter = 0 + private connected = false + private currentChainId: string | null = null + private accounts: string[] = [] + private readonly targetOrigin: string + + // EIP-1193 required properties + readonly isMetaMask = false + readonly isKeyApp = true + + constructor(targetOrigin = '*') { + this.targetOrigin = targetOrigin + this.setupMessageListener() + } + + private setupMessageListener(): void { + window.addEventListener('message', this.handleMessage.bind(this)) + } + + private handleMessage(event: MessageEvent): void { + const data = event.data as HostMessage + if (!data || typeof data !== 'object') return + + if (data.type === 'eth_response') { + this.handleResponse(data) + } else if (data.type === 'eth_event') { + this.handleEvent(data) + } + } + + private handleResponse(message: ResponseMessage): void { + const pending = this.pendingRequests.get(message.id) + if (!pending) return + + this.pendingRequests.delete(message.id) + + if (message.success) { + pending.resolve(message.result) + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' } + pending.reject(createProviderError(error.code, error.message, error.data)) + } + } + + private handleEvent(message: EventMessage): void { + this.events.emit(message.event, ...message.args) + + // Handle built-in events + if (message.event === 'connect') { + this.connected = true + const info = message.args[0] as ProviderConnectInfo + this.currentChainId = info?.chainId ?? null + } else if (message.event === 'disconnect') { + this.connected = false + this.accounts = [] + } else if (message.event === 'chainChanged') { + this.currentChainId = message.args[0] as string + } else if (message.event === 'accountsChanged') { + this.accounts = message.args[0] as string[] + } + } + + private generateId(): string { + return `eth_${Date.now()}_${++this.requestIdCounter}` + } + + private postMessage(message: RequestMessage): void { + if (window.parent === window) { + console.warn('[EthereumProvider] Not running in iframe, cannot communicate with host') + return + } + window.parent.postMessage(message, this.targetOrigin) + } + + /** + * EIP-1193 request method + */ + async request(args: EthRequestArguments): Promise { + const { method, params } = args + const paramsArray = Array.isArray(params) ? params : params ? [params] : [] + + const id = this.generateId() + + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve: resolve as (value: unknown) => void, + reject, + }) + + this.postMessage({ + type: 'eth_request', + id, + method, + params: paramsArray, + }) + + // Timeout after 5 minutes (for user interactions) + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id) + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout')) + } + }, 5 * 60 * 1000) + }) + } + + /** + * Subscribe to an event + */ + on(event: string, handler: (...args: unknown[]) => void): this { + this.events.on(event, handler) + return this + } + + /** + * Unsubscribe from an event + */ + off(event: string, handler: (...args: unknown[]) => void): this { + this.events.off(event, handler) + return this + } + + /** + * Alias for off (Node.js EventEmitter compatibility) + */ + removeListener(event: string, handler: (...args: unknown[]) => void): this { + return this.off(event, handler) + } + + /** + * Add listener that fires only once + */ + once(event: string, handler: (...args: unknown[]) => void): this { + const wrapper = (...args: unknown[]) => { + this.off(event, wrapper) + handler(...args) + } + this.on(event, wrapper) + return this + } + + /** + * EIP-1193 isConnected method + */ + isConnected(): boolean { + return this.connected + } + + /** + * Get current chain ID (cached) + */ + get chainId(): string | null { + return this.currentChainId + } + + /** + * Get selected address (first account) + */ + get selectedAddress(): string | null { + return this.accounts[0] ?? null + } + + // ============================================ + // Legacy methods (for backwards compatibility) + // ============================================ + + /** + * @deprecated Use request({ method: 'eth_requestAccounts' }) + */ + async enable(): Promise { + return this.request({ method: 'eth_requestAccounts' }) + } + + /** + * @deprecated Use request() + */ + send(method: string, params?: unknown[]): Promise { + return this.request({ method, params }) + } + + /** + * @deprecated Use request() + */ + sendAsync( + payload: { method: string; params?: unknown[]; id?: number }, + callback: (error: Error | null, result?: { result: unknown }) => void + ): void { + this.request({ method: payload.method, params: payload.params }) + .then((result) => callback(null, { result })) + .catch((error) => callback(error)) + } +} + +// Extend Window interface +declare global { + interface Window { + ethereum?: EthereumProvider + } +} + +/** + * Initialize and inject the Ethereum provider into window.ethereum + */ +export function initEthereumProvider(targetOrigin = '*'): EthereumProvider { + if (typeof window === 'undefined') { + throw new Error('[EthereumProvider] Cannot initialize: window is not defined') + } + + if (window.ethereum) { + console.warn('[EthereumProvider] Provider already exists, returning existing instance') + return window.ethereum + } + + const provider = new EthereumProvider(targetOrigin) + window.ethereum = provider + + console.log('[EthereumProvider] Provider initialized') + return provider +} diff --git a/packages/bio-sdk/src/index.ts b/packages/bio-sdk/src/index.ts index a9dfbf8f1..4d6e022e4 100644 --- a/packages/bio-sdk/src/index.ts +++ b/packages/bio-sdk/src/index.ts @@ -1,26 +1,40 @@ /** * Bio SDK - Client SDK for Bio Ecosystem MiniApps * - * Injects `window.bio` provider similar to `window.ethereum` in Web3 DApps. + * Injects providers for multi-chain dApp support: + * - `window.bio` - BioChain + KeyApp wallet features + * - `window.ethereum` - EVM-compatible chains (ETH, BSC) + * - `window.tronWeb` / `window.tronLink` - Tron chain * * @example * ```typescript * import '@biochain/bio-sdk' * - * // Now window.bio is available + * // BioChain operations * const accounts = await window.bio.request({ method: 'bio_requestAccounts' }) + * + * // EVM operations (ETH, BSC) + * const ethAccounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) + * + * // Tron operations + * const tronAccounts = await window.tronLink.request({ method: 'tron_requestAccounts' }) * ``` */ import { BioProviderImpl } from './provider' +import { EthereumProvider, initEthereumProvider } from './ethereum-provider' +import { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider' import type { BioProvider } from './types' // Re-export types export * from './types' +export * from './chain-id' export { EventEmitter } from './events' export { BioProviderImpl } from './provider' +export { EthereumProvider, initEthereumProvider } from './ethereum-provider' +export { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider' -// Extend Window interface +// Extend Window interface (bio is declared in types.ts already for ethereum/tron) declare global { interface Window { bio?: BioProvider @@ -47,12 +61,34 @@ export function initBioProvider(targetOrigin = '*'): BioProvider { return provider } +/** + * Initialize all providers (bio, ethereum, tron) + */ +export function initAllProviders(targetOrigin = '*'): { + bio: BioProvider + ethereum: EthereumProvider + tronLink: TronLinkProvider + tronWeb: TronWebProvider +} { + const bio = initBioProvider(targetOrigin) + const ethereum = initEthereumProvider(targetOrigin) + const { tronLink, tronWeb } = initTronProvider(targetOrigin) + + return { bio, ethereum, tronLink, tronWeb } +} + // Auto-initialize if running in browser if (typeof window !== 'undefined') { + const init = () => { + initBioProvider() + initEthereumProvider() + initTronProvider() + } + // Use a slight delay to ensure DOM is ready if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => initBioProvider()) + document.addEventListener('DOMContentLoaded', init) } else { - initBioProvider() + init() } } diff --git a/packages/bio-sdk/src/tron-provider.ts b/packages/bio-sdk/src/tron-provider.ts new file mode 100644 index 000000000..5539815d6 --- /dev/null +++ b/packages/bio-sdk/src/tron-provider.ts @@ -0,0 +1,310 @@ +/** + * Tron Provider (TronLink Compatible) + * + * Provides window.tronWeb and window.tronLink for Tron dApps. + * Communicates with KeyApp host via postMessage. + */ + +import { EventEmitter } from './events' +import { BioErrorCodes, createProviderError } from './types' + +/** Tron address format */ +export interface TronAddress { + base58: string + hex: string +} + +/** TronLink request arguments (EIP-1193 style) */ +export interface TronRequestArguments { + method: string + params?: unknown +} + +/** Message sent to host */ +interface RequestMessage { + type: 'tron_request' + id: string + method: string + params?: unknown[] +} + +/** Response from host */ +interface ResponseMessage { + type: 'tron_response' + id: string + success: boolean + result?: unknown + error?: { code: number; message: string; data?: unknown } +} + +/** Event from host */ +interface EventMessage { + type: 'tron_event' + event: string + args: unknown[] +} + +type HostMessage = ResponseMessage | EventMessage + +/** + * TronLink-compatible Provider + */ +export class TronLinkProvider { + private events = new EventEmitter() + private pendingRequests = new Map void + reject: (error: Error) => void + }>() + private requestIdCounter = 0 + private readonly targetOrigin: string + + constructor(targetOrigin = '*') { + this.targetOrigin = targetOrigin + this.setupMessageListener() + } + + private setupMessageListener(): void { + window.addEventListener('message', this.handleMessage.bind(this)) + } + + private handleMessage(event: MessageEvent): void { + const data = event.data as HostMessage + if (!data || typeof data !== 'object') return + + if (data.type === 'tron_response') { + this.handleResponse(data) + } else if (data.type === 'tron_event') { + this.handleEvent(data) + } + } + + private handleResponse(message: ResponseMessage): void { + const pending = this.pendingRequests.get(message.id) + if (!pending) return + + this.pendingRequests.delete(message.id) + + if (message.success) { + pending.resolve(message.result) + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' } + pending.reject(createProviderError(error.code, error.message, error.data)) + } + } + + private handleEvent(message: EventMessage): void { + this.events.emit(message.event, ...message.args) + } + + private generateId(): string { + return `tron_${Date.now()}_${++this.requestIdCounter}` + } + + private postMessage(message: RequestMessage): void { + if (window.parent === window) { + console.warn('[TronLinkProvider] Not running in iframe, cannot communicate with host') + return + } + window.parent.postMessage(message, this.targetOrigin) + } + + /** + * TronLink request method (EIP-1193 style) + */ + async request(args: TronRequestArguments): Promise { + const { method, params } = args + const paramsArray = Array.isArray(params) ? params : params !== undefined ? [params] : [] + + const id = this.generateId() + + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve: resolve as (value: unknown) => void, + reject, + }) + + this.postMessage({ + type: 'tron_request', + id, + method, + params: paramsArray, + }) + + // Timeout after 5 minutes + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id) + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout')) + } + }, 5 * 60 * 1000) + }) + } + + on(event: string, handler: (...args: unknown[]) => void): this { + this.events.on(event, handler) + return this + } + + off(event: string, handler: (...args: unknown[]) => void): this { + this.events.off(event, handler) + return this + } +} + +/** + * TronWeb-compatible API + * Provides the subset of TronWeb API that KeyApp supports + */ +export class TronWebProvider { + private tronLink: TronLinkProvider + private _ready = false + private _defaultAddress: TronAddress = { base58: '', hex: '' } + + /** TRX operations */ + readonly trx: TronWebTrx + + constructor(tronLink: TronLinkProvider) { + this.tronLink = tronLink + this.trx = new TronWebTrx(tronLink) + + // Listen for account changes + tronLink.on('accountsChanged', (accounts: unknown) => { + if (Array.isArray(accounts) && accounts.length > 0) { + const addr = accounts[0] as TronAddress + this._defaultAddress = addr + this._ready = true + } else { + this._defaultAddress = { base58: '', hex: '' } + this._ready = false + } + }) + } + + /** Whether TronWeb is ready (connected) */ + get ready(): boolean { + return this._ready + } + + /** Current default address */ + get defaultAddress(): TronAddress { + return this._defaultAddress + } + + /** + * Set default address (called by host after connection) + */ + setAddress(address: TronAddress): void { + this._defaultAddress = address + this._ready = true + } + + /** + * Check if an address is valid + */ + isAddress(address: string): boolean { + // Basic validation: base58 starts with T, hex starts with 41 + if (address.startsWith('T')) { + return address.length === 34 + } + if (address.startsWith('41')) { + return address.length === 42 + } + return false + } + + /** + * Convert address to hex format + */ + address = { + toHex: (base58: string): string => { + // This is a stub - actual conversion requires TronWeb library + // KeyApp will handle the conversion on the host side + return base58 + }, + fromHex: (hex: string): string => { + return hex + }, + } +} + +/** + * TronWeb.trx operations + */ +class TronWebTrx { + private tronLink: TronLinkProvider + + constructor(tronLink: TronLinkProvider) { + this.tronLink = tronLink + } + + /** + * Sign a transaction + */ + async sign(transaction: unknown): Promise { + return this.tronLink.request({ + method: 'tron_signTransaction', + params: transaction, + }) + } + + /** + * Send raw transaction (broadcast) + */ + async sendRawTransaction(signedTransaction: unknown): Promise { + return this.tronLink.request({ + method: 'tron_sendRawTransaction', + params: signedTransaction, + }) + } + + /** + * Get account balance + */ + async getBalance(address: string): Promise { + return this.tronLink.request({ + method: 'tron_getBalance', + params: address, + }) + } + + /** + * Get account info + */ + async getAccount(address: string): Promise { + return this.tronLink.request({ + method: 'tron_getAccount', + params: address, + }) + } +} + +// Extend Window interface +declare global { + interface Window { + tronLink?: TronLinkProvider + tronWeb?: TronWebProvider + } +} + +/** + * Initialize and inject the Tron providers + */ +export function initTronProvider(targetOrigin = '*'): { tronLink: TronLinkProvider; tronWeb: TronWebProvider } { + if (typeof window === 'undefined') { + throw new Error('[TronProvider] Cannot initialize: window is not defined') + } + + if (window.tronLink && window.tronWeb) { + console.warn('[TronProvider] Providers already exist, returning existing instances') + return { tronLink: window.tronLink, tronWeb: window.tronWeb } + } + + const tronLink = new TronLinkProvider(targetOrigin) + const tronWeb = new TronWebProvider(tronLink) + + window.tronLink = tronLink + window.tronWeb = tronWeb + + console.log('[TronProvider] Providers initialized') + return { tronLink, tronWeb } +} diff --git a/src/services/ecosystem/bridge.ts b/src/services/ecosystem/bridge.ts index 000a99325..0ac2a5c97 100644 --- a/src/services/ecosystem/bridge.ts +++ b/src/services/ecosystem/bridge.ts @@ -9,6 +9,60 @@ import type { MethodHandler, HandlerContext, } from './types' + +/** EVM request message from miniapp */ +interface EthRequestMessage { + type: 'eth_request' + id: string + method: string + params?: unknown[] +} + +/** EVM response message to miniapp */ +interface EthResponseMessage { + type: 'eth_response' + id: string + success: boolean + result?: unknown + error?: { code: number; message: string; data?: unknown } +} + +/** EVM event message to miniapp */ +interface EthEventMessage { + type: 'eth_event' + event: string + args: unknown[] +} + +/** TRON request message from miniapp */ +interface TronRequestMessage { + type: 'tron_request' + id: string + method: string + params?: unknown[] +} + +/** TRON response message to miniapp */ +interface TronResponseMessage { + type: 'tron_response' + id: string + success: boolean + result?: unknown + error?: { code: number; message: string; data?: unknown } +} + +/** TRON event message to miniapp */ +interface TronEventMessage { + type: 'tron_event' + event: string + args: unknown[] +} + +/** All supported request types */ +type RequestMessage = BioRequestMessage | EthRequestMessage | TronRequestMessage + +/** Protocol type */ +type Protocol = 'bio' | 'eth' | 'tron' import { BioErrorCodes, createErrorResponse, @@ -87,12 +141,27 @@ export class PostMessageBridge { this.manifestPermissions = [] } - /** Send event to miniapp */ + /** Send event to miniapp (Bio protocol) */ emit(event: string, ...args: unknown[]): void { + this.emitTo('bio', event, ...args) + } + + /** Send event to miniapp (EVM protocol) */ + emitEth(event: string, ...args: unknown[]): void { + this.emitTo('eth', event, ...args) + } + + /** Send event to miniapp (TRON protocol) */ + emitTron(event: string, ...args: unknown[]): void { + this.emitTo('tron', event, ...args) + } + + /** Send event to specific protocol */ + private emitTo(protocol: Protocol, event: string, ...args: unknown[]): void { if (!this.iframe?.contentWindow) return - const message: BioEventMessage = { - type: 'bio_event', + const message: BioEventMessage | EthEventMessage | TronEventMessage = { + type: `${protocol}_event` as 'bio_event' | 'eth_event' | 'tron_event', event, args, } @@ -111,32 +180,40 @@ export class PostMessageBridge { return } - const data = event.data as BioRequestMessage - if (!data || data.type !== 'bio_request') { + const data = event.data as RequestMessage + if (!data || typeof data !== 'object' || !('type' in data)) { return } - this.processRequest(data) + // Route to appropriate handler based on protocol + if (data.type === 'bio_request') { + this.processRequest(data, 'bio') + } else if (data.type === 'eth_request') { + this.processRequest(data as EthRequestMessage, 'eth') + } else if (data.type === 'tron_request') { + this.processRequest(data as TronRequestMessage, 'tron') + } } - private async processRequest(request: BioRequestMessage): Promise { + private async processRequest(request: RequestMessage, protocol: Protocol): Promise { const { id, method, params } = request - console.log('[BioProvider] Request:', method, params) + console.log(`[BioProvider] ${protocol.toUpperCase()} Request:`, method, params) // Check if handler exists const handler = this.handlers.get(method) if (!handler) { - this.sendResponse(createErrorResponse( + this.sendResponse(protocol, { + type: `${protocol}_response` as 'bio_response', id, - BioErrorCodes.METHOD_NOT_FOUND, - `Method not found: ${method}` - )) + success: false, + error: { code: BioErrorCodes.METHOD_NOT_FOUND, message: `Method not found: ${method}` }, + }) return } - // Check permissions - const skipPermissionCheck = ['bio_connect', 'bio_closeSplashScreen'].includes(method) + // Permission check (skip for EVM/TRON - they use their own connection flow) + const skipPermissionCheck = protocol !== 'bio' || ['bio_connect', 'bio_closeSplashScreen'].includes(method) if (!skipPermissionCheck) { const accountRelatedMethods = ['bio_accounts', 'bio_selectAccount', 'bio_pickWallet'] const shouldMapToRequestAccounts = @@ -148,11 +225,7 @@ export class PostMessageBridge { this.manifestPermissions.includes(method) || method === 'bio_requestAccounts' || shouldMapToRequestAccounts if (!isDeclaredInManifest) { - this.sendResponse(createErrorResponse( - id, - BioErrorCodes.UNAUTHORIZED, - `Permission not declared in manifest: ${method}` - )) + this.sendResponse(protocol, createErrorResponse(id, BioErrorCodes.UNAUTHORIZED, `Permission not declared in manifest: ${method}`)) return } @@ -163,11 +236,7 @@ export class PostMessageBridge { // 请求权限 const approved = await this.requestPermission([permissionKey]) if (!approved) { - this.sendResponse(createErrorResponse( - id, - BioErrorCodes.USER_REJECTED, - 'Permission denied by user' - )) + this.sendResponse(protocol, createErrorResponse(id, BioErrorCodes.USER_REJECTED, 'Permission denied by user')) return } } @@ -184,11 +253,21 @@ export class PostMessageBridge { } const result = await handler(params?.[0], context) - this.sendResponse(createSuccessResponse(id, result)) + this.sendResponse(protocol, { + type: `${protocol}_response` as 'bio_response', + id, + success: true, + result, + }) } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error' const code = (error as { code?: number }).code ?? BioErrorCodes.INTERNAL_ERROR - this.sendResponse(createErrorResponse(id, code, message)) + this.sendResponse(protocol, { + type: `${protocol}_response` as 'bio_response', + id, + success: false, + error: { code, message }, + }) } } @@ -215,10 +294,10 @@ export class PostMessageBridge { } } - private sendResponse(response: BioResponseMessage): void { + private sendResponse(protocol: Protocol, response: BioResponseMessage | EthResponseMessage | TronResponseMessage): void { if (!this.iframe?.contentWindow) return - console.log('[BioProvider] Response:', response) + console.log(`[BioProvider] ${protocol.toUpperCase()} Response:`, response) this.iframe.contentWindow.postMessage(response, this.origin) } } diff --git a/src/services/ecosystem/handlers/context.ts b/src/services/ecosystem/handlers/context.ts index f157d27ae..4ecfb205a 100644 --- a/src/services/ecosystem/handlers/context.ts +++ b/src/services/ecosystem/handlers/context.ts @@ -5,6 +5,28 @@ import type { BioAccount, BioSignedTransaction, BioUnsignedTransaction, TransferParams } from '../types' +/** EVM 交易请求类型 */ +export interface EvmTransactionRequest { + from: string + to?: string + value?: string + data?: string + gas?: string + gasPrice?: string + maxFeePerGas?: string + maxPriorityFeePerGas?: string + nonce?: string + chainId?: string +} + +/** TRON 交易类型 */ +export interface TronTransaction { + txID: string + raw_data: unknown + raw_data_hex: string + signature?: string[] +} + /** 小程序信息(用于在 Sheet 中显示) */ export interface MiniappInfo { name: string @@ -32,13 +54,42 @@ export interface SignTransactionParams { app: MiniappInfo } +/** EVM 签名参数 */ +export interface EvmSigningParams { + message: string + address: string + appName: string +} + +/** EVM 交易参数 */ +export interface EvmTransactionParams { + tx: EvmTransactionRequest + appName: string +} + +/** TRON 签名参数 */ +export interface TronSigningParams { + transaction: TronTransaction + appName: string +} + /** Handler 回调接口 */ export interface HandlerCallbacks { + // Bio (BioChain) callbacks showWalletPicker: (opts?: { chain?: string; exclude?: string; app?: MiniappInfo }) => Promise getConnectedAccounts: () => BioAccount[] showSigningDialog: (params: SigningParams) => Promise showTransferDialog: (params: TransferParams & { app: MiniappInfo }) => Promise<{ txHash: string } | null> showSignTransactionDialog: (params: SignTransactionParams) => Promise + + // EVM (Ethereum/BSC) callbacks + showEvmWalletPicker?: (opts: { chainId: string }) => Promise + showEvmSigningDialog?: (params: EvmSigningParams) => Promise<{ signature: string } | null> + showEvmTransactionDialog?: (params: EvmTransactionParams) => Promise<{ txHash: string } | null> + + // TRON callbacks + showTronWalletPicker?: () => Promise + showTronSigningDialog?: (params: TronSigningParams) => Promise<{ signedTransaction: TronTransaction } | null> } /** 回调注册表 */ diff --git a/src/services/ecosystem/handlers/evm.ts b/src/services/ecosystem/handlers/evm.ts new file mode 100644 index 000000000..355417474 --- /dev/null +++ b/src/services/ecosystem/handlers/evm.ts @@ -0,0 +1,354 @@ +/** + * EVM (Ethereum/BSC) method handlers + * + * Handles window.ethereum requests from miniapps. + * Maps EIP-1193 methods to KeyApp wallet operations. + */ + +import type { MethodHandler, BioAccount } from '../types' +import { BioErrorCodes } from '../types' +import { HandlerContext } from './context' +import { + toHexChainId, + parseHexChainId, + getKeyAppChainId, + getEvmChainId, + EVM_CHAIN_IDS, +} from '@biochain/bio-sdk' + +import type { EvmTransactionRequest } from './context' + +// Re-export for convenience +export type { EvmTransactionRequest } from './context' + +// ============================================ +// Types +// ============================================ + +/** EVM Sign Request */ +export interface EvmSignRequest { + address: string + message: string +} + +/** Typed Data (EIP-712) */ +export interface TypedDataV4 { + types: Record> + primaryType: string + domain: { + name?: string + version?: string + chainId?: number + verifyingContract?: string + salt?: string + } + message: Record +} + +// ============================================ +// State +// ============================================ + +/** Current selected chain for each app */ +const appChainState = new Map() + +/** Connected accounts for each app */ +const appAccountState = new Map() + +// ============================================ +// Callback setters (for UI integration) +// ============================================ + +let _showEvmWalletPicker: ((opts: { chainId: string }) => Promise) | null = null +let _showChainSwitchConfirm: ((opts: { fromChainId: string; toChainId: string; appName: string }) => Promise) | null = null +let _showEvmSigningDialog: ((opts: { message: string; address: string; appName: string }) => Promise<{ signature: string } | null>) | null = null +let _showEvmTransactionDialog: ((opts: { tx: EvmTransactionRequest; appName: string }) => Promise<{ txHash: string } | null>) | null = null + +export function setEvmWalletPicker(picker: typeof _showEvmWalletPicker): void { + _showEvmWalletPicker = picker +} + +export function setChainSwitchConfirm(confirm: typeof _showChainSwitchConfirm): void { + _showChainSwitchConfirm = confirm +} + +export function setEvmSigningDialog(dialog: typeof _showEvmSigningDialog): void { + _showEvmSigningDialog = dialog +} + +export function setEvmTransactionDialog(dialog: typeof _showEvmTransactionDialog): void { + _showEvmTransactionDialog = dialog +} + +// ============================================ +// Helper functions +// ============================================ + +function getWalletPicker(appId: string) { + const callbacks = HandlerContext.get(appId) + return callbacks?.showEvmWalletPicker ?? _showEvmWalletPicker +} + +function getSigningDialog(appId: string) { + const callbacks = HandlerContext.get(appId) + return callbacks?.showEvmSigningDialog ?? _showEvmSigningDialog +} + +function getTransactionDialog(appId: string) { + const callbacks = HandlerContext.get(appId) + return callbacks?.showEvmTransactionDialog ?? _showEvmTransactionDialog +} + +function getCurrentChainId(appId: string): string { + // Default to BSC + return appChainState.get(appId) ?? toHexChainId(EVM_CHAIN_IDS.binance) +} + +function setCurrentChainId(appId: string, chainId: string): void { + appChainState.set(appId, chainId) +} + +function getConnectedAccounts(appId: string): string[] { + return appAccountState.get(appId) ?? [] +} + +function setConnectedAccounts(appId: string, accounts: string[]): void { + appAccountState.set(appId, accounts) +} + +// ============================================ +// Handlers +// ============================================ + +/** eth_chainId - Get current chain ID */ +export const handleEthChainId: MethodHandler = async (_params, context) => { + return getCurrentChainId(context.appId) +} + +/** eth_requestAccounts - Request wallet connection */ +export const handleEthRequestAccounts: MethodHandler = async (_params, context) => { + const showWalletPicker = getWalletPicker(context.appId) + if (!showWalletPicker) { + throw Object.assign(new Error('Wallet picker not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + const chainId = getCurrentChainId(context.appId) + const wallet = await showWalletPicker({ chainId }) + if (!wallet) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + const accounts = [wallet.address] + setConnectedAccounts(context.appId, accounts) + return accounts +} + +/** eth_accounts - Get connected accounts (no UI) */ +export const handleEthAccounts: MethodHandler = async (_params, context) => { + return getConnectedAccounts(context.appId) +} + +/** wallet_switchEthereumChain - Switch to a different chain */ +export const handleSwitchChain: MethodHandler = async (params, context) => { + const request = params as { chainId: string } | undefined + if (!request?.chainId) { + throw Object.assign(new Error('Missing chainId'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + const targetChainId = request.chainId + const keyAppChainId = getKeyAppChainId(targetChainId) + + if (!keyAppChainId) { + // Chain not supported + throw Object.assign( + new Error(`Chain ${targetChainId} not supported`), + { code: 4902 } // EIP-3326 chain not added + ) + } + + const currentChainId = getCurrentChainId(context.appId) + if (currentChainId === targetChainId) { + // Already on this chain + return null + } + + // Show confirmation dialog + if (_showChainSwitchConfirm) { + const approved = await _showChainSwitchConfirm({ + fromChainId: currentChainId, + toChainId: targetChainId, + appName: context.appName, + }) + if (!approved) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + } + + setCurrentChainId(context.appId, targetChainId) + + // Note: chainChanged event should be emitted by the bridge + return null +} + +/** wallet_addEthereumChain - Add a new chain (not supported) */ +export const handleAddChain: MethodHandler = async (_params, _context) => { + throw Object.assign( + new Error('Adding custom chains is not supported'), + { code: BioErrorCodes.UNSUPPORTED_METHOD } + ) +} + +/** personal_sign - Sign a message */ +export const handlePersonalSign: MethodHandler = async (params, context) => { + // personal_sign params: [message, address] + const [message, address] = params as [string, string] + if (!message || !address) { + throw Object.assign(new Error('Missing message or address'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + const showSigningDialog = getSigningDialog(context.appId) + if (!showSigningDialog) { + throw Object.assign(new Error('Signing dialog not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + const result = await showSigningDialog({ message, address, appName: context.appName }) + if (!result) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + return result.signature +} + +/** eth_sign - Sign data (deprecated, same as personal_sign) */ +export const handleEthSign: MethodHandler = async (params, context) => { + // eth_sign params: [address, message] (order is reversed from personal_sign) + const [address, message] = params as [string, string] + return handlePersonalSign([message, address], context) +} + +/** eth_signTypedData_v4 - Sign EIP-712 typed data */ +export const handleSignTypedDataV4: MethodHandler = async (params, context) => { + const [address, typedData] = params as [string, string | TypedDataV4] + if (!address || !typedData) { + throw Object.assign(new Error('Missing address or typedData'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + const data = typeof typedData === 'string' ? JSON.parse(typedData) : typedData + + const showSigningDialog = getSigningDialog(context.appId) + if (!showSigningDialog) { + throw Object.assign(new Error('Signing dialog not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + // Format typed data for display + const displayMessage = JSON.stringify(data, null, 2) + + const result = await showSigningDialog({ + message: displayMessage, + address, + appName: context.appName, + }) + if (!result) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + return result.signature +} + +/** eth_sendTransaction - Send a transaction */ +export const handleEthSendTransaction: MethodHandler = async (params, context) => { + const tx = params as EvmTransactionRequest | undefined + if (!tx?.from) { + throw Object.assign(new Error('Missing transaction data'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + const showTransactionDialog = getTransactionDialog(context.appId) + if (!showTransactionDialog) { + throw Object.assign(new Error('Transaction dialog not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + // Add current chainId if not specified + if (!tx.chainId) { + tx.chainId = getCurrentChainId(context.appId) + } + + const result = await showTransactionDialog({ tx, appName: context.appName }) + if (!result) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + return result.txHash +} + +/** eth_signTransaction - Sign transaction without broadcasting */ +export const handleEthSignTransaction: MethodHandler = async (params, context) => { + const tx = params as EvmTransactionRequest | undefined + if (!tx?.from) { + throw Object.assign(new Error('Missing transaction data'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + // TODO: Implement sign-only (no broadcast) + // For now, not supported + throw Object.assign( + new Error('eth_signTransaction not yet supported, use eth_sendTransaction'), + { code: BioErrorCodes.UNSUPPORTED_METHOD } + ) +} + +/** eth_getBalance - Get account balance */ +export const handleEthGetBalance: MethodHandler = async (params, context) => { + const [address, _blockTag] = params as [string, string?] + if (!address) { + throw Object.assign(new Error('Missing address'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + // TODO: Query balance from chain adapter + // For now, return 0 + return '0x0' +} + +/** net_version - Get network version (decimal chain ID) */ +export const handleNetVersion: MethodHandler = async (_params, context) => { + const hexChainId = getCurrentChainId(context.appId) + const decimal = parseHexChainId(hexChainId) + return String(decimal) +} + +/** eth_blockNumber - Get current block number */ +export const handleEthBlockNumber: MethodHandler = async (_params, _context) => { + // TODO: Query from RPC + return '0x0' +} + +/** web3_clientVersion - Get client version */ +export const handleWeb3ClientVersion: MethodHandler = async (_params, _context) => { + return 'KeyApp/1.0.0' +} + +// ============================================ +// Export all handlers map +// ============================================ + +export const evmHandlers: Record = { + eth_chainId: handleEthChainId, + eth_requestAccounts: handleEthRequestAccounts, + eth_accounts: handleEthAccounts, + wallet_switchEthereumChain: handleSwitchChain, + wallet_addEthereumChain: handleAddChain, + personal_sign: handlePersonalSign, + eth_sign: handleEthSign, + eth_signTypedData_v4: handleSignTypedDataV4, + eth_sendTransaction: handleEthSendTransaction, + eth_signTransaction: handleEthSignTransaction, + eth_getBalance: handleEthGetBalance, + net_version: handleNetVersion, + eth_blockNumber: handleEthBlockNumber, + web3_clientVersion: handleWeb3ClientVersion, +} + +/** Register all EVM handlers with the bridge */ +export function registerEvmHandlers(registerHandler: (method: string, handler: MethodHandler) => void): void { + for (const [method, handler] of Object.entries(evmHandlers)) { + registerHandler(method, handler) + } +} diff --git a/src/services/ecosystem/handlers/index.ts b/src/services/ecosystem/handlers/index.ts index ebefa2e35..f5312a847 100644 --- a/src/services/ecosystem/handlers/index.ts +++ b/src/services/ecosystem/handlers/index.ts @@ -2,7 +2,18 @@ * Handler exports */ -export { HandlerContext, getCallbacksOrThrow, type HandlerCallbacks, type SigningParams, type SignTransactionParams } from './context' +export { + HandlerContext, + getCallbacksOrThrow, + type HandlerCallbacks, + type SigningParams, + type SignTransactionParams, + type EvmTransactionRequest, + type TronTransaction, + type EvmSigningParams, + type EvmTransactionParams, + type TronSigningParams, +} from './context' export { handleCloseSplashScreen } from './system' @@ -35,3 +46,22 @@ export { setSignTransactionDialog, signUnsignedTransaction, } from './transaction' + +// EVM handlers +export { + evmHandlers, + registerEvmHandlers, + setEvmWalletPicker, + setChainSwitchConfirm, + setEvmSigningDialog, + setEvmTransactionDialog, +} from './evm' + +// TRON handlers +export { + tronHandlers, + registerTronHandlers, + setTronWalletPicker, + setTronSigningDialog, + type TronAddress, +} from './tron' diff --git a/src/services/ecosystem/handlers/tron.ts b/src/services/ecosystem/handlers/tron.ts new file mode 100644 index 000000000..f7fc5c009 --- /dev/null +++ b/src/services/ecosystem/handlers/tron.ts @@ -0,0 +1,214 @@ +/** + * TRON method handlers + * + * Handles window.tronLink/tronWeb requests from miniapps. + * Maps TronLink API to KeyApp wallet operations. + */ + +import type { MethodHandler, BioAccount } from '../types' +import { BioErrorCodes } from '../types' +import { HandlerContext, type TronTransaction } from './context' + +// Re-export for convenience +export type { TronTransaction } from './context' + +// ============================================ +// Types +// ============================================ + +/** Tron address format */ +export interface TronAddress { + base58: string + hex: string +} + +// ============================================ +// State +// ============================================ + +/** Connected address for each app */ +const appAddressState = new Map() + +// ============================================ +// Callback setters (for UI integration) +// ============================================ + +let _showTronWalletPicker: (() => Promise) | null = null +let _showTronSigningDialog: ((opts: { transaction: TronTransaction; appName: string }) => Promise<{ signedTransaction: TronTransaction } | null>) | null = null + +export function setTronWalletPicker(picker: typeof _showTronWalletPicker): void { + _showTronWalletPicker = picker +} + +export function setTronSigningDialog(dialog: typeof _showTronSigningDialog): void { + _showTronSigningDialog = dialog +} + +// ============================================ +// Helper functions +// ============================================ + +function getWalletPicker(appId: string) { + const callbacks = HandlerContext.get(appId) + return callbacks?.showTronWalletPicker ?? _showTronWalletPicker +} + +function getSigningDialog(appId: string) { + const callbacks = HandlerContext.get(appId) + return callbacks?.showTronSigningDialog ?? _showTronSigningDialog +} + +function getCurrentAddress(appId: string): TronAddress | null { + return appAddressState.get(appId) ?? null +} + +function setCurrentAddress(appId: string, address: TronAddress): void { + appAddressState.set(appId, address) +} + +/** + * Convert KeyApp BioAccount to TronAddress + * Note: KeyApp stores Tron addresses in base58 format + */ +function bioAccountToTronAddress(account: BioAccount): TronAddress { + return { + base58: account.address, + // Hex conversion would require TronWeb library + // For now, we return base58 for both (host will convert) + hex: account.address, + } +} + +// ============================================ +// Handlers +// ============================================ + +/** tron_requestAccounts - Request wallet connection */ +export const handleTronRequestAccounts: MethodHandler = async (_params, context) => { + const showWalletPicker = getWalletPicker(context.appId) + if (!showWalletPicker) { + throw Object.assign(new Error('Wallet picker not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + const wallet = await showWalletPicker() + if (!wallet) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + const tronAddress = bioAccountToTronAddress(wallet) + setCurrentAddress(context.appId, tronAddress) + + return { + code: 200, + message: 'ok', + data: tronAddress, + } +} + +/** tron_accounts - Get connected accounts */ +export const handleTronAccounts: MethodHandler = async (_params, context) => { + const address = getCurrentAddress(context.appId) + if (!address) { + return { + code: 4000, + message: 'Not connected', + data: null, + } + } + + return { + code: 200, + message: 'ok', + data: address, + } +} + +/** tron_getDefaultAddress - Get current default address */ +export const handleTronGetDefaultAddress: MethodHandler = async (_params, context) => { + return getCurrentAddress(context.appId) +} + +/** tron_signTransaction - Sign a transaction */ +export const handleTronSignTransaction: MethodHandler = async (params, context) => { + const transaction = params as TronTransaction | undefined + if (!transaction?.txID) { + throw Object.assign(new Error('Invalid transaction'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + const showSigningDialog = getSigningDialog(context.appId) + if (!showSigningDialog) { + throw Object.assign(new Error('Signing dialog not available'), { code: BioErrorCodes.INTERNAL_ERROR }) + } + + const result = await showSigningDialog({ + transaction, + appName: context.appName, + }) + + if (!result) { + throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) + } + + return result.signedTransaction +} + +/** tron_sendRawTransaction - Broadcast signed transaction */ +export const handleTronSendRawTransaction: MethodHandler = async (params, _context) => { + const signedTransaction = params as TronTransaction | undefined + if (!signedTransaction?.txID || !signedTransaction.signature) { + throw Object.assign(new Error('Invalid signed transaction'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + // TODO: Broadcast to TRON network via chain adapter + // For now, return success with txID + return { + result: true, + txid: signedTransaction.txID, + } +} + +/** tron_getBalance - Get TRX balance */ +export const handleTronGetBalance: MethodHandler = async (params, _context) => { + const address = params as string | undefined + if (!address) { + throw Object.assign(new Error('Missing address'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + // TODO: Query balance from chain adapter + return 0 +} + +/** tron_getAccount - Get account info */ +export const handleTronGetAccount: MethodHandler = async (params, _context) => { + const address = params as string | undefined + if (!address) { + throw Object.assign(new Error('Missing address'), { code: BioErrorCodes.INVALID_PARAMS }) + } + + // TODO: Query account from chain adapter + return { + address, + balance: 0, + } +} + +// ============================================ +// Export all handlers map +// ============================================ + +export const tronHandlers: Record = { + tron_requestAccounts: handleTronRequestAccounts, + tron_accounts: handleTronAccounts, + tron_getDefaultAddress: handleTronGetDefaultAddress, + tron_signTransaction: handleTronSignTransaction, + tron_sendRawTransaction: handleTronSendRawTransaction, + tron_getBalance: handleTronGetBalance, + tron_getAccount: handleTronGetAccount, +} + +/** Register all TRON handlers with the bridge */ +export function registerTronHandlers(registerHandler: (method: string, handler: MethodHandler) => void): void { + for (const [method, handler] of Object.entries(tronHandlers)) { + registerHandler(method, handler) + } +} diff --git a/src/services/ecosystem/provider.ts b/src/services/ecosystem/provider.ts index 481b99bcd..b1878e4b3 100644 --- a/src/services/ecosystem/provider.ts +++ b/src/services/ecosystem/provider.ts @@ -19,11 +19,13 @@ import { handleCreateTransaction, handleSignTransaction, handleSendTransaction, + registerEvmHandlers, + registerTronHandlers, } from './handlers' /** Initialize the Bio provider with all handlers */ export function initBioProvider(): void { - // Wallet methods + // Bio methods (BioChain + wallet features) bridge.registerHandler('bio_connect', handleConnect) bridge.registerHandler('bio_closeSplashScreen', handleCloseSplashScreen) bridge.registerHandler('bio_requestAccounts', handleRequestAccounts) @@ -44,21 +46,44 @@ export function initBioProvider(): void { // Transfer methods bridge.registerHandler('bio_sendTransaction', handleSendTransaction) - console.log('[BioProvider] Initialized with handlers:', [ - 'bio_connect', - 'bio_closeSplashScreen', - 'bio_requestAccounts', - 'bio_accounts', - 'bio_selectAccount', - 'bio_pickWallet', - 'bio_chainId', - 'bio_getBalance', - 'bio_signMessage', - 'bio_signTypedData', - 'bio_createTransaction', - 'bio_signTransaction', - 'bio_sendTransaction', - ]) + // EVM methods (Ethereum/BSC via window.ethereum) + registerEvmHandlers((method, handler) => bridge.registerHandler(method, handler)) + + // TRON methods (via window.tronLink/tronWeb) + registerTronHandlers((method, handler) => bridge.registerHandler(method, handler)) + + console.log('[BioProvider] Initialized with handlers:', { + bio: [ + 'bio_connect', + 'bio_closeSplashScreen', + 'bio_requestAccounts', + 'bio_accounts', + 'bio_selectAccount', + 'bio_pickWallet', + 'bio_chainId', + 'bio_getBalance', + 'bio_signMessage', + 'bio_signTypedData', + 'bio_createTransaction', + 'bio_signTransaction', + 'bio_sendTransaction', + ], + evm: [ + 'eth_chainId', + 'eth_requestAccounts', + 'eth_accounts', + 'wallet_switchEthereumChain', + 'personal_sign', + 'eth_sendTransaction', + // ... + ], + tron: [ + 'tron_requestAccounts', + 'tron_accounts', + 'tron_signTransaction', + // ... + ], + }) } /** Get the bridge instance */ @@ -73,4 +98,12 @@ export { setSigningDialog, setTransferDialog, setSignTransactionDialog, + // EVM setters + setEvmWalletPicker, + setChainSwitchConfirm, + setEvmSigningDialog, + setEvmTransactionDialog, + // TRON setters + setTronWalletPicker, + setTronSigningDialog, } from './handlers' diff --git a/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx b/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx new file mode 100644 index 000000000..06259e55f --- /dev/null +++ b/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx @@ -0,0 +1,169 @@ +/** + * ChainSwitchConfirmJob - 链切换确认弹窗 + * 当 DApp 请求 wallet_switchEthereumChain 时显示 + */ + +import type { ActivityComponentType } from '@stackflow/react' +import { BottomSheet } from '@/components/layout/bottom-sheet' +import { useTranslation } from 'react-i18next' +import { IconArrowRight, IconAlertTriangle } from '@tabler/icons-react' +import { ChainIcon } from '@/components/wallet/chain-icon' +import { MiniappIcon } from '@/components/ecosystem' +import { useFlow } from '../../stackflow' +import { ActivityParamsProvider, useActivityParams } from '../../hooks' +import { parseHexChainId, getKeyAppChainId, CHAIN_DISPLAY_NAMES } from '@biochain/bio-sdk' +import type { ChainType } from '@/stores' + +type ChainSwitchConfirmJobParams = { + /** 当前链 ID (hex, e.g., '0x38') */ + fromChainId: string + /** 目标链 ID (hex, e.g., '0x1') */ + toChainId: string + /** 请求来源小程序名称 */ + appName?: string + /** 请求来源小程序图标 */ + appIcon?: string +} + +/** 获取链的显示名称 */ +function getChainDisplayName(hexChainId: string): string { + const keyAppId = getKeyAppChainId(hexChainId) + if (keyAppId && CHAIN_DISPLAY_NAMES[keyAppId]) { + return CHAIN_DISPLAY_NAMES[keyAppId] + } + // Fallback: 显示 decimal chainId + try { + const decimal = parseHexChainId(hexChainId) + return `Chain ${decimal}` + } catch { + return hexChainId + } +} + +/** 获取 KeyApp 链类型 */ +function getChainType(hexChainId: string): ChainType | null { + const keyAppId = getKeyAppChainId(hexChainId) + return keyAppId as ChainType | null +} + +function ChainSwitchConfirmJobContent() { + const { t } = useTranslation('common') + const { pop } = useFlow() + const { fromChainId, toChainId, appName, appIcon } = useActivityParams() + + const fromChainName = getChainDisplayName(fromChainId) + const toChainName = getChainDisplayName(toChainId) + const fromChainType = getChainType(fromChainId) + const toChainType = getChainType(toChainId) + + const handleConfirm = () => { + const event = new CustomEvent('chain-switch-confirm', { + detail: { approved: true, toChainId }, + }) + window.dispatchEvent(event) + pop() + } + + const handleCancel = () => { + const event = new CustomEvent('chain-switch-confirm', { + detail: { approved: false }, + }) + window.dispatchEvent(event) + pop() + } + + return ( + +
+ {/* Handle */} +
+
+
+ + {/* App Info */} +
+ {(appName || appIcon) && ( +
+ +
+ )} +

+ {t('switchNetwork', '切换网络')} +

+

+ {appName || t('unknownDApp', '未知 DApp')} {t('requestsNetworkSwitch', '请求切换网络')} +

+
+ + {/* Chain Switch Visual */} +
+ {/* From Chain */} +
+ {fromChainType ? ( + + ) : ( +
+ +
+ )} + {fromChainName} +
+ + {/* Arrow */} + + + {/* To Chain */} +
+ {toChainType ? ( + + ) : ( +
+ +
+ )} + {toChainName} +
+
+ + {/* Warning */} +
+

+ {t('chainSwitchWarning', '切换网络后,您的交易将在新网络上进行。请确保您了解此操作的影响。')} +

+
+ + {/* Buttons */} +
+ + +
+ + {/* Safe area */} +
+
+ + ) +} + +export const ChainSwitchConfirmJob: ActivityComponentType = ({ params }) => { + return ( + + + + ) +} diff --git a/src/stackflow/activities/sheets/WalletPickerJob.tsx b/src/stackflow/activities/sheets/WalletPickerJob.tsx index 14354c0ea..f08bba4a9 100644 --- a/src/stackflow/activities/sheets/WalletPickerJob.tsx +++ b/src/stackflow/activities/sheets/WalletPickerJob.tsx @@ -8,15 +8,15 @@ import type { ActivityComponentType } from '@stackflow/react' import { BottomSheet } from '@/components/layout/bottom-sheet' import { useTranslation } from 'react-i18next' import { useStore } from '@tanstack/react-store' -import { IconApps } from '@tabler/icons-react' import { walletStore, walletSelectors, type Wallet, type ChainAddress } from '@/stores' import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' import { WalletList, type WalletListItem } from '@/components/wallet/wallet-list' import { MiniappIcon } from '@/components/ecosystem' +import { getKeyAppChainId, normalizeChainId, CHAIN_DISPLAY_NAMES } from '@biochain/bio-sdk' type WalletPickerJobParams = { - /** 限定链类型 */ + /** 限定链类型 (支持: KeyApp 内部 ID, EVM hex chainId, API 名称如 BSC) */ chain?: string /** 排除的地址(不显示在列表中) */ exclude?: string @@ -26,10 +26,41 @@ type WalletPickerJobParams = { appIcon?: string } +/** + * 将任意链标识符转换为 KeyApp 内部 ID + * 支持: + * - EVM hex chainId: '0x38' -> 'binance' + * - API 名称: 'BSC', 'ETH' -> 'binance', 'ethereum' + * - 已有的 KeyApp ID: 'binance' -> 'binance' + */ +function resolveChainId(chain: string | undefined): string | undefined { + if (!chain) return undefined + + // Try EVM hex chainId first (e.g., '0x38') + if (chain.startsWith('0x')) { + const keyAppId = getKeyAppChainId(chain) + if (keyAppId) return keyAppId + } + + // Try API name normalization (e.g., 'BSC' -> 'binance') + const normalized = normalizeChainId(chain) + + // Check if it's a known chain + if (CHAIN_DISPLAY_NAMES[normalized]) { + return normalized + } + + // Return as-is (might be already a KeyApp ID like 'binance') + return normalized +} + function WalletPickerJobContent() { const { t } = useTranslation('common') const { pop } = useFlow() - const { chain, exclude, appName, appIcon } = useActivityParams() + const { chain: rawChain, exclude, appName, appIcon } = useActivityParams() + + // Resolve chain to KeyApp internal ID + const chain = useMemo(() => resolveChainId(rawChain), [rawChain]) const walletState = useStore(walletStore) const currentWallet = walletSelectors.getCurrentWallet(walletState) diff --git a/src/stackflow/activities/sheets/index.ts b/src/stackflow/activities/sheets/index.ts index 158b560c3..b39947719 100644 --- a/src/stackflow/activities/sheets/index.ts +++ b/src/stackflow/activities/sheets/index.ts @@ -22,3 +22,4 @@ export { SigningConfirmJob } from "./SigningConfirmJob"; export { PermissionRequestJob } from "./PermissionRequestJob"; export { MiniappTransferConfirmJob } from "./MiniappTransferConfirmJob"; export { MiniappSignTransactionJob } from "./MiniappSignTransactionJob"; +export { ChainSwitchConfirmJob } from "./ChainSwitchConfirmJob"; diff --git a/src/stackflow/stackflow.ts b/src/stackflow/stackflow.ts index 065ec9f45..8167c4e63 100644 --- a/src/stackflow/stackflow.ts +++ b/src/stackflow/stackflow.ts @@ -58,6 +58,7 @@ import { PermissionRequestJob, MiniappTransferConfirmJob, MiniappSignTransactionJob, + ChainSwitchConfirmJob, } from './activities/sheets'; export const { Stack, useFlow, useStepFlow, activities } = stackflow({ @@ -122,6 +123,7 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ PermissionRequestJob: '/job/permission-request', MiniappTransferConfirmJob: '/job/miniapp-transfer-confirm', MiniappSignTransactionJob: '/job/miniapp-sign-transaction', + ChainSwitchConfirmJob: '/job/chain-switch-confirm', }, fallbackActivity: () => 'MainTabsActivity', useHash: true, @@ -185,6 +187,7 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ PermissionRequestJob, MiniappTransferConfirmJob, MiniappSignTransactionJob, + ChainSwitchConfirmJob, }, // Note: Don't set initialActivity when using historySyncPlugin // The plugin will determine the initial activity based on the URL From df8ba9c20a64ee60fccdd4ebc99dfee8b4a1ed56 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Mon, 5 Jan 2026 11:53:43 +0800 Subject: [PATCH 2/8] feat(forge): use window.ethereum for EVM signing + add tests - Update useForge.ts to use window.ethereum for EVM chains (ETH, BSC) - Update useForge.ts to use window.tronLink for TRON chain - Add ethereum-provider.test.ts with 18 tests - Add tron-provider.test.ts with 16 tests - Add ChainSwitchConfirmJob.stories.tsx for Storybook All 65 SDK tests pass. --- miniapps/forge/src/hooks/useForge.ts | 166 ++++++++++++++--- .../bio-sdk/src/ethereum-provider.test.ts | 169 ++++++++++++++++++ packages/bio-sdk/src/tron-provider.test.ts | 164 +++++++++++++++++ .../ChainSwitchConfirmJob.stories.tsx | 125 +++++++++++++ 4 files changed, 596 insertions(+), 28 deletions(-) create mode 100644 packages/bio-sdk/src/ethereum-provider.test.ts create mode 100644 packages/bio-sdk/src/tron-provider.test.ts create mode 100644 src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx diff --git a/miniapps/forge/src/hooks/useForge.ts b/miniapps/forge/src/hooks/useForge.ts index 86c701b41..5b9a028e5 100644 --- a/miniapps/forge/src/hooks/useForge.ts +++ b/miniapps/forge/src/hooks/useForge.ts @@ -6,6 +6,7 @@ import { useState, useCallback } from 'react' import type { BioAccount, BioSignedTransaction } from '@biochain/bio-sdk' import { rechargeApi } from '@/api' import { encodeRechargeV2ToTrInfoData, createRechargeMessage } from '@/api/helpers' +import { getChainType } from '@/lib/chain' import type { ExternalChainName, FromTrJson, @@ -40,26 +41,113 @@ export interface ForgeParams { internalAccount: BioAccount } +/** EVM signed transaction result */ +interface EvmSignedTransaction { + txHash: string + signedData: string +} + +/** TRON signed transaction result */ +interface TronSignedTransaction { + txID: string + signature: string[] + raw_data: unknown +} + +/** Union type for signed transactions */ +type SignedTransactionResult = BioSignedTransaction | EvmSignedTransaction | TronSignedTransaction + /** * Build FromTrJson from signed transaction */ -function buildFromTrJson(chain: ExternalChainName, signedTx: BioSignedTransaction): FromTrJson { - const signTransData = typeof signedTx.data === 'string' - ? signedTx.data - : JSON.stringify(signedTx.data) - +function buildFromTrJson(chain: ExternalChainName, signedTx: SignedTransactionResult): FromTrJson { switch (chain) { - case 'ETH': - return { eth: { signTransData } } - case 'BSC': - return { bsc: { signTransData } } - case 'TRON': - return { tron: signedTx.data } + case 'ETH': { + const evmTx = signedTx as EvmSignedTransaction + return { eth: { signTransData: evmTx.signedData } } + } + case 'BSC': { + const evmTx = signedTx as EvmSignedTransaction + return { bsc: { signTransData: evmTx.signedData } } + } + case 'TRON': { + const tronTx = signedTx as TronSignedTransaction + return { tron: tronTx } + } default: throw new Error(`Unsupported chain: ${chain}`) } } +/** + * Sign EVM transaction using window.ethereum + */ +async function signEvmTransaction( + from: string, + to: string, + amount: string, + _asset: string +): Promise { + if (!window.ethereum) { + throw new Error('Ethereum provider not available') + } + + // Convert amount to wei (assuming 18 decimals for native token) + // For USDT, we'd need to handle ERC20 transfer differently + const valueInWei = BigInt(Math.floor(parseFloat(amount) * 1e18)).toString(16) + + const txHash = await window.ethereum.request({ + method: 'eth_sendTransaction', + params: [{ + from, + to, + value: `0x${valueInWei}`, + }], + }) + + if (!txHash) { + throw new Error('Transaction failed') + } + + return { + txHash, + signedData: txHash, // For EVM, txHash is returned after broadcast + } +} + +/** + * Sign TRON transaction using window.tronLink + */ +async function signTronTransaction( + from: string, + to: string, + amount: string, + _asset: string +): Promise { + if (!window.tronWeb || !window.tronLink) { + throw new Error('TronLink provider not available') + } + + // Convert amount to SUN (1 TRX = 1,000,000 SUN) + const amountInSun = Math.floor(parseFloat(amount) * 1e6) + + // Create unsigned transaction via tronWeb + // Note: In real implementation, we'd use tronWeb.transactionBuilder + // For now, we'll use the simplified tron_signTransaction method + const unsignedTx = { + to_address: to, + owner_address: from, + amount: amountInSun, + } + + const signedTx = await window.tronLink.request({ + method: 'tron_signTransaction', + params: unsignedTx, + }) + + return signedTx as TronSignedTransaction +} + export function useForge() { const [state, setState] = useState({ step: 'idle', @@ -92,25 +180,47 @@ export function useForge() { // Step 1: Create and sign external chain transaction setState({ step: 'signing_external', orderId: null, error: null }) - const unsignedTx = await window.bio.request({ - method: 'bio_createTransaction', - params: [{ - from: externalAccount.address, - to: depositAddress, + const chainType = getChainType(externalChain) + let signedTx: SignedTransactionResult + + if (chainType === 'evm') { + // Use window.ethereum for EVM chains (ETH, BSC) + signedTx = await signEvmTransaction( + externalAccount.address, + depositAddress, amount, - chain: externalChain.toLowerCase(), - asset: externalAsset, - }], - }) + externalAsset + ) + } else if (chainType === 'tron') { + // Use window.tronLink for TRON + signedTx = await signTronTransaction( + externalAccount.address, + depositAddress, + amount, + externalAsset + ) + } else { + // Use bio_createTransaction + bio_signTransaction for BioChain + const unsignedTx = await window.bio.request({ + method: 'bio_createTransaction', + params: [{ + from: externalAccount.address, + to: depositAddress, + amount, + chain: externalChain.toLowerCase(), + asset: externalAsset, + }], + }) - const signedTx = await window.bio.request({ - method: 'bio_signTransaction', - params: [{ - from: externalAccount.address, - chain: externalChain.toLowerCase(), - unsignedTx, - }], - }) + signedTx = await window.bio.request({ + method: 'bio_signTransaction', + params: [{ + from: externalAccount.address, + chain: externalChain.toLowerCase(), + unsignedTx, + }], + }) + } // Step 2: Sign internal chain message setState({ step: 'signing_internal', orderId: null, error: null }) diff --git a/packages/bio-sdk/src/ethereum-provider.test.ts b/packages/bio-sdk/src/ethereum-provider.test.ts new file mode 100644 index 000000000..386eb6946 --- /dev/null +++ b/packages/bio-sdk/src/ethereum-provider.test.ts @@ -0,0 +1,169 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { EthereumProvider, initEthereumProvider } from './ethereum-provider' + +describe('EthereumProvider', () => { + let provider: EthereumProvider + let originalWindow: typeof window + + beforeEach(() => { + provider = new EthereumProvider('*') + // Save original window.ethereum + originalWindow = { ...window } + }) + + afterEach(() => { + // Restore window + if (window.ethereum) { + delete (window as { ethereum?: unknown }).ethereum + } + }) + + describe('constructor', () => { + it('should have isKeyApp property', () => { + expect(provider.isKeyApp).toBe(true) + }) + + it('should have isMetaMask as false', () => { + expect(provider.isMetaMask).toBe(false) + }) + + it('should start disconnected', () => { + expect(provider.isConnected()).toBe(false) + }) + + it('should have null chainId initially', () => { + expect(provider.chainId).toBeNull() + }) + + it('should have null selectedAddress initially', () => { + expect(provider.selectedAddress).toBeNull() + }) + }) + + describe('request', () => { + it('should return a promise', () => { + const result = provider.request({ method: 'eth_chainId' }) + expect(result).toBeInstanceOf(Promise) + }) + + it('should timeout after 5 minutes', async () => { + vi.useFakeTimers() + + const promise = provider.request({ method: 'eth_chainId' }) + + // Fast forward 5 minutes + vi.advanceTimersByTime(5 * 60 * 1000 + 100) + + await expect(promise).rejects.toThrow('Request timeout') + + vi.useRealTimers() + }) + }) + + describe('event emitter', () => { + it('should support on/off', () => { + const handler = vi.fn() + + provider.on('connect', handler) + provider.off('connect', handler) + + // Should not throw + expect(true).toBe(true) + }) + + it('should support once', () => { + const handler = vi.fn() + provider.once('connect', handler) + + // Should not throw + expect(true).toBe(true) + }) + + it('should support removeListener alias', () => { + const handler = vi.fn() + provider.on('connect', handler) + provider.removeListener('connect', handler) + + // Should not throw + expect(true).toBe(true) + }) + }) + + describe('legacy methods', () => { + it('should have enable method', () => { + expect(typeof provider.enable).toBe('function') + }) + + it('should have send method', () => { + expect(typeof provider.send).toBe('function') + }) + + it('should have sendAsync method', () => { + expect(typeof provider.sendAsync).toBe('function') + }) + }) + + describe('initEthereumProvider', () => { + it('should inject provider into window.ethereum', () => { + const injected = initEthereumProvider('*') + expect(window.ethereum).toBe(injected) + }) + + it('should return existing provider if already initialized', () => { + const first = initEthereumProvider('*') + const second = initEthereumProvider('*') + expect(first).toBe(second) + }) + }) + + describe('message handling', () => { + it('should handle connect event', () => { + const handler = vi.fn() + provider.on('connect', handler) + + // Simulate message from host + const event = new MessageEvent('message', { + data: { + type: 'eth_event', + event: 'connect', + args: [{ chainId: '0x38' }], + }, + }) + window.dispatchEvent(event) + + expect(handler).toHaveBeenCalledWith({ chainId: '0x38' }) + }) + + it('should handle chainChanged event', () => { + const handler = vi.fn() + provider.on('chainChanged', handler) + + const event = new MessageEvent('message', { + data: { + type: 'eth_event', + event: 'chainChanged', + args: ['0x1'], + }, + }) + window.dispatchEvent(event) + + expect(handler).toHaveBeenCalledWith('0x1') + }) + + it('should handle accountsChanged event', () => { + const handler = vi.fn() + provider.on('accountsChanged', handler) + + const event = new MessageEvent('message', { + data: { + type: 'eth_event', + event: 'accountsChanged', + args: [['0x1234...']], + }, + }) + window.dispatchEvent(event) + + expect(handler).toHaveBeenCalledWith(['0x1234...']) + }) + }) +}) diff --git a/packages/bio-sdk/src/tron-provider.test.ts b/packages/bio-sdk/src/tron-provider.test.ts new file mode 100644 index 000000000..65f05dae7 --- /dev/null +++ b/packages/bio-sdk/src/tron-provider.test.ts @@ -0,0 +1,164 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider' + +describe('TronLinkProvider', () => { + let provider: TronLinkProvider + + beforeEach(() => { + provider = new TronLinkProvider('*') + }) + + describe('request', () => { + it('should return a promise', () => { + const result = provider.request({ method: 'tron_requestAccounts' }) + expect(result).toBeInstanceOf(Promise) + }) + + it('should timeout after 5 minutes', async () => { + vi.useFakeTimers() + + const promise = provider.request({ method: 'tron_requestAccounts' }) + + // Fast forward 5 minutes + vi.advanceTimersByTime(5 * 60 * 1000 + 100) + + await expect(promise).rejects.toThrow('Request timeout') + + vi.useRealTimers() + }) + }) + + describe('event emitter', () => { + it('should support on/off', () => { + const handler = vi.fn() + + provider.on('accountsChanged', handler) + provider.off('accountsChanged', handler) + + expect(true).toBe(true) + }) + }) + + describe('message handling', () => { + it('should handle tron_response', () => { + const requestPromise = provider.request({ method: 'tron_accounts' }) + + // Get the request ID from pending requests (internal) + // We'll simulate a response + const event = new MessageEvent('message', { + data: { + type: 'tron_response', + id: 'tron_1_1', // This won't match, but tests the handler path + success: true, + result: { base58: 'T...', hex: '41...' }, + }, + }) + window.dispatchEvent(event) + + // Request will still timeout since ID doesn't match + // This just tests that the handler doesn't throw + expect(true).toBe(true) + }) + + it('should handle tron_event', () => { + const handler = vi.fn() + provider.on('accountsChanged', handler) + + const event = new MessageEvent('message', { + data: { + type: 'tron_event', + event: 'accountsChanged', + args: [{ base58: 'T...', hex: '41...' }], + }, + }) + window.dispatchEvent(event) + + expect(handler).toHaveBeenCalled() + }) + }) +}) + +describe('TronWebProvider', () => { + let tronLink: TronLinkProvider + let tronWeb: TronWebProvider + + beforeEach(() => { + tronLink = new TronLinkProvider('*') + tronWeb = new TronWebProvider(tronLink) + }) + + describe('initial state', () => { + it('should not be ready initially', () => { + expect(tronWeb.ready).toBe(false) + }) + + it('should have empty default address', () => { + expect(tronWeb.defaultAddress).toEqual({ base58: '', hex: '' }) + }) + }) + + describe('setAddress', () => { + it('should set address and mark as ready', () => { + tronWeb.setAddress({ base58: 'TAddr...', hex: '41...' }) + + expect(tronWeb.ready).toBe(true) + expect(tronWeb.defaultAddress.base58).toBe('TAddr...') + }) + }) + + describe('isAddress', () => { + it('should validate base58 address', () => { + expect(tronWeb.isAddress('T' + 'x'.repeat(33))).toBe(true) + expect(tronWeb.isAddress('invalid')).toBe(false) + }) + + it('should validate hex address', () => { + expect(tronWeb.isAddress('41' + 'x'.repeat(40))).toBe(true) + expect(tronWeb.isAddress('00' + 'x'.repeat(40))).toBe(false) + }) + }) + + describe('trx methods', () => { + it('should have sign method', () => { + expect(typeof tronWeb.trx.sign).toBe('function') + }) + + it('should have sendRawTransaction method', () => { + expect(typeof tronWeb.trx.sendRawTransaction).toBe('function') + }) + + it('should have getBalance method', () => { + expect(typeof tronWeb.trx.getBalance).toBe('function') + }) + + it('should have getAccount method', () => { + expect(typeof tronWeb.trx.getAccount).toBe('function') + }) + }) +}) + +describe('initTronProvider', () => { + afterEach(() => { + if (window.tronLink) { + delete (window as { tronLink?: unknown }).tronLink + } + if (window.tronWeb) { + delete (window as { tronWeb?: unknown }).tronWeb + } + }) + + it('should inject providers into window', () => { + const { tronLink, tronWeb } = initTronProvider('*') + + expect(window.tronLink).toBe(tronLink) + expect(window.tronWeb).toBe(tronWeb) + }) + + it('should return existing providers if already initialized', () => { + const first = initTronProvider('*') + const second = initTronProvider('*') + + expect(first.tronLink).toBe(second.tronLink) + expect(first.tronWeb).toBe(second.tronWeb) + }) +}) diff --git a/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx b/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx new file mode 100644 index 000000000..f96e81de2 --- /dev/null +++ b/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx @@ -0,0 +1,125 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useState, useEffect } from 'react' +import { ChainSwitchConfirmJob } from '../ChainSwitchConfirmJob' +import { ActivityParamsProvider } from '../../../hooks' +import { BottomSheet } from '@/components/layout/bottom-sheet' + +const meta: Meta = { + title: 'Sheets/ChainSwitchConfirmJob', + component: ChainSwitchConfirmJob, + parameters: { + layout: 'fullscreen', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} + +export default meta +type Story = StoryObj + +/** Demo wrapper to handle events */ +function ChainSwitchDemo({ + fromChainId, + toChainId, + appName, + appIcon, +}: { + fromChainId: string + toChainId: string + appName?: string + appIcon?: string +}) { + const [result, setResult] = useState<{ approved: boolean; toChainId?: string } | null>(null) + const [isOpen, setIsOpen] = useState(true) + + useEffect(() => { + const handleConfirm = (e: CustomEvent) => { + setResult(e.detail) + setIsOpen(false) + } + window.addEventListener('chain-switch-confirm', handleConfirm as EventListener) + return () => window.removeEventListener('chain-switch-confirm', handleConfirm as EventListener) + }, []) + + if (!isOpen) { + return ( +
+
+ {result?.approved ? '✅ 已确认切换' : '❌ 已取消'} +
+ {result?.approved && ( +
+ 切换到链: {result.toChainId} +
+ )} + +
+ ) + } + + return ( + + + + + + ) +} + +/** BSC 切换到 Ethereum */ +export const BSCToEthereum: Story = { + render: () => ( + + ), +} + +/** Ethereum 切换到 BSC */ +export const EthereumToBSC: Story = { + render: () => ( + + ), +} + +/** 无应用信息 */ +export const NoAppInfo: Story = { + render: () => ( + + ), +} + +/** 未知链 ID */ +export const UnknownChain: Story = { + render: () => ( + + ), +} From 7acdc607c15a15145d165d6b5cacf920b3d102da Mon Sep 17 00:00:00 2001 From: Gaubee Date: Mon, 5 Jan 2026 12:25:06 +0800 Subject: [PATCH 3/8] feat(ecosystem): finish multi-chain provider integration --- ...00\345\217\221\346\214\207\345\215\227.md" | 50 +- miniapps/forge/src/App.stories.tsx | 44 +- miniapps/forge/src/App.test.tsx | 26 +- miniapps/forge/src/App.tsx | 9 +- miniapps/forge/src/hooks/useForge.ts | 167 ++----- miniapps/forge/src/vite-env.d.ts | 19 +- packages/bio-sdk/dist/index.cjs | 450 ++++++++++++++++- packages/bio-sdk/dist/index.cjs.map | 2 +- packages/bio-sdk/dist/index.d.ts | 251 ++++++++++ packages/bio-sdk/dist/index.js | 452 +++++++++++++++++- packages/bio-sdk/dist/index.js.map | 2 +- packages/bio-sdk/dist/index.umd.js | 450 ++++++++++++++++- packages/bio-sdk/dist/index.umd.js.map | 2 +- src/services/ecosystem/handlers/context.ts | 4 +- src/services/ecosystem/handlers/evm.ts | 6 +- src/services/ecosystem/handlers/tron.ts | 6 +- src/stackflow/activities/MainTabsActivity.tsx | 158 ++++++ .../ChainSwitchConfirmJob.stories.tsx | 104 ++-- 18 files changed, 1971 insertions(+), 231 deletions(-) diff --git "a/docs/white-book/10-\347\224\237\346\200\201\347\257\207/02-BioSDK\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/white-book/10-\347\224\237\346\200\201\347\257\207/02-BioSDK\345\274\200\345\217\221\346\214\207\345\215\227.md" index 9f9e58772..9b4e57bfa 100644 --- "a/docs/white-book/10-\347\224\237\346\200\201\347\257\207/02-BioSDK\345\274\200\345\217\221\346\214\207\345\215\227.md" +++ "b/docs/white-book/10-\347\224\237\346\200\201\347\257\207/02-BioSDK\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -13,6 +13,11 @@ pnpm add @biochain/bio-sdk ```typescript import '@biochain/bio-sdk' +// KeyApp Miniapp 环境会注入: +// - window.bio (KeyApp/BioChain 专用 API) +// - window.ethereum (EIP-1193 兼容,面向 EVM: Ethereum/BSC) +// - window.tronWeb / window.tronLink (TronLink 兼容,面向 TRON) + // window.bio 现在可用 async function connect() { const accounts = await window.bio.request({ @@ -22,6 +27,16 @@ async function connect() { } ``` +## 链标识(非常重要) + +KeyApp 内部链 ID 与常见外部标识存在差异: + +- `ETH` / `eth` / `0x1` → `ethereum` +- `BSC` / `bsc` / `0x38` → `binance` +- `TRON` / `tron` → `tron` + +建议在调用 `window.bio` 的 `chain` 参数时使用 KeyApp 内部 ID(`ethereum` / `binance` / `tron`),避免出现“暂无支持 bsc 的钱包”这类匹配失败。 + ## API 参考 ### request(args) @@ -161,7 +176,7 @@ const result = await window.bio.request<{ txHash: string }>({ ```typescript type BioUnsignedTransaction = { - chain: string + chainId: string data: unknown } @@ -182,9 +197,9 @@ const unsignedTx = await window.bio.request({ ```typescript type BioSignedTransaction = { - chain: string - raw: string // 链特定的 raw tx(例如 EVM 的 RLP hex) - signature?: string + chainId: string + data: unknown // 链特定的签名产物(例如 EVM 的 raw tx hex) + signature: string } const signedTx = await window.bio.request({ @@ -197,6 +212,33 @@ const signedTx = await window.bio.request({ }) ``` +## EVM Provider(window.ethereum) + +面向传统 dApp(EIP-1193 规范)的注入对象:`window.ethereum`。 + +常用示例: + +```typescript +const chainId = await window.ethereum.request({ method: 'eth_chainId' }) +const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) +``` + +当前支持(按需逐步完善): + +- `eth_chainId` / `eth_accounts` / `eth_requestAccounts` +- `wallet_switchEthereumChain` +- `personal_sign` / `eth_sign` / `eth_signTypedData_v4` + +## TRON Provider(window.tronLink / window.tronWeb) + +面向 TronLink 生态的注入对象:`window.tronLink` 与 `window.tronWeb`(兼容常见调用路径)。 + +常用示例: + +```typescript +const result = await window.tronLink.request({ method: 'tron_requestAccounts' }) +``` + ## 事件 ### accountsChanged diff --git a/miniapps/forge/src/App.stories.tsx b/miniapps/forge/src/App.stories.tsx index 384c3c9a0..d51da13a4 100644 --- a/miniapps/forge/src/App.stories.tsx +++ b/miniapps/forge/src/App.stories.tsx @@ -47,6 +47,23 @@ const setupMockApi = () => { }) } +type EthereumRequest = (args: { method: string; params?: unknown[] }) => Promise + +function setupMockEthereumProvider(opts?: { + accounts?: string[] +}): void { + const accounts = opts?.accounts ?? ['0x1111111111111111111111111111111111111111'] + + const request: EthereumRequest = fn().mockImplementation(({ method }) => { + if (method === 'wallet_switchEthereumChain') return Promise.resolve(null) + if (method === 'eth_requestAccounts') return Promise.resolve(accounts) + if (method === 'eth_chainId') return Promise.resolve('0x1') + return Promise.resolve(null) + }) + + window.ethereum = { request } as unknown as typeof window.ethereum +} + const meta = { title: 'App/ForgeApp', component: App, @@ -59,6 +76,7 @@ const meta = { decorators: [ (Story) => { setupMockApi() + setupMockEthereumProvider() return (
@@ -103,14 +121,17 @@ export const SwapStep: Story = { decorators: [ (Story) => { setupMockApi() + setupMockEthereumProvider() // Mock bio SDK with connected wallet // @ts-expect-error - mock global window.bio = { - request: fn().mockImplementation(({ method }: { method: string }) => { + request: fn().mockImplementation(({ method, params }: { method: string; params?: unknown[] }) => { if (method === 'bio_selectAccount') { + const chain = (params?.[0] as { chain?: string } | undefined)?.chain ?? 'bfmeta' return Promise.resolve({ - address: '0x1234567890abcdef1234567890abcdef12345678', - chain: 'eth', + address: chain === 'bfmeta' ? 'bfmeta123' : '0x1234567890abcdef1234567890abcdef12345678', + chain, + publicKey: '0x', }) } if (method === 'bio_closeSplashScreen') { @@ -222,13 +243,16 @@ export const TokenPicker: Story = { decorators: [ (Story) => { setupMockApi() + setupMockEthereumProvider() // @ts-expect-error - mock global window.bio = { - request: fn().mockImplementation(({ method }: { method: string }) => { + request: fn().mockImplementation(({ method, params }: { method: string; params?: unknown[] }) => { if (method === 'bio_selectAccount') { + const chain = (params?.[0] as { chain?: string } | undefined)?.chain ?? 'bfmeta' return Promise.resolve({ - address: '0x1234567890abcdef1234567890abcdef12345678', - chain: 'eth', + address: chain === 'bfmeta' ? 'bfmeta123' : '0x1234567890abcdef1234567890abcdef12345678', + chain, + publicKey: '0x', }) } if (method === 'bio_closeSplashScreen') { @@ -286,17 +310,19 @@ export const LoadingState: Story = { decorators: [ (Story) => { setupMockApi() + setupMockEthereumProvider() // Mock slow bio SDK // @ts-expect-error - mock global window.bio = { - request: fn().mockImplementation(({ method }: { method: string }) => { + request: fn().mockImplementation(({ method, params }: { method: string; params?: unknown[] }) => { if (method === 'bio_selectAccount') { // Simulate slow connection return new Promise((resolve) => { setTimeout(() => { resolve({ - address: '0x123', - chain: 'eth', + address: ((params?.[0] as { chain?: string } | undefined)?.chain ?? 'bfmeta') === 'bfmeta' ? 'bfmeta123' : '0x123', + chain: (params?.[0] as { chain?: string } | undefined)?.chain ?? 'bfmeta', + publicKey: '0x', }) }, 10000) }) diff --git a/miniapps/forge/src/App.test.tsx b/miniapps/forge/src/App.test.tsx index 9a49be4a9..ce758ff4e 100644 --- a/miniapps/forge/src/App.test.tsx +++ b/miniapps/forge/src/App.test.tsx @@ -39,10 +39,21 @@ const mockBio = { isConnected: vi.fn(() => true), } +const mockEthereum = { + request: vi.fn(), +} + describe('Forge App', () => { beforeEach(() => { vi.clearAllMocks() ;(window as unknown as { bio: typeof mockBio }).bio = mockBio + + // Default EVM provider mock (ETH in test config) + ;(window as unknown as { ethereum: typeof mockEthereum }).ethereum = mockEthereum + mockEthereum.request.mockImplementation(({ method }: { method: string }) => { + if (method === 'eth_requestAccounts') return Promise.resolve(['0xexternal123']) + return Promise.resolve(null) + }) }) it('should render initial connect step after config loads', async () => { @@ -63,13 +74,18 @@ describe('Forge App', () => { render() await waitFor(() => { - expect(screen.getByRole('button', { name: '连接钱包' })).toBeInTheDocument() + expect(screen.getByTestId('connect-button')).toBeInTheDocument() }) - fireEvent.click(screen.getByRole('button', { name: '连接钱包' })) + // Ensure option auto-selected and button enabled + await waitFor(() => { + expect(screen.getByTestId('connect-button')).not.toBeDisabled() + }) + + fireEvent.click(screen.getByTestId('connect-button')) await waitFor(() => { - expect(screen.getByRole('button', { name: '连接中...' })).toBeInTheDocument() + expect(screen.getByTestId('connect-button')).toHaveTextContent('连接中...') }) }) @@ -122,7 +138,7 @@ describe('Forge App', () => { }) it('should call bio_selectAccount on connect', async () => { - mockBio.request.mockResolvedValue({ address: '0x123', chain: 'eth' }) + mockBio.request.mockResolvedValue({ address: 'bfmeta123', chain: 'bfmeta' }) render() @@ -133,7 +149,7 @@ describe('Forge App', () => { fireEvent.click(screen.getByRole('button', { name: '连接钱包' })) await waitFor(() => { - // Should call bio_selectAccount at least once (for external and internal accounts) + // Should call bio_selectAccount at least once (internal account) expect(mockBio.request).toHaveBeenCalledWith( expect.objectContaining({ method: 'bio_selectAccount', diff --git a/miniapps/forge/src/App.tsx b/miniapps/forge/src/App.tsx index 618cf8951..82bae12cc 100644 --- a/miniapps/forge/src/App.tsx +++ b/miniapps/forge/src/App.tsx @@ -1,6 +1,7 @@ import { useState, useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import type { BioAccount } from '@biochain/bio-sdk' +import { normalizeChainId } from '@biochain/bio-sdk' import { getChainType, getEvmChainIdFromApi } from '@/lib/chain' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' @@ -112,7 +113,7 @@ export default function App() { } extAcc = { address: accounts[0], - chain: externalChain.toLowerCase(), + chain: normalizeChainId(externalChain), publicKey: '', // EVM doesn't expose public key directly } } else if (chainType === 'tron') { @@ -120,7 +121,9 @@ export default function App() { if (!window.tronLink) { throw new Error('TronLink provider not available') } - const result = await window.tronLink.request({ method: 'tron_requestAccounts' }) + const result = await window.tronLink.request<{ code: number; message: string; data: { base58: string } }>({ + method: 'tron_requestAccounts', + }) if (!result || result.code !== 200) { throw new Error('TRON connection failed') } @@ -133,7 +136,7 @@ export default function App() { // Use bio_selectAccount for BioChain extAcc = await window.bio.request({ method: 'bio_selectAccount', - params: [{ chain: externalChain.toLowerCase() }], + params: [{ chain: normalizeChainId(externalChain) }], }) } setExternalAccount(extAcc) diff --git a/miniapps/forge/src/hooks/useForge.ts b/miniapps/forge/src/hooks/useForge.ts index 5b9a028e5..e6e06a988 100644 --- a/miniapps/forge/src/hooks/useForge.ts +++ b/miniapps/forge/src/hooks/useForge.ts @@ -4,9 +4,9 @@ import { useState, useCallback } from 'react' import type { BioAccount, BioSignedTransaction } from '@biochain/bio-sdk' +import { normalizeChainId } from '@biochain/bio-sdk' import { rechargeApi } from '@/api' import { encodeRechargeV2ToTrInfoData, createRechargeMessage } from '@/api/helpers' -import { getChainType } from '@/lib/chain' import type { ExternalChainName, FromTrJson, @@ -41,113 +41,26 @@ export interface ForgeParams { internalAccount: BioAccount } -/** EVM signed transaction result */ -interface EvmSignedTransaction { - txHash: string - signedData: string -} - -/** TRON signed transaction result */ -interface TronSignedTransaction { - txID: string - signature: string[] - raw_data: unknown -} - -/** Union type for signed transactions */ -type SignedTransactionResult = BioSignedTransaction | EvmSignedTransaction | TronSignedTransaction - /** * Build FromTrJson from signed transaction */ -function buildFromTrJson(chain: ExternalChainName, signedTx: SignedTransactionResult): FromTrJson { +function buildFromTrJson(chain: ExternalChainName, signedTx: BioSignedTransaction): FromTrJson { + const signTransData = typeof signedTx.data === 'string' + ? signedTx.data + : JSON.stringify(signedTx.data) + switch (chain) { - case 'ETH': { - const evmTx = signedTx as EvmSignedTransaction - return { eth: { signTransData: evmTx.signedData } } - } - case 'BSC': { - const evmTx = signedTx as EvmSignedTransaction - return { bsc: { signTransData: evmTx.signedData } } - } - case 'TRON': { - const tronTx = signedTx as TronSignedTransaction - return { tron: tronTx } - } + case 'ETH': + return { eth: { signTransData } } + case 'BSC': + return { bsc: { signTransData } } + case 'TRON': + return { tron: signedTx.data } default: throw new Error(`Unsupported chain: ${chain}`) } } -/** - * Sign EVM transaction using window.ethereum - */ -async function signEvmTransaction( - from: string, - to: string, - amount: string, - _asset: string -): Promise { - if (!window.ethereum) { - throw new Error('Ethereum provider not available') - } - - // Convert amount to wei (assuming 18 decimals for native token) - // For USDT, we'd need to handle ERC20 transfer differently - const valueInWei = BigInt(Math.floor(parseFloat(amount) * 1e18)).toString(16) - - const txHash = await window.ethereum.request({ - method: 'eth_sendTransaction', - params: [{ - from, - to, - value: `0x${valueInWei}`, - }], - }) - - if (!txHash) { - throw new Error('Transaction failed') - } - - return { - txHash, - signedData: txHash, // For EVM, txHash is returned after broadcast - } -} - -/** - * Sign TRON transaction using window.tronLink - */ -async function signTronTransaction( - from: string, - to: string, - amount: string, - _asset: string -): Promise { - if (!window.tronWeb || !window.tronLink) { - throw new Error('TronLink provider not available') - } - - // Convert amount to SUN (1 TRX = 1,000,000 SUN) - const amountInSun = Math.floor(parseFloat(amount) * 1e6) - - // Create unsigned transaction via tronWeb - // Note: In real implementation, we'd use tronWeb.transactionBuilder - // For now, we'll use the simplified tron_signTransaction method - const unsignedTx = { - to_address: to, - owner_address: from, - amount: amountInSun, - } - - const signedTx = await window.tronLink.request({ - method: 'tron_signTransaction', - params: unsignedTx, - }) - - return signedTx as TronSignedTransaction -} - export function useForge() { const [state, setState] = useState({ step: 'idle', @@ -180,47 +93,27 @@ export function useForge() { // Step 1: Create and sign external chain transaction setState({ step: 'signing_external', orderId: null, error: null }) - const chainType = getChainType(externalChain) - let signedTx: SignedTransactionResult + const externalKeyAppChainId = normalizeChainId(externalChain) - if (chainType === 'evm') { - // Use window.ethereum for EVM chains (ETH, BSC) - signedTx = await signEvmTransaction( - externalAccount.address, - depositAddress, - amount, - externalAsset - ) - } else if (chainType === 'tron') { - // Use window.tronLink for TRON - signedTx = await signTronTransaction( - externalAccount.address, - depositAddress, + const unsignedTx = await window.bio.request({ + method: 'bio_createTransaction', + params: [{ + from: externalAccount.address, + to: depositAddress, amount, - externalAsset - ) - } else { - // Use bio_createTransaction + bio_signTransaction for BioChain - const unsignedTx = await window.bio.request({ - method: 'bio_createTransaction', - params: [{ - from: externalAccount.address, - to: depositAddress, - amount, - chain: externalChain.toLowerCase(), - asset: externalAsset, - }], - }) + chain: externalKeyAppChainId, + asset: externalAsset, + }], + }) - signedTx = await window.bio.request({ - method: 'bio_signTransaction', - params: [{ - from: externalAccount.address, - chain: externalChain.toLowerCase(), - unsignedTx, - }], - }) - } + const signedTx = await window.bio.request({ + method: 'bio_signTransaction', + params: [{ + from: externalAccount.address, + chain: externalKeyAppChainId, + unsignedTx, + }], + }) // Step 2: Sign internal chain message setState({ step: 'signing_internal', orderId: null, error: null }) diff --git a/miniapps/forge/src/vite-env.d.ts b/miniapps/forge/src/vite-env.d.ts index 640aba57e..b9a38e161 100644 --- a/miniapps/forge/src/vite-env.d.ts +++ b/miniapps/forge/src/vite-env.d.ts @@ -1,5 +1,12 @@ /// +import type { + BioProvider, + EthereumProvider, + TronLinkProvider, + TronWebProvider, +} from '@biochain/bio-sdk' + interface ImportMetaEnv { readonly VITE_COT_API_BASE_URL?: string } @@ -8,5 +15,13 @@ interface ImportMeta { readonly env: ImportMetaEnv } -// Bio SDK types are auto-declared by importing @biochain/bio-sdk -// This ensures window.bio, window.ethereum, window.tronLink are available +declare global { + interface Window { + bio?: BioProvider + ethereum?: EthereumProvider + tronLink?: TronLinkProvider + tronWeb?: TronWebProvider + } +} + +export {} diff --git a/packages/bio-sdk/dist/index.cjs b/packages/bio-sdk/dist/index.cjs index c26a8a31a..2cd333fb9 100644 --- a/packages/bio-sdk/dist/index.cjs +++ b/packages/bio-sdk/dist/index.cjs @@ -146,6 +146,425 @@ class BioProviderImpl { return this.connected; } } +class EthereumProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + connected = false; + currentChainId = null; + accounts = []; + targetOrigin; + // EIP-1193 required properties + isMetaMask = false; + isKeyApp = true; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "eth_response") { + this.handleResponse(data); + } else if (data.type === "eth_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + if (message.event === "connect") { + this.connected = true; + const info = message.args[0]; + this.currentChainId = info?.chainId ?? null; + } else if (message.event === "disconnect") { + this.connected = false; + this.accounts = []; + } else if (message.event === "chainChanged") { + this.currentChainId = message.args[0]; + } else if (message.event === "accountsChanged") { + this.accounts = message.args[0]; + } + } + generateId() { + return `eth_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[EthereumProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * EIP-1193 request method + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "eth_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + /** + * Subscribe to an event + */ + on(event, handler) { + this.events.on(event, handler); + return this; + } + /** + * Unsubscribe from an event + */ + off(event, handler) { + this.events.off(event, handler); + return this; + } + /** + * Alias for off (Node.js EventEmitter compatibility) + */ + removeListener(event, handler) { + return this.off(event, handler); + } + /** + * Add listener that fires only once + */ + once(event, handler) { + const wrapper = (...args) => { + this.off(event, wrapper); + handler(...args); + }; + this.on(event, wrapper); + return this; + } + /** + * EIP-1193 isConnected method + */ + isConnected() { + return this.connected; + } + /** + * Get current chain ID (cached) + */ + get chainId() { + return this.currentChainId; + } + /** + * Get selected address (first account) + */ + get selectedAddress() { + return this.accounts[0] ?? null; + } + // ============================================ + // Legacy methods (for backwards compatibility) + // ============================================ + /** + * @deprecated Use request({ method: 'eth_requestAccounts' }) + */ + async enable() { + return this.request({ method: "eth_requestAccounts" }); + } + /** + * @deprecated Use request() + */ + send(method, params) { + return this.request({ method, params }); + } + /** + * @deprecated Use request() + */ + sendAsync(payload, callback) { + this.request({ method: payload.method, params: payload.params }).then((result) => callback(null, { result })).catch((error) => callback(error)); + } +} +function initEthereumProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[EthereumProvider] Cannot initialize: window is not defined"); + } + if (window.ethereum) { + console.warn("[EthereumProvider] Provider already exists, returning existing instance"); + return window.ethereum; + } + const provider = new EthereumProvider(targetOrigin); + window.ethereum = provider; + console.log("[EthereumProvider] Provider initialized"); + return provider; +} +class TronLinkProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + targetOrigin; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "tron_response") { + this.handleResponse(data); + } else if (data.type === "tron_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + } + generateId() { + return `tron_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[TronLinkProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * TronLink request method (EIP-1193 style) + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params !== void 0 ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "tron_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + on(event, handler) { + this.events.on(event, handler); + return this; + } + off(event, handler) { + this.events.off(event, handler); + return this; + } +} +class TronWebProvider { + tronLink; + _ready = false; + _defaultAddress = { base58: "", hex: "" }; + /** TRX operations */ + trx; + constructor(tronLink) { + this.tronLink = tronLink; + this.trx = new TronWebTrx(tronLink); + tronLink.on("accountsChanged", (accounts) => { + if (Array.isArray(accounts) && accounts.length > 0) { + const addr = accounts[0]; + this._defaultAddress = addr; + this._ready = true; + } else { + this._defaultAddress = { base58: "", hex: "" }; + this._ready = false; + } + }); + } + /** Whether TronWeb is ready (connected) */ + get ready() { + return this._ready; + } + /** Current default address */ + get defaultAddress() { + return this._defaultAddress; + } + /** + * Set default address (called by host after connection) + */ + setAddress(address) { + this._defaultAddress = address; + this._ready = true; + } + /** + * Check if an address is valid + */ + isAddress(address) { + if (address.startsWith("T")) { + return address.length === 34; + } + if (address.startsWith("41")) { + return address.length === 42; + } + return false; + } + /** + * Convert address to hex format + */ + address = { + toHex: (base58) => { + return base58; + }, + fromHex: (hex) => { + return hex; + } + }; +} +class TronWebTrx { + tronLink; + constructor(tronLink) { + this.tronLink = tronLink; + } + /** + * Sign a transaction + */ + async sign(transaction) { + return this.tronLink.request({ + method: "tron_signTransaction", + params: transaction + }); + } + /** + * Send raw transaction (broadcast) + */ + async sendRawTransaction(signedTransaction) { + return this.tronLink.request({ + method: "tron_sendRawTransaction", + params: signedTransaction + }); + } + /** + * Get account balance + */ + async getBalance(address) { + return this.tronLink.request({ + method: "tron_getBalance", + params: address + }); + } + /** + * Get account info + */ + async getAccount(address) { + return this.tronLink.request({ + method: "tron_getAccount", + params: address + }); + } +} +function initTronProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[TronProvider] Cannot initialize: window is not defined"); + } + if (window.tronLink && window.tronWeb) { + console.warn("[TronProvider] Providers already exist, returning existing instances"); + return { tronLink: window.tronLink, tronWeb: window.tronWeb }; + } + const tronLink = new TronLinkProvider(targetOrigin); + const tronWeb = new TronWebProvider(tronLink); + window.tronLink = tronLink; + window.tronWeb = tronWeb; + console.log("[TronProvider] Providers initialized"); + return { tronLink, tronWeb }; +} +const EVM_CHAIN_IDS = { + ethereum: 1, + binance: 56 + // Future chains + // polygon: 137, + // arbitrum: 42161, + // optimism: 10, +}; +const EVM_CHAIN_ID_TO_KEYAPP = Object.fromEntries( + Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key]) +); +const API_CHAIN_TO_KEYAPP = { + ETH: "ethereum", + BSC: "binance", + TRON: "tron", + // Lowercase variants + eth: "ethereum", + bsc: "binance", + tron: "tron" +}; +const CHAIN_DISPLAY_NAMES = { + ethereum: "Ethereum", + binance: "BNB Smart Chain", + tron: "Tron", + bfmeta: "BFMeta", + bfchain: "BFChain" +}; +function toHexChainId(chainId) { + return `0x${chainId.toString(16)}`; +} +function parseHexChainId(hexChainId) { + if (!hexChainId.startsWith("0x")) { + throw new Error(`Invalid hex chain ID: ${hexChainId}`); + } + return parseInt(hexChainId, 16); +} +function getKeyAppChainId(hexChainId) { + const decimal = parseHexChainId(hexChainId); + return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null; +} +function getEvmChainId(keyAppChainId) { + const decimal = EVM_CHAIN_IDS[keyAppChainId]; + return decimal ? toHexChainId(decimal) : null; +} +function isEvmChain(chainId) { + return chainId in EVM_CHAIN_IDS; +} +function normalizeChainId(chainName) { + return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase(); +} function initBioProvider(targetOrigin = "*") { if (typeof window === "undefined") { throw new Error("[BioSDK] Cannot initialize: window is not defined"); @@ -159,16 +578,43 @@ function initBioProvider(targetOrigin = "*") { console.log("[BioSDK] Provider initialized"); return provider; } +function initAllProviders(targetOrigin = "*") { + const bio = initBioProvider(targetOrigin); + const ethereum = initEthereumProvider(targetOrigin); + const { tronLink, tronWeb } = initTronProvider(targetOrigin); + return { bio, ethereum, tronLink, tronWeb }; +} if (typeof window !== "undefined") { + const init = () => { + initBioProvider(); + initEthereumProvider(); + initTronProvider(); + }; if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => initBioProvider()); + document.addEventListener("DOMContentLoaded", init); } else { - initBioProvider(); + init(); } } +exports.API_CHAIN_TO_KEYAPP = API_CHAIN_TO_KEYAPP; exports.BioErrorCodes = BioErrorCodes; exports.BioProviderImpl = BioProviderImpl; +exports.CHAIN_DISPLAY_NAMES = CHAIN_DISPLAY_NAMES; +exports.EVM_CHAIN_IDS = EVM_CHAIN_IDS; +exports.EVM_CHAIN_ID_TO_KEYAPP = EVM_CHAIN_ID_TO_KEYAPP; +exports.EthereumProvider = EthereumProvider; exports.EventEmitter = EventEmitter; +exports.TronLinkProvider = TronLinkProvider; +exports.TronWebProvider = TronWebProvider; exports.createProviderError = createProviderError; +exports.getEvmChainId = getEvmChainId; +exports.getKeyAppChainId = getKeyAppChainId; +exports.initAllProviders = initAllProviders; exports.initBioProvider = initBioProvider; +exports.initEthereumProvider = initEthereumProvider; +exports.initTronProvider = initTronProvider; +exports.isEvmChain = isEvmChain; +exports.normalizeChainId = normalizeChainId; +exports.parseHexChainId = parseHexChainId; +exports.toHexChainId = toHexChainId; //# sourceMappingURL=index.cjs.map diff --git a/packages/bio-sdk/dist/index.cjs.map b/packages/bio-sdk/dist/index.cjs.map index e6b4df822..c79280637 100644 --- a/packages/bio-sdk/dist/index.cjs.map +++ b/packages/bio-sdk/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"file":"index.cjs","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects `window.bio` provider similar to `window.ethereum` in Web3 DApps.\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // Now window.bio is available\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\n\n// Extend Window interface\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => initBioProvider())\n } else {\n initBioProvider()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.cjs","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/ethereum-provider.ts","../src/tron-provider.ts","../src/chain-id.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Ethereum Provider (EIP-1193 Compatible)\n *\n * Provides window.ethereum for EVM-compatible dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError, type ProviderRpcError } from './types'\nimport { toHexChainId, parseHexChainId, getKeyAppChainId, EVM_CHAIN_IDS } from './chain-id'\n\n/** EIP-1193 Request Arguments */\nexport interface EthRequestArguments {\n method: string\n params?: unknown[] | Record\n}\n\n/** EIP-1193 Provider Connect Info */\nexport interface ProviderConnectInfo {\n chainId: string\n}\n\n/** EIP-1193 Provider Message */\nexport interface ProviderMessage {\n type: string\n data: unknown\n}\n\n/** Transaction request (eth_sendTransaction) */\nexport interface TransactionRequest {\n from: string\n to?: string\n value?: string\n data?: string\n gas?: string\n gasPrice?: string\n maxFeePerGas?: string\n maxPriorityFeePerGas?: string\n nonce?: string\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'eth_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'eth_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'eth_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * EIP-1193 Ethereum Provider Implementation\n */\nexport class EthereumProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private currentChainId: string | null = null\n private accounts: string[] = []\n private readonly targetOrigin: string\n\n // EIP-1193 required properties\n readonly isMetaMask = false\n readonly isKeyApp = true\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'eth_response') {\n this.handleResponse(data)\n } else if (data.type === 'eth_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n const info = message.args[0] as ProviderConnectInfo\n this.currentChainId = info?.chainId ?? null\n } else if (message.event === 'disconnect') {\n this.connected = false\n this.accounts = []\n } else if (message.event === 'chainChanged') {\n this.currentChainId = message.args[0] as string\n } else if (message.event === 'accountsChanged') {\n this.accounts = message.args[0] as string[]\n }\n }\n\n private generateId(): string {\n return `eth_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[EthereumProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * EIP-1193 request method\n */\n async request(args: EthRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'eth_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n /**\n * Subscribe to an event\n */\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n /**\n * Unsubscribe from an event\n */\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n\n /**\n * Alias for off (Node.js EventEmitter compatibility)\n */\n removeListener(event: string, handler: (...args: unknown[]) => void): this {\n return this.off(event, handler)\n }\n\n /**\n * Add listener that fires only once\n */\n once(event: string, handler: (...args: unknown[]) => void): this {\n const wrapper = (...args: unknown[]) => {\n this.off(event, wrapper)\n handler(...args)\n }\n this.on(event, wrapper)\n return this\n }\n\n /**\n * EIP-1193 isConnected method\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * Get current chain ID (cached)\n */\n get chainId(): string | null {\n return this.currentChainId\n }\n\n /**\n * Get selected address (first account)\n */\n get selectedAddress(): string | null {\n return this.accounts[0] ?? null\n }\n\n // ============================================\n // Legacy methods (for backwards compatibility)\n // ============================================\n\n /**\n * @deprecated Use request({ method: 'eth_requestAccounts' })\n */\n async enable(): Promise {\n return this.request({ method: 'eth_requestAccounts' })\n }\n\n /**\n * @deprecated Use request()\n */\n send(method: string, params?: unknown[]): Promise {\n return this.request({ method, params })\n }\n\n /**\n * @deprecated Use request()\n */\n sendAsync(\n payload: { method: string; params?: unknown[]; id?: number },\n callback: (error: Error | null, result?: { result: unknown }) => void\n ): void {\n this.request({ method: payload.method, params: payload.params })\n .then((result) => callback(null, { result }))\n .catch((error) => callback(error))\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n ethereum?: EthereumProvider\n }\n}\n\n/**\n * Initialize and inject the Ethereum provider into window.ethereum\n */\nexport function initEthereumProvider(targetOrigin = '*'): EthereumProvider {\n if (typeof window === 'undefined') {\n throw new Error('[EthereumProvider] Cannot initialize: window is not defined')\n }\n\n if (window.ethereum) {\n console.warn('[EthereumProvider] Provider already exists, returning existing instance')\n return window.ethereum\n }\n\n const provider = new EthereumProvider(targetOrigin)\n window.ethereum = provider\n\n console.log('[EthereumProvider] Provider initialized')\n return provider\n}\n","/**\n * Tron Provider (TronLink Compatible)\n *\n * Provides window.tronWeb and window.tronLink for Tron dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError } from './types'\n\n/** Tron address format */\nexport interface TronAddress {\n base58: string\n hex: string\n}\n\n/** TronLink request arguments (EIP-1193 style) */\nexport interface TronRequestArguments {\n method: string\n params?: unknown\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'tron_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'tron_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'tron_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * TronLink-compatible Provider\n */\nexport class TronLinkProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'tron_response') {\n this.handleResponse(data)\n } else if (data.type === 'tron_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n }\n\n private generateId(): string {\n return `tron_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[TronLinkProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * TronLink request method (EIP-1193 style)\n */\n async request(args: TronRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params !== undefined ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'tron_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n}\n\n/**\n * TronWeb-compatible API\n * Provides the subset of TronWeb API that KeyApp supports\n */\nexport class TronWebProvider {\n private tronLink: TronLinkProvider\n private _ready = false\n private _defaultAddress: TronAddress = { base58: '', hex: '' }\n\n /** TRX operations */\n readonly trx: TronWebTrx\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n this.trx = new TronWebTrx(tronLink)\n\n // Listen for account changes\n tronLink.on('accountsChanged', (accounts: unknown) => {\n if (Array.isArray(accounts) && accounts.length > 0) {\n const addr = accounts[0] as TronAddress\n this._defaultAddress = addr\n this._ready = true\n } else {\n this._defaultAddress = { base58: '', hex: '' }\n this._ready = false\n }\n })\n }\n\n /** Whether TronWeb is ready (connected) */\n get ready(): boolean {\n return this._ready\n }\n\n /** Current default address */\n get defaultAddress(): TronAddress {\n return this._defaultAddress\n }\n\n /**\n * Set default address (called by host after connection)\n */\n setAddress(address: TronAddress): void {\n this._defaultAddress = address\n this._ready = true\n }\n\n /**\n * Check if an address is valid\n */\n isAddress(address: string): boolean {\n // Basic validation: base58 starts with T, hex starts with 41\n if (address.startsWith('T')) {\n return address.length === 34\n }\n if (address.startsWith('41')) {\n return address.length === 42\n }\n return false\n }\n\n /**\n * Convert address to hex format\n */\n address = {\n toHex: (base58: string): string => {\n // This is a stub - actual conversion requires TronWeb library\n // KeyApp will handle the conversion on the host side\n return base58\n },\n fromHex: (hex: string): string => {\n return hex\n },\n }\n}\n\n/**\n * TronWeb.trx operations\n */\nclass TronWebTrx {\n private tronLink: TronLinkProvider\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n }\n\n /**\n * Sign a transaction\n */\n async sign(transaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_signTransaction',\n params: transaction,\n })\n }\n\n /**\n * Send raw transaction (broadcast)\n */\n async sendRawTransaction(signedTransaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_sendRawTransaction',\n params: signedTransaction,\n })\n }\n\n /**\n * Get account balance\n */\n async getBalance(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getBalance',\n params: address,\n })\n }\n\n /**\n * Get account info\n */\n async getAccount(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getAccount',\n params: address,\n })\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n tronLink?: TronLinkProvider\n tronWeb?: TronWebProvider\n }\n}\n\n/**\n * Initialize and inject the Tron providers\n */\nexport function initTronProvider(targetOrigin = '*'): { tronLink: TronLinkProvider; tronWeb: TronWebProvider } {\n if (typeof window === 'undefined') {\n throw new Error('[TronProvider] Cannot initialize: window is not defined')\n }\n\n if (window.tronLink && window.tronWeb) {\n console.warn('[TronProvider] Providers already exist, returning existing instances')\n return { tronLink: window.tronLink, tronWeb: window.tronWeb }\n }\n\n const tronLink = new TronLinkProvider(targetOrigin)\n const tronWeb = new TronWebProvider(tronLink)\n\n window.tronLink = tronLink\n window.tronWeb = tronWeb\n\n console.log('[TronProvider] Providers initialized')\n return { tronLink, tronWeb }\n}\n","/**\n * Chain ID utilities\n * Maps between KeyApp internal chain IDs and standard chain IDs\n */\n\n/** EVM Chain ID mapping (decimal) */\nexport const EVM_CHAIN_IDS: Record = {\n ethereum: 1,\n binance: 56,\n // Future chains\n // polygon: 137,\n // arbitrum: 42161,\n // optimism: 10,\n} as const\n\n/** Reverse mapping: EVM chainId -> KeyApp chain ID */\nexport const EVM_CHAIN_ID_TO_KEYAPP: Record = Object.fromEntries(\n Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key])\n)\n\n/** API chain name to KeyApp chain ID mapping */\nexport const API_CHAIN_TO_KEYAPP: Record = {\n ETH: 'ethereum',\n BSC: 'binance',\n TRON: 'tron',\n // Lowercase variants\n eth: 'ethereum',\n bsc: 'binance',\n tron: 'tron',\n} as const\n\n/** KeyApp chain ID to display name */\nexport const CHAIN_DISPLAY_NAMES: Record = {\n ethereum: 'Ethereum',\n binance: 'BNB Smart Chain',\n tron: 'Tron',\n bfmeta: 'BFMeta',\n bfchain: 'BFChain',\n} as const\n\n/**\n * Convert decimal chain ID to hex string (EIP-155 format)\n * @example toHexChainId(56) => '0x38'\n */\nexport function toHexChainId(chainId: number): string {\n return `0x${chainId.toString(16)}`\n}\n\n/**\n * Parse hex chain ID to decimal\n * @example parseHexChainId('0x38') => 56\n */\nexport function parseHexChainId(hexChainId: string): number {\n if (!hexChainId.startsWith('0x')) {\n throw new Error(`Invalid hex chain ID: ${hexChainId}`)\n }\n return parseInt(hexChainId, 16)\n}\n\n/**\n * Get KeyApp chain ID from EVM hex chain ID\n * @example getKeyAppChainId('0x38') => 'binance'\n */\nexport function getKeyAppChainId(hexChainId: string): string | null {\n const decimal = parseHexChainId(hexChainId)\n return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null\n}\n\n/**\n * Get EVM hex chain ID from KeyApp chain ID\n * @example getEvmChainId('binance') => '0x38'\n */\nexport function getEvmChainId(keyAppChainId: string): string | null {\n const decimal = EVM_CHAIN_IDS[keyAppChainId]\n return decimal ? toHexChainId(decimal) : null\n}\n\n/**\n * Check if a chain is EVM compatible\n */\nexport function isEvmChain(chainId: string): boolean {\n return chainId in EVM_CHAIN_IDS\n}\n\n/**\n * Normalize API chain name to KeyApp chain ID\n * @example normalizeChainId('BSC') => 'binance'\n */\nexport function normalizeChainId(chainName: string): string {\n return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase()\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects providers for multi-chain dApp support:\n * - `window.bio` - BioChain + KeyApp wallet features\n * - `window.ethereum` - EVM-compatible chains (ETH, BSC)\n * - `window.tronWeb` / `window.tronLink` - Tron chain\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // BioChain operations\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n *\n * // EVM operations (ETH, BSC)\n * const ethAccounts = await window.ethereum.request({ method: 'eth_requestAccounts' })\n *\n * // Tron operations\n * const tronAccounts = await window.tronLink.request({ method: 'tron_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nimport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport * from './chain-id'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\nexport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nexport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\n\n// Extend Window interface (bio is declared in types.ts already for ethereum/tron)\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n/**\n * Initialize all providers (bio, ethereum, tron)\n */\nexport function initAllProviders(targetOrigin = '*'): {\n bio: BioProvider\n ethereum: EthereumProvider\n tronLink: TronLinkProvider\n tronWeb: TronWebProvider\n} {\n const bio = initBioProvider(targetOrigin)\n const ethereum = initEthereumProvider(targetOrigin)\n const { tronLink, tronWeb } = initTronProvider(targetOrigin)\n\n return { bio, ethereum, tronLink, tronWeb }\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n const init = () => {\n initBioProvider()\n initEthereumProvider()\n initTronProvider()\n }\n\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init)\n } else {\n init()\n }\n}\n"],"names":[],"mappings":";;AAqIO,MAAM,gBAAgB;AAAA,EAC3B,eAAe;AAAA,EACf,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,kBAAkB;AACpB;AAGO,SAAS,oBAAoB,MAAc,SAAiB,MAAkC;AACnG,QAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAM,OAAO;AACb,QAAM,OAAO;AACb,SAAO;AACT;AChJO,MAAM,aAAa;AAAA,EAChB,+BAAe,IAAA;AAAA,EAEvB,GAAG,OAAe,SAA6B;AAC7C,QAAI,WAAW,KAAK,SAAS,IAAI,KAAK;AACtC,QAAI,CAAC,UAAU;AACb,qCAAe,IAAA;AACf,WAAK,SAAS,IAAI,OAAO,QAAQ;AAAA,IACnC;AACA,aAAS,IAAI,OAAO;AAAA,EACtB;AAAA,EAEA,IAAI,OAAe,SAA6B;AAC9C,UAAM,WAAW,KAAK,SAAS,IAAI,KAAK;AACxC,QAAI,UAAU;AACZ,eAAS,OAAO,OAAO;AACvB,UAAI,SAAS,SAAS,GAAG;AACvB,aAAK,SAAS,OAAO,KAAK;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,KAAK,UAAkB,MAAuB;AAC5C,UAAM,WAAW,KAAK,SAAS,IAAI,KAAK;AACxC,QAAI,UAAU;AACZ,eAAS,QAAQ,CAAC,YAAY;AAC5B,YAAI;AACF,kBAAQ,GAAG,IAAI;AAAA,QACjB,SAAS,OAAO;AACd,kBAAQ,MAAM,wCAAwC,KAAK,MAAM,KAAK;AAAA,QACxE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,mBAAmB,OAAsB;AACvC,QAAI,OAAO;AACT,WAAK,SAAS,OAAO,KAAK;AAAA,IAC5B,OAAO;AACL,WAAK,SAAS,MAAA;AAAA,IAChB;AAAA,EACF;AACF;ACbO,MAAM,gBAAuC;AAAA,EAC1C,SAAS,IAAI,aAAA;AAAA,EACb,sCAAsB,IAAA;AAAA,EAItB,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACH;AAAA,EAEjB,YAAY,eAAe,KAAK;AAC9B,SAAK,eAAe;AACpB,SAAK,qBAAA;AACL,SAAK,QAAA;AAAA,EACP;AAAA,EAEQ,uBAA6B;AACnC,WAAO,iBAAiB,WAAW,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EAClE;AAAA,EAEQ,cAAc,OAA2B;AAC/C,UAAM,OAAO,MAAM;AACnB,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAEvC,QAAI,KAAK,SAAS,gBAAgB;AAChC,WAAK,eAAe,IAAI;AAAA,IAC1B,WAAW,KAAK,SAAS,aAAa;AACpC,WAAK,YAAY,IAAI;AAAA,IACvB;AAAA,EACF;AAAA,EAEQ,eAAe,SAAgC;AACrD,UAAM,UAAU,KAAK,gBAAgB,IAAI,QAAQ,EAAE;AACnD,QAAI,CAAC,QAAS;AAEd,SAAK,gBAAgB,OAAO,QAAQ,EAAE;AAEtC,QAAI,QAAQ,SAAS;AACnB,cAAQ,QAAQ,QAAQ,MAAM;AAAA,IAChC,OAAO;AACL,YAAM,QAAQ,QAAQ,SAAS,EAAE,MAAM,cAAc,gBAAgB,SAAS,gBAAA;AAC9E,cAAQ,OAAO,oBAAoB,MAAM,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EAEQ,YAAY,SAA6B;AAC/C,SAAK,OAAO,KAAK,QAAQ,OAAO,GAAG,QAAQ,IAAI;AAG/C,QAAI,QAAQ,UAAU,WAAW;AAC/B,WAAK,YAAY;AAAA,IACnB,WAAW,QAAQ,UAAU,cAAc;AACzC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,UAAgB;AAEtB,SAAK,YAAY;AAAA,MACf,MAAM;AAAA,MACN,IAAI,KAAK,WAAA;AAAA,MACT,QAAQ;AAAA,MACR,QAAQ,CAAA;AAAA,IAAC,CACV;AAAA,EACH;AAAA,EAEQ,aAAqB;AAC3B,WAAO,OAAO,KAAK,IAAA,CAAK,IAAI,EAAE,KAAK,gBAAgB;AAAA,EACrD;AAAA,EAEQ,YAAY,SAA+B;AACjD,QAAI,OAAO,WAAW,QAAQ;AAC5B,cAAQ,KAAK,8DAA8D;AAC3E;AAAA,IACF;AACA,WAAO,OAAO,YAAY,SAAS,KAAK,YAAY;AAAA,EACtD;AAAA,EAEA,MAAM,QAAqB,MAAoC;AAC7D,UAAM,KAAK,KAAK,WAAA;AAEhB,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,WAAK,gBAAgB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,MAAA,CACD;AAED,WAAK,YAAY;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,MAAA,CACd;AAGD,iBAAW,MAAM;AACf,YAAI,KAAK,gBAAgB,IAAI,EAAE,GAAG;AAChC,eAAK,gBAAgB,OAAO,EAAE;AAC9B,iBAAO,oBAAoB,cAAc,gBAAgB,iBAAiB,CAAC;AAAA,QAC7E;AAAA,MACF,GAAG,IAAI,KAAK,GAAI;AAAA,IAClB,CAAC;AAAA,EACH;AAAA,EAEA,GAAG,OAAe,SAA6B;AAC7C,SAAK,OAAO,GAAG,OAAO,OAAO;AAAA,EAC/B;AAAA,EAEA,IAAI,OAAe,SAA6B;AAC9C,SAAK,OAAO,IAAI,OAAO,OAAO;AAAA,EAChC;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AACF;AChFO,MAAM,iBAAiB;AAAA,EACpB,SAAS,IAAI,aAAA;AAAA,EACb,sCAAsB,IAAA;AAAA,EAItB,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,iBAAgC;AAAA,EAChC,WAAqB,CAAA;AAAA,EACZ;AAAA;AAAA,EAGR,aAAa;AAAA,EACb,WAAW;AAAA,EAEpB,YAAY,eAAe,KAAK;AAC9B,SAAK,eAAe;AACpB,SAAK,qBAAA;AAAA,EACP;AAAA,EAEQ,uBAA6B;AACnC,WAAO,iBAAiB,WAAW,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EAClE;AAAA,EAEQ,cAAc,OAA2B;AAC/C,UAAM,OAAO,MAAM;AACnB,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAEvC,QAAI,KAAK,SAAS,gBAAgB;AAChC,WAAK,eAAe,IAAI;AAAA,IAC1B,WAAW,KAAK,SAAS,aAAa;AACpC,WAAK,YAAY,IAAI;AAAA,IACvB;AAAA,EACF;AAAA,EAEQ,eAAe,SAAgC;AACrD,UAAM,UAAU,KAAK,gBAAgB,IAAI,QAAQ,EAAE;AACnD,QAAI,CAAC,QAAS;AAEd,SAAK,gBAAgB,OAAO,QAAQ,EAAE;AAEtC,QAAI,QAAQ,SAAS;AACnB,cAAQ,QAAQ,QAAQ,MAAM;AAAA,IAChC,OAAO;AACL,YAAM,QAAQ,QAAQ,SAAS,EAAE,MAAM,cAAc,gBAAgB,SAAS,gBAAA;AAC9E,cAAQ,OAAO,oBAAoB,MAAM,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EAEQ,YAAY,SAA6B;AAC/C,SAAK,OAAO,KAAK,QAAQ,OAAO,GAAG,QAAQ,IAAI;AAG/C,QAAI,QAAQ,UAAU,WAAW;AAC/B,WAAK,YAAY;AACjB,YAAM,OAAO,QAAQ,KAAK,CAAC;AAC3B,WAAK,iBAAiB,MAAM,WAAW;AAAA,IACzC,WAAW,QAAQ,UAAU,cAAc;AACzC,WAAK,YAAY;AACjB,WAAK,WAAW,CAAA;AAAA,IAClB,WAAW,QAAQ,UAAU,gBAAgB;AAC3C,WAAK,iBAAiB,QAAQ,KAAK,CAAC;AAAA,IACtC,WAAW,QAAQ,UAAU,mBAAmB;AAC9C,WAAK,WAAW,QAAQ,KAAK,CAAC;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,aAAqB;AAC3B,WAAO,OAAO,KAAK,IAAA,CAAK,IAAI,EAAE,KAAK,gBAAgB;AAAA,EACrD;AAAA,EAEQ,YAAY,SAA+B;AACjD,QAAI,OAAO,WAAW,QAAQ;AAC5B,cAAQ,KAAK,wEAAwE;AACrF;AAAA,IACF;AACA,WAAO,OAAO,YAAY,SAAS,KAAK,YAAY;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAqB,MAAuC;AAChE,UAAM,EAAE,QAAQ,OAAA,IAAW;AAC3B,UAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,SAAS,SAAS,CAAC,MAAM,IAAI,CAAA;AAEzE,UAAM,KAAK,KAAK,WAAA;AAEhB,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,WAAK,gBAAgB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,MAAA,CACD;AAED,WAAK,YAAY;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MAAA,CACT;AAGD,iBAAW,MAAM;AACf,YAAI,KAAK,gBAAgB,IAAI,EAAE,GAAG;AAChC,eAAK,gBAAgB,OAAO,EAAE;AAC9B,iBAAO,oBAAoB,cAAc,gBAAgB,iBAAiB,CAAC;AAAA,QAC7E;AAAA,MACF,GAAG,IAAI,KAAK,GAAI;AAAA,IAClB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,GAAG,OAAe,SAA6C;AAC7D,SAAK,OAAO,GAAG,OAAO,OAAO;AAC7B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe,SAA6C;AAC9D,SAAK,OAAO,IAAI,OAAO,OAAO;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,OAAe,SAA6C;AACzE,WAAO,KAAK,IAAI,OAAO,OAAO;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,OAAe,SAA6C;AAC/D,UAAM,UAAU,IAAI,SAAoB;AACtC,WAAK,IAAI,OAAO,OAAO;AACvB,cAAQ,GAAG,IAAI;AAAA,IACjB;AACA,SAAK,GAAG,OAAO,OAAO;AACtB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,kBAAiC;AACnC,WAAO,KAAK,SAAS,CAAC,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,SAA4B;AAChC,WAAO,KAAK,QAAQ,EAAE,QAAQ,uBAAuB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,QAAgB,QAAsC;AACzD,WAAO,KAAK,QAAQ,EAAE,QAAQ,QAAQ;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,UACE,SACA,UACM;AACN,SAAK,QAAQ,EAAE,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,OAAA,CAAQ,EAC5D,KAAK,CAAC,WAAW,SAAS,MAAM,EAAE,QAAQ,CAAC,EAC3C,MAAM,CAAC,UAAU,SAAS,KAAK,CAAC;AAAA,EACrC;AACF;AAYO,SAAS,qBAAqB,eAAe,KAAuB;AACzE,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AAEA,MAAI,OAAO,UAAU;AACnB,YAAQ,KAAK,yEAAyE;AACtF,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,WAAW,IAAI,iBAAiB,YAAY;AAClD,SAAO,WAAW;AAElB,UAAQ,IAAI,yCAAyC;AACrD,SAAO;AACT;ACnPO,MAAM,iBAAiB;AAAA,EACpB,SAAS,IAAI,aAAA;AAAA,EACb,sCAAsB,IAAA;AAAA,EAItB,mBAAmB;AAAA,EACV;AAAA,EAEjB,YAAY,eAAe,KAAK;AAC9B,SAAK,eAAe;AACpB,SAAK,qBAAA;AAAA,EACP;AAAA,EAEQ,uBAA6B;AACnC,WAAO,iBAAiB,WAAW,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EAClE;AAAA,EAEQ,cAAc,OAA2B;AAC/C,UAAM,OAAO,MAAM;AACnB,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAEvC,QAAI,KAAK,SAAS,iBAAiB;AACjC,WAAK,eAAe,IAAI;AAAA,IAC1B,WAAW,KAAK,SAAS,cAAc;AACrC,WAAK,YAAY,IAAI;AAAA,IACvB;AAAA,EACF;AAAA,EAEQ,eAAe,SAAgC;AACrD,UAAM,UAAU,KAAK,gBAAgB,IAAI,QAAQ,EAAE;AACnD,QAAI,CAAC,QAAS;AAEd,SAAK,gBAAgB,OAAO,QAAQ,EAAE;AAEtC,QAAI,QAAQ,SAAS;AACnB,cAAQ,QAAQ,QAAQ,MAAM;AAAA,IAChC,OAAO;AACL,YAAM,QAAQ,QAAQ,SAAS,EAAE,MAAM,cAAc,gBAAgB,SAAS,gBAAA;AAC9E,cAAQ,OAAO,oBAAoB,MAAM,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EAEQ,YAAY,SAA6B;AAC/C,SAAK,OAAO,KAAK,QAAQ,OAAO,GAAG,QAAQ,IAAI;AAAA,EACjD;AAAA,EAEQ,aAAqB;AAC3B,WAAO,QAAQ,KAAK,IAAA,CAAK,IAAI,EAAE,KAAK,gBAAgB;AAAA,EACtD;AAAA,EAEQ,YAAY,SAA+B;AACjD,QAAI,OAAO,WAAW,QAAQ;AAC5B,cAAQ,KAAK,wEAAwE;AACrF;AAAA,IACF;AACA,WAAO,OAAO,YAAY,SAAS,KAAK,YAAY;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAqB,MAAwC;AACjE,UAAM,EAAE,QAAQ,OAAA,IAAW;AAC3B,UAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,SAAS,WAAW,SAAY,CAAC,MAAM,IAAI,CAAA;AAEvF,UAAM,KAAK,KAAK,WAAA;AAEhB,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,WAAK,gBAAgB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,MAAA,CACD;AAED,WAAK,YAAY;AAAA,QACf,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MAAA,CACT;AAGD,iBAAW,MAAM;AACf,YAAI,KAAK,gBAAgB,IAAI,EAAE,GAAG;AAChC,eAAK,gBAAgB,OAAO,EAAE;AAC9B,iBAAO,oBAAoB,cAAc,gBAAgB,iBAAiB,CAAC;AAAA,QAC7E;AAAA,MACF,GAAG,IAAI,KAAK,GAAI;AAAA,IAClB,CAAC;AAAA,EACH;AAAA,EAEA,GAAG,OAAe,SAA6C;AAC7D,SAAK,OAAO,GAAG,OAAO,OAAO;AAC7B,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAe,SAA6C;AAC9D,SAAK,OAAO,IAAI,OAAO,OAAO;AAC9B,WAAO;AAAA,EACT;AACF;AAMO,MAAM,gBAAgB;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,EACT,kBAA+B,EAAE,QAAQ,IAAI,KAAK,GAAA;AAAA;AAAA,EAGjD;AAAA,EAET,YAAY,UAA4B;AACtC,SAAK,WAAW;AAChB,SAAK,MAAM,IAAI,WAAW,QAAQ;AAGlC,aAAS,GAAG,mBAAmB,CAAC,aAAsB;AACpD,UAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAAG;AAClD,cAAM,OAAO,SAAS,CAAC;AACvB,aAAK,kBAAkB;AACvB,aAAK,SAAS;AAAA,MAChB,OAAO;AACL,aAAK,kBAAkB,EAAE,QAAQ,IAAI,KAAK,GAAA;AAC1C,aAAK,SAAS;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,IAAI,QAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,iBAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA4B;AACrC,SAAK,kBAAkB;AACvB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAA0B;AAElC,QAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,aAAO,QAAQ,WAAW;AAAA,IAC5B;AACA,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC5B,aAAO,QAAQ,WAAW;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AAAA,IACR,OAAO,CAAC,WAA2B;AAGjC,aAAO;AAAA,IACT;AAAA,IACA,SAAS,CAAC,QAAwB;AAChC,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAKA,MAAM,WAAW;AAAA,EACP;AAAA,EAER,YAAY,UAA4B;AACtC,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,aAAwC;AACjD,WAAO,KAAK,SAAS,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA,CACT;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB,mBAA8C;AACrE,WAAO,KAAK,SAAS,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA,CACT;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,SAAkC;AACjD,WAAO,KAAK,SAAS,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA,CACT;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,SAAmC;AAClD,WAAO,KAAK,SAAS,QAAQ;AAAA,MAC3B,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA,CACT;AAAA,EACH;AACF;AAaO,SAAS,iBAAiB,eAAe,KAA+D;AAC7G,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AAEA,MAAI,OAAO,YAAY,OAAO,SAAS;AACrC,YAAQ,KAAK,sEAAsE;AACnF,WAAO,EAAE,UAAU,OAAO,UAAU,SAAS,OAAO,QAAA;AAAA,EACtD;AAEA,QAAM,WAAW,IAAI,iBAAiB,YAAY;AAClD,QAAM,UAAU,IAAI,gBAAgB,QAAQ;AAE5C,SAAO,WAAW;AAClB,SAAO,UAAU;AAEjB,UAAQ,IAAI,sCAAsC;AAClD,SAAO,EAAE,UAAU,QAAA;AACrB;AC/SO,MAAM,gBAAwC;AAAA,EACnD,UAAU;AAAA,EACV,SAAS;AAAA;AAAA;AAAA;AAAA;AAKX;AAGO,MAAM,yBAAiD,OAAO;AAAA,EACnE,OAAO,QAAQ,aAAa,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,GAAG,CAAC;AAClE;AAGO,MAAM,sBAA8C;AAAA,EACzD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA;AAAA,EAEN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AACR;AAGO,MAAM,sBAA8C;AAAA,EACzD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AACX;AAMO,SAAS,aAAa,SAAyB;AACpD,SAAO,KAAK,QAAQ,SAAS,EAAE,CAAC;AAClC;AAMO,SAAS,gBAAgB,YAA4B;AAC1D,MAAI,CAAC,WAAW,WAAW,IAAI,GAAG;AAChC,UAAM,IAAI,MAAM,yBAAyB,UAAU,EAAE;AAAA,EACvD;AACA,SAAO,SAAS,YAAY,EAAE;AAChC;AAMO,SAAS,iBAAiB,YAAmC;AAClE,QAAM,UAAU,gBAAgB,UAAU;AAC1C,SAAO,uBAAuB,OAAO,KAAK;AAC5C;AAMO,SAAS,cAAc,eAAsC;AAClE,QAAM,UAAU,cAAc,aAAa;AAC3C,SAAO,UAAU,aAAa,OAAO,IAAI;AAC3C;AAKO,SAAS,WAAW,SAA0B;AACnD,SAAO,WAAW;AACpB;AAMO,SAAS,iBAAiB,WAA2B;AAC1D,SAAO,oBAAoB,SAAS,KAAK,UAAU,YAAA;AACrD;AC5CO,SAAS,gBAAgB,eAAe,KAAkB;AAC/D,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,MAAI,OAAO,KAAK;AACd,YAAQ,KAAK,+DAA+D;AAC5E,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,WAAW,IAAI,gBAAgB,YAAY;AACjD,SAAO,MAAM;AAEb,UAAQ,IAAI,+BAA+B;AAC3C,SAAO;AACT;AAKO,SAAS,iBAAiB,eAAe,KAK9C;AACA,QAAM,MAAM,gBAAgB,YAAY;AACxC,QAAM,WAAW,qBAAqB,YAAY;AAClD,QAAM,EAAE,UAAU,YAAY,iBAAiB,YAAY;AAE3D,SAAO,EAAE,KAAK,UAAU,UAAU,QAAA;AACpC;AAGA,IAAI,OAAO,WAAW,aAAa;AACjC,QAAM,OAAO,MAAM;AACjB,oBAAA;AACA,yBAAA;AACA,qBAAA;AAAA,EACF;AAGA,MAAI,SAAS,eAAe,WAAW;AACrC,aAAS,iBAAiB,oBAAoB,IAAI;AAAA,EACpD,OAAO;AACL,SAAA;AAAA,EACF;AACF;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/packages/bio-sdk/dist/index.d.ts b/packages/bio-sdk/dist/index.d.ts index 5b18d899f..10143204a 100644 --- a/packages/bio-sdk/dist/index.d.ts +++ b/packages/bio-sdk/dist/index.d.ts @@ -1,3 +1,6 @@ +/** API chain name to KeyApp chain ID mapping */ +export declare const API_CHAIN_TO_KEYAPP: Record; + /** * Bio SDK Types * EIP-1193 style provider interface for Bio ecosystem @@ -151,9 +154,96 @@ export declare interface BioUnsignedTransaction { data: unknown; } +/** KeyApp chain ID to display name */ +export declare const CHAIN_DISPLAY_NAMES: Record; + /** Create a provider RPC error */ export declare function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError; +/** + * EIP-1193 Ethereum Provider Implementation + */ +export declare class EthereumProvider { + private events; + private pendingRequests; + private requestIdCounter; + private connected; + private currentChainId; + private accounts; + private readonly targetOrigin; + readonly isMetaMask = false; + readonly isKeyApp = true; + constructor(targetOrigin?: string); + private setupMessageListener; + private handleMessage; + private handleResponse; + private handleEvent; + private generateId; + private postMessage; + /** + * EIP-1193 request method + */ + request(args: EthRequestArguments): Promise; + /** + * Subscribe to an event + */ + on(event: string, handler: (...args: unknown[]) => void): this; + /** + * Unsubscribe from an event + */ + off(event: string, handler: (...args: unknown[]) => void): this; + /** + * Alias for off (Node.js EventEmitter compatibility) + */ + removeListener(event: string, handler: (...args: unknown[]) => void): this; + /** + * Add listener that fires only once + */ + once(event: string, handler: (...args: unknown[]) => void): this; + /** + * EIP-1193 isConnected method + */ + isConnected(): boolean; + /** + * Get current chain ID (cached) + */ + get chainId(): string | null; + /** + * Get selected address (first account) + */ + get selectedAddress(): string | null; + /** + * @deprecated Use request({ method: 'eth_requestAccounts' }) + */ + enable(): Promise; + /** + * @deprecated Use request() + */ + send(method: string, params?: unknown[]): Promise; + /** + * @deprecated Use request() + */ + sendAsync(payload: { + method: string; + params?: unknown[]; + id?: number; + }, callback: (error: Error | null, result?: { + result: unknown; + }) => void): void; +} + +/** + * Ethereum Provider (EIP-1193 Compatible) + * + * Provides window.ethereum for EVM-compatible dApps. + * Communicates with KeyApp host via postMessage. + */ +/** EIP-1193 Request Arguments */ +declare interface EthRequestArguments { + method: string; + params?: unknown[] | Record; +} + export declare class EventEmitter { private handlers; on(event: string, handler: EventHandler): void; @@ -165,11 +255,69 @@ export declare class EventEmitter { /** Event handler type */ export declare type EventHandler = (...args: T[]) => void; +/** Reverse mapping: EVM chainId -> KeyApp chain ID */ +export declare const EVM_CHAIN_ID_TO_KEYAPP: Record; + +/** EVM Chain ID mapping (decimal) */ +export declare const EVM_CHAIN_IDS: Record; + +/** + * Get EVM hex chain ID from KeyApp chain ID + * @example getEvmChainId('binance') => '0x38' + */ +export declare function getEvmChainId(keyAppChainId: string): string | null; + +/** + * Get KeyApp chain ID from EVM hex chain ID + * @example getKeyAppChainId('0x38') => 'binance' + */ +export declare function getKeyAppChainId(hexChainId: string): string | null; + +/** + * Initialize all providers (bio, ethereum, tron) + */ +export declare function initAllProviders(targetOrigin?: string): { + bio: BioProvider; + ethereum: EthereumProvider; + tronLink: TronLinkProvider; + tronWeb: TronWebProvider; +}; + /** * Initialize and inject the Bio provider into window.bio */ export declare function initBioProvider(targetOrigin?: string): BioProvider; +/** + * Initialize and inject the Ethereum provider into window.ethereum + */ +export declare function initEthereumProvider(targetOrigin?: string): EthereumProvider; + +/** + * Initialize and inject the Tron providers + */ +export declare function initTronProvider(targetOrigin?: string): { + tronLink: TronLinkProvider; + tronWeb: TronWebProvider; +}; + +/** + * Check if a chain is EVM compatible + */ +export declare function isEvmChain(chainId: string): boolean; + +/** + * Normalize API chain name to KeyApp chain ID + * @example normalizeChainId('BSC') => 'binance' + */ +export declare function normalizeChainId(chainName: string): string; + +/** + * Parse hex chain ID to decimal + * @example parseHexChainId('0x38') => 56 + */ +export declare function parseHexChainId(hexChainId: string): number; + /** Provider RPC error */ export declare interface ProviderRpcError extends Error { code: number; @@ -182,6 +330,12 @@ export declare interface RequestArguments { params?: unknown[]; } +/** + * Convert decimal chain ID to hex string (EIP-155 format) + * @example toHexChainId(56) => '0x38' + */ +export declare function toHexChainId(chainId: number): string; + /** Transfer parameters */ export declare interface TransferParams { from: string; @@ -191,4 +345,101 @@ export declare interface TransferParams { asset?: string; } +/** + * Tron Provider (TronLink Compatible) + * + * Provides window.tronWeb and window.tronLink for Tron dApps. + * Communicates with KeyApp host via postMessage. + */ +/** Tron address format */ +declare interface TronAddress { + base58: string; + hex: string; +} + +/** + * TronLink-compatible Provider + */ +export declare class TronLinkProvider { + private events; + private pendingRequests; + private requestIdCounter; + private readonly targetOrigin; + constructor(targetOrigin?: string); + private setupMessageListener; + private handleMessage; + private handleResponse; + private handleEvent; + private generateId; + private postMessage; + /** + * TronLink request method (EIP-1193 style) + */ + request(args: TronRequestArguments): Promise; + on(event: string, handler: (...args: unknown[]) => void): this; + off(event: string, handler: (...args: unknown[]) => void): this; +} + +/** TronLink request arguments (EIP-1193 style) */ +declare interface TronRequestArguments { + method: string; + params?: unknown; +} + +/** + * TronWeb-compatible API + * Provides the subset of TronWeb API that KeyApp supports + */ +export declare class TronWebProvider { + private tronLink; + private _ready; + private _defaultAddress; + /** TRX operations */ + readonly trx: TronWebTrx; + constructor(tronLink: TronLinkProvider); + /** Whether TronWeb is ready (connected) */ + get ready(): boolean; + /** Current default address */ + get defaultAddress(): TronAddress; + /** + * Set default address (called by host after connection) + */ + setAddress(address: TronAddress): void; + /** + * Check if an address is valid + */ + isAddress(address: string): boolean; + /** + * Convert address to hex format + */ + address: { + toHex: (base58: string) => string; + fromHex: (hex: string) => string; + }; +} + +/** + * TronWeb.trx operations + */ +declare class TronWebTrx { + private tronLink; + constructor(tronLink: TronLinkProvider); + /** + * Sign a transaction + */ + sign(transaction: unknown): Promise; + /** + * Send raw transaction (broadcast) + */ + sendRawTransaction(signedTransaction: unknown): Promise; + /** + * Get account balance + */ + getBalance(address: string): Promise; + /** + * Get account info + */ + getAccount(address: string): Promise; +} + export { } diff --git a/packages/bio-sdk/dist/index.js b/packages/bio-sdk/dist/index.js index 3342a76c6..7b21fd7f7 100644 --- a/packages/bio-sdk/dist/index.js +++ b/packages/bio-sdk/dist/index.js @@ -144,6 +144,425 @@ class BioProviderImpl { return this.connected; } } +class EthereumProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + connected = false; + currentChainId = null; + accounts = []; + targetOrigin; + // EIP-1193 required properties + isMetaMask = false; + isKeyApp = true; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "eth_response") { + this.handleResponse(data); + } else if (data.type === "eth_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + if (message.event === "connect") { + this.connected = true; + const info = message.args[0]; + this.currentChainId = info?.chainId ?? null; + } else if (message.event === "disconnect") { + this.connected = false; + this.accounts = []; + } else if (message.event === "chainChanged") { + this.currentChainId = message.args[0]; + } else if (message.event === "accountsChanged") { + this.accounts = message.args[0]; + } + } + generateId() { + return `eth_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[EthereumProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * EIP-1193 request method + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "eth_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + /** + * Subscribe to an event + */ + on(event, handler) { + this.events.on(event, handler); + return this; + } + /** + * Unsubscribe from an event + */ + off(event, handler) { + this.events.off(event, handler); + return this; + } + /** + * Alias for off (Node.js EventEmitter compatibility) + */ + removeListener(event, handler) { + return this.off(event, handler); + } + /** + * Add listener that fires only once + */ + once(event, handler) { + const wrapper = (...args) => { + this.off(event, wrapper); + handler(...args); + }; + this.on(event, wrapper); + return this; + } + /** + * EIP-1193 isConnected method + */ + isConnected() { + return this.connected; + } + /** + * Get current chain ID (cached) + */ + get chainId() { + return this.currentChainId; + } + /** + * Get selected address (first account) + */ + get selectedAddress() { + return this.accounts[0] ?? null; + } + // ============================================ + // Legacy methods (for backwards compatibility) + // ============================================ + /** + * @deprecated Use request({ method: 'eth_requestAccounts' }) + */ + async enable() { + return this.request({ method: "eth_requestAccounts" }); + } + /** + * @deprecated Use request() + */ + send(method, params) { + return this.request({ method, params }); + } + /** + * @deprecated Use request() + */ + sendAsync(payload, callback) { + this.request({ method: payload.method, params: payload.params }).then((result) => callback(null, { result })).catch((error) => callback(error)); + } +} +function initEthereumProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[EthereumProvider] Cannot initialize: window is not defined"); + } + if (window.ethereum) { + console.warn("[EthereumProvider] Provider already exists, returning existing instance"); + return window.ethereum; + } + const provider = new EthereumProvider(targetOrigin); + window.ethereum = provider; + console.log("[EthereumProvider] Provider initialized"); + return provider; +} +class TronLinkProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + targetOrigin; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "tron_response") { + this.handleResponse(data); + } else if (data.type === "tron_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + } + generateId() { + return `tron_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[TronLinkProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * TronLink request method (EIP-1193 style) + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params !== void 0 ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "tron_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + on(event, handler) { + this.events.on(event, handler); + return this; + } + off(event, handler) { + this.events.off(event, handler); + return this; + } +} +class TronWebProvider { + tronLink; + _ready = false; + _defaultAddress = { base58: "", hex: "" }; + /** TRX operations */ + trx; + constructor(tronLink) { + this.tronLink = tronLink; + this.trx = new TronWebTrx(tronLink); + tronLink.on("accountsChanged", (accounts) => { + if (Array.isArray(accounts) && accounts.length > 0) { + const addr = accounts[0]; + this._defaultAddress = addr; + this._ready = true; + } else { + this._defaultAddress = { base58: "", hex: "" }; + this._ready = false; + } + }); + } + /** Whether TronWeb is ready (connected) */ + get ready() { + return this._ready; + } + /** Current default address */ + get defaultAddress() { + return this._defaultAddress; + } + /** + * Set default address (called by host after connection) + */ + setAddress(address) { + this._defaultAddress = address; + this._ready = true; + } + /** + * Check if an address is valid + */ + isAddress(address) { + if (address.startsWith("T")) { + return address.length === 34; + } + if (address.startsWith("41")) { + return address.length === 42; + } + return false; + } + /** + * Convert address to hex format + */ + address = { + toHex: (base58) => { + return base58; + }, + fromHex: (hex) => { + return hex; + } + }; +} +class TronWebTrx { + tronLink; + constructor(tronLink) { + this.tronLink = tronLink; + } + /** + * Sign a transaction + */ + async sign(transaction) { + return this.tronLink.request({ + method: "tron_signTransaction", + params: transaction + }); + } + /** + * Send raw transaction (broadcast) + */ + async sendRawTransaction(signedTransaction) { + return this.tronLink.request({ + method: "tron_sendRawTransaction", + params: signedTransaction + }); + } + /** + * Get account balance + */ + async getBalance(address) { + return this.tronLink.request({ + method: "tron_getBalance", + params: address + }); + } + /** + * Get account info + */ + async getAccount(address) { + return this.tronLink.request({ + method: "tron_getAccount", + params: address + }); + } +} +function initTronProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[TronProvider] Cannot initialize: window is not defined"); + } + if (window.tronLink && window.tronWeb) { + console.warn("[TronProvider] Providers already exist, returning existing instances"); + return { tronLink: window.tronLink, tronWeb: window.tronWeb }; + } + const tronLink = new TronLinkProvider(targetOrigin); + const tronWeb = new TronWebProvider(tronLink); + window.tronLink = tronLink; + window.tronWeb = tronWeb; + console.log("[TronProvider] Providers initialized"); + return { tronLink, tronWeb }; +} +const EVM_CHAIN_IDS = { + ethereum: 1, + binance: 56 + // Future chains + // polygon: 137, + // arbitrum: 42161, + // optimism: 10, +}; +const EVM_CHAIN_ID_TO_KEYAPP = Object.fromEntries( + Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key]) +); +const API_CHAIN_TO_KEYAPP = { + ETH: "ethereum", + BSC: "binance", + TRON: "tron", + // Lowercase variants + eth: "ethereum", + bsc: "binance", + tron: "tron" +}; +const CHAIN_DISPLAY_NAMES = { + ethereum: "Ethereum", + binance: "BNB Smart Chain", + tron: "Tron", + bfmeta: "BFMeta", + bfchain: "BFChain" +}; +function toHexChainId(chainId) { + return `0x${chainId.toString(16)}`; +} +function parseHexChainId(hexChainId) { + if (!hexChainId.startsWith("0x")) { + throw new Error(`Invalid hex chain ID: ${hexChainId}`); + } + return parseInt(hexChainId, 16); +} +function getKeyAppChainId(hexChainId) { + const decimal = parseHexChainId(hexChainId); + return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null; +} +function getEvmChainId(keyAppChainId) { + const decimal = EVM_CHAIN_IDS[keyAppChainId]; + return decimal ? toHexChainId(decimal) : null; +} +function isEvmChain(chainId) { + return chainId in EVM_CHAIN_IDS; +} +function normalizeChainId(chainName) { + return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase(); +} function initBioProvider(targetOrigin = "*") { if (typeof window === "undefined") { throw new Error("[BioSDK] Cannot initialize: window is not defined"); @@ -157,18 +576,45 @@ function initBioProvider(targetOrigin = "*") { console.log("[BioSDK] Provider initialized"); return provider; } +function initAllProviders(targetOrigin = "*") { + const bio = initBioProvider(targetOrigin); + const ethereum = initEthereumProvider(targetOrigin); + const { tronLink, tronWeb } = initTronProvider(targetOrigin); + return { bio, ethereum, tronLink, tronWeb }; +} if (typeof window !== "undefined") { + const init = () => { + initBioProvider(); + initEthereumProvider(); + initTronProvider(); + }; if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => initBioProvider()); + document.addEventListener("DOMContentLoaded", init); } else { - initBioProvider(); + init(); } } export { + API_CHAIN_TO_KEYAPP, BioErrorCodes, BioProviderImpl, + CHAIN_DISPLAY_NAMES, + EVM_CHAIN_IDS, + EVM_CHAIN_ID_TO_KEYAPP, + EthereumProvider, EventEmitter, + TronLinkProvider, + TronWebProvider, createProviderError, - initBioProvider + getEvmChainId, + getKeyAppChainId, + initAllProviders, + initBioProvider, + initEthereumProvider, + initTronProvider, + isEvmChain, + normalizeChainId, + parseHexChainId, + toHexChainId }; //# sourceMappingURL=index.js.map diff --git a/packages/bio-sdk/dist/index.js.map b/packages/bio-sdk/dist/index.js.map index 0eb660931..f75a23424 100644 --- a/packages/bio-sdk/dist/index.js.map +++ b/packages/bio-sdk/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects `window.bio` provider similar to `window.ethereum` in Web3 DApps.\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // Now window.bio is available\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\n\n// Extend Window interface\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => initBioProvider())\n } else {\n initBioProvider()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/ethereum-provider.ts","../src/tron-provider.ts","../src/chain-id.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Ethereum Provider (EIP-1193 Compatible)\n *\n * Provides window.ethereum for EVM-compatible dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError, type ProviderRpcError } from './types'\nimport { toHexChainId, parseHexChainId, getKeyAppChainId, EVM_CHAIN_IDS } from './chain-id'\n\n/** EIP-1193 Request Arguments */\nexport interface EthRequestArguments {\n method: string\n params?: unknown[] | Record\n}\n\n/** EIP-1193 Provider Connect Info */\nexport interface ProviderConnectInfo {\n chainId: string\n}\n\n/** EIP-1193 Provider Message */\nexport interface ProviderMessage {\n type: string\n data: unknown\n}\n\n/** Transaction request (eth_sendTransaction) */\nexport interface TransactionRequest {\n from: string\n to?: string\n value?: string\n data?: string\n gas?: string\n gasPrice?: string\n maxFeePerGas?: string\n maxPriorityFeePerGas?: string\n nonce?: string\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'eth_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'eth_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'eth_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * EIP-1193 Ethereum Provider Implementation\n */\nexport class EthereumProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private currentChainId: string | null = null\n private accounts: string[] = []\n private readonly targetOrigin: string\n\n // EIP-1193 required properties\n readonly isMetaMask = false\n readonly isKeyApp = true\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'eth_response') {\n this.handleResponse(data)\n } else if (data.type === 'eth_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n const info = message.args[0] as ProviderConnectInfo\n this.currentChainId = info?.chainId ?? null\n } else if (message.event === 'disconnect') {\n this.connected = false\n this.accounts = []\n } else if (message.event === 'chainChanged') {\n this.currentChainId = message.args[0] as string\n } else if (message.event === 'accountsChanged') {\n this.accounts = message.args[0] as string[]\n }\n }\n\n private generateId(): string {\n return `eth_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[EthereumProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * EIP-1193 request method\n */\n async request(args: EthRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'eth_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n /**\n * Subscribe to an event\n */\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n /**\n * Unsubscribe from an event\n */\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n\n /**\n * Alias for off (Node.js EventEmitter compatibility)\n */\n removeListener(event: string, handler: (...args: unknown[]) => void): this {\n return this.off(event, handler)\n }\n\n /**\n * Add listener that fires only once\n */\n once(event: string, handler: (...args: unknown[]) => void): this {\n const wrapper = (...args: unknown[]) => {\n this.off(event, wrapper)\n handler(...args)\n }\n this.on(event, wrapper)\n return this\n }\n\n /**\n * EIP-1193 isConnected method\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * Get current chain ID (cached)\n */\n get chainId(): string | null {\n return this.currentChainId\n }\n\n /**\n * Get selected address (first account)\n */\n get selectedAddress(): string | null {\n return this.accounts[0] ?? null\n }\n\n // ============================================\n // Legacy methods (for backwards compatibility)\n // ============================================\n\n /**\n * @deprecated Use request({ method: 'eth_requestAccounts' })\n */\n async enable(): Promise {\n return this.request({ method: 'eth_requestAccounts' })\n }\n\n /**\n * @deprecated Use request()\n */\n send(method: string, params?: unknown[]): Promise {\n return this.request({ method, params })\n }\n\n /**\n * @deprecated Use request()\n */\n sendAsync(\n payload: { method: string; params?: unknown[]; id?: number },\n callback: (error: Error | null, result?: { result: unknown }) => void\n ): void {\n this.request({ method: payload.method, params: payload.params })\n .then((result) => callback(null, { result }))\n .catch((error) => callback(error))\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n ethereum?: EthereumProvider\n }\n}\n\n/**\n * Initialize and inject the Ethereum provider into window.ethereum\n */\nexport function initEthereumProvider(targetOrigin = '*'): EthereumProvider {\n if (typeof window === 'undefined') {\n throw new Error('[EthereumProvider] Cannot initialize: window is not defined')\n }\n\n if (window.ethereum) {\n console.warn('[EthereumProvider] Provider already exists, returning existing instance')\n return window.ethereum\n }\n\n const provider = new EthereumProvider(targetOrigin)\n window.ethereum = provider\n\n console.log('[EthereumProvider] Provider initialized')\n return provider\n}\n","/**\n * Tron Provider (TronLink Compatible)\n *\n * Provides window.tronWeb and window.tronLink for Tron dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError } from './types'\n\n/** Tron address format */\nexport interface TronAddress {\n base58: string\n hex: string\n}\n\n/** TronLink request arguments (EIP-1193 style) */\nexport interface TronRequestArguments {\n method: string\n params?: unknown\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'tron_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'tron_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'tron_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * TronLink-compatible Provider\n */\nexport class TronLinkProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'tron_response') {\n this.handleResponse(data)\n } else if (data.type === 'tron_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n }\n\n private generateId(): string {\n return `tron_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[TronLinkProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * TronLink request method (EIP-1193 style)\n */\n async request(args: TronRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params !== undefined ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'tron_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n}\n\n/**\n * TronWeb-compatible API\n * Provides the subset of TronWeb API that KeyApp supports\n */\nexport class TronWebProvider {\n private tronLink: TronLinkProvider\n private _ready = false\n private _defaultAddress: TronAddress = { base58: '', hex: '' }\n\n /** TRX operations */\n readonly trx: TronWebTrx\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n this.trx = new TronWebTrx(tronLink)\n\n // Listen for account changes\n tronLink.on('accountsChanged', (accounts: unknown) => {\n if (Array.isArray(accounts) && accounts.length > 0) {\n const addr = accounts[0] as TronAddress\n this._defaultAddress = addr\n this._ready = true\n } else {\n this._defaultAddress = { base58: '', hex: '' }\n this._ready = false\n }\n })\n }\n\n /** Whether TronWeb is ready (connected) */\n get ready(): boolean {\n return this._ready\n }\n\n /** Current default address */\n get defaultAddress(): TronAddress {\n return this._defaultAddress\n }\n\n /**\n * Set default address (called by host after connection)\n */\n setAddress(address: TronAddress): void {\n this._defaultAddress = address\n this._ready = true\n }\n\n /**\n * Check if an address is valid\n */\n isAddress(address: string): boolean {\n // Basic validation: base58 starts with T, hex starts with 41\n if (address.startsWith('T')) {\n return address.length === 34\n }\n if (address.startsWith('41')) {\n return address.length === 42\n }\n return false\n }\n\n /**\n * Convert address to hex format\n */\n address = {\n toHex: (base58: string): string => {\n // This is a stub - actual conversion requires TronWeb library\n // KeyApp will handle the conversion on the host side\n return base58\n },\n fromHex: (hex: string): string => {\n return hex\n },\n }\n}\n\n/**\n * TronWeb.trx operations\n */\nclass TronWebTrx {\n private tronLink: TronLinkProvider\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n }\n\n /**\n * Sign a transaction\n */\n async sign(transaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_signTransaction',\n params: transaction,\n })\n }\n\n /**\n * Send raw transaction (broadcast)\n */\n async sendRawTransaction(signedTransaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_sendRawTransaction',\n params: signedTransaction,\n })\n }\n\n /**\n * Get account balance\n */\n async getBalance(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getBalance',\n params: address,\n })\n }\n\n /**\n * Get account info\n */\n async getAccount(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getAccount',\n params: address,\n })\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n tronLink?: TronLinkProvider\n tronWeb?: TronWebProvider\n }\n}\n\n/**\n * Initialize and inject the Tron providers\n */\nexport function initTronProvider(targetOrigin = '*'): { tronLink: TronLinkProvider; tronWeb: TronWebProvider } {\n if (typeof window === 'undefined') {\n throw new Error('[TronProvider] Cannot initialize: window is not defined')\n }\n\n if (window.tronLink && window.tronWeb) {\n console.warn('[TronProvider] Providers already exist, returning existing instances')\n return { tronLink: window.tronLink, tronWeb: window.tronWeb }\n }\n\n const tronLink = new TronLinkProvider(targetOrigin)\n const tronWeb = new TronWebProvider(tronLink)\n\n window.tronLink = tronLink\n window.tronWeb = tronWeb\n\n console.log('[TronProvider] Providers initialized')\n return { tronLink, tronWeb }\n}\n","/**\n * Chain ID utilities\n * Maps between KeyApp internal chain IDs and standard chain IDs\n */\n\n/** EVM Chain ID mapping (decimal) */\nexport const EVM_CHAIN_IDS: Record = {\n ethereum: 1,\n binance: 56,\n // Future chains\n // polygon: 137,\n // arbitrum: 42161,\n // optimism: 10,\n} as const\n\n/** Reverse mapping: EVM chainId -> KeyApp chain ID */\nexport const EVM_CHAIN_ID_TO_KEYAPP: Record = Object.fromEntries(\n Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key])\n)\n\n/** API chain name to KeyApp chain ID mapping */\nexport const API_CHAIN_TO_KEYAPP: Record = {\n ETH: 'ethereum',\n BSC: 'binance',\n TRON: 'tron',\n // Lowercase variants\n eth: 'ethereum',\n bsc: 'binance',\n tron: 'tron',\n} as const\n\n/** KeyApp chain ID to display name */\nexport const CHAIN_DISPLAY_NAMES: Record = {\n ethereum: 'Ethereum',\n binance: 'BNB Smart Chain',\n tron: 'Tron',\n bfmeta: 'BFMeta',\n bfchain: 'BFChain',\n} as const\n\n/**\n * Convert decimal chain ID to hex string (EIP-155 format)\n * @example toHexChainId(56) => '0x38'\n */\nexport function toHexChainId(chainId: number): string {\n return `0x${chainId.toString(16)}`\n}\n\n/**\n * Parse hex chain ID to decimal\n * @example parseHexChainId('0x38') => 56\n */\nexport function parseHexChainId(hexChainId: string): number {\n if (!hexChainId.startsWith('0x')) {\n throw new Error(`Invalid hex chain ID: ${hexChainId}`)\n }\n return parseInt(hexChainId, 16)\n}\n\n/**\n * Get KeyApp chain ID from EVM hex chain ID\n * @example getKeyAppChainId('0x38') => 'binance'\n */\nexport function getKeyAppChainId(hexChainId: string): string | null {\n const decimal = parseHexChainId(hexChainId)\n return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null\n}\n\n/**\n * Get EVM hex chain ID from KeyApp chain ID\n * @example getEvmChainId('binance') => '0x38'\n */\nexport function getEvmChainId(keyAppChainId: string): string | null {\n const decimal = EVM_CHAIN_IDS[keyAppChainId]\n return decimal ? toHexChainId(decimal) : null\n}\n\n/**\n * Check if a chain is EVM compatible\n */\nexport function isEvmChain(chainId: string): boolean {\n return chainId in EVM_CHAIN_IDS\n}\n\n/**\n * Normalize API chain name to KeyApp chain ID\n * @example normalizeChainId('BSC') => 'binance'\n */\nexport function normalizeChainId(chainName: string): string {\n return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase()\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects providers for multi-chain dApp support:\n * - `window.bio` - BioChain + KeyApp wallet features\n * - `window.ethereum` - EVM-compatible chains (ETH, BSC)\n * - `window.tronWeb` / `window.tronLink` - Tron chain\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // BioChain operations\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n *\n * // EVM operations (ETH, BSC)\n * const ethAccounts = await window.ethereum.request({ method: 'eth_requestAccounts' })\n *\n * // Tron operations\n * const tronAccounts = await window.tronLink.request({ method: 'tron_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nimport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport * from './chain-id'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\nexport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nexport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\n\n// Extend Window interface (bio is declared in types.ts already for ethereum/tron)\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n/**\n * Initialize all providers (bio, ethereum, tron)\n */\nexport function initAllProviders(targetOrigin = '*'): {\n bio: BioProvider\n ethereum: EthereumProvider\n tronLink: TronLinkProvider\n tronWeb: TronWebProvider\n} {\n const bio = initBioProvider(targetOrigin)\n const ethereum = initEthereumProvider(targetOrigin)\n const { tronLink, tronWeb } = initTronProvider(targetOrigin)\n\n return { bio, ethereum, tronLink, tronWeb }\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n const init = () => {\n initBioProvider()\n initEthereumProvider()\n initTronProvider()\n }\n\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init)\n } else {\n init()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/bio-sdk/dist/index.umd.js b/packages/bio-sdk/dist/index.umd.js index 29a26ff9d..d375b02af 100644 --- a/packages/bio-sdk/dist/index.umd.js +++ b/packages/bio-sdk/dist/index.umd.js @@ -148,6 +148,425 @@ return this.connected; } } + class EthereumProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + connected = false; + currentChainId = null; + accounts = []; + targetOrigin; + // EIP-1193 required properties + isMetaMask = false; + isKeyApp = true; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "eth_response") { + this.handleResponse(data); + } else if (data.type === "eth_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + if (message.event === "connect") { + this.connected = true; + const info = message.args[0]; + this.currentChainId = info?.chainId ?? null; + } else if (message.event === "disconnect") { + this.connected = false; + this.accounts = []; + } else if (message.event === "chainChanged") { + this.currentChainId = message.args[0]; + } else if (message.event === "accountsChanged") { + this.accounts = message.args[0]; + } + } + generateId() { + return `eth_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[EthereumProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * EIP-1193 request method + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "eth_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + /** + * Subscribe to an event + */ + on(event, handler) { + this.events.on(event, handler); + return this; + } + /** + * Unsubscribe from an event + */ + off(event, handler) { + this.events.off(event, handler); + return this; + } + /** + * Alias for off (Node.js EventEmitter compatibility) + */ + removeListener(event, handler) { + return this.off(event, handler); + } + /** + * Add listener that fires only once + */ + once(event, handler) { + const wrapper = (...args) => { + this.off(event, wrapper); + handler(...args); + }; + this.on(event, wrapper); + return this; + } + /** + * EIP-1193 isConnected method + */ + isConnected() { + return this.connected; + } + /** + * Get current chain ID (cached) + */ + get chainId() { + return this.currentChainId; + } + /** + * Get selected address (first account) + */ + get selectedAddress() { + return this.accounts[0] ?? null; + } + // ============================================ + // Legacy methods (for backwards compatibility) + // ============================================ + /** + * @deprecated Use request({ method: 'eth_requestAccounts' }) + */ + async enable() { + return this.request({ method: "eth_requestAccounts" }); + } + /** + * @deprecated Use request() + */ + send(method, params) { + return this.request({ method, params }); + } + /** + * @deprecated Use request() + */ + sendAsync(payload, callback) { + this.request({ method: payload.method, params: payload.params }).then((result) => callback(null, { result })).catch((error) => callback(error)); + } + } + function initEthereumProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[EthereumProvider] Cannot initialize: window is not defined"); + } + if (window.ethereum) { + console.warn("[EthereumProvider] Provider already exists, returning existing instance"); + return window.ethereum; + } + const provider = new EthereumProvider(targetOrigin); + window.ethereum = provider; + console.log("[EthereumProvider] Provider initialized"); + return provider; + } + class TronLinkProvider { + events = new EventEmitter(); + pendingRequests = /* @__PURE__ */ new Map(); + requestIdCounter = 0; + targetOrigin; + constructor(targetOrigin = "*") { + this.targetOrigin = targetOrigin; + this.setupMessageListener(); + } + setupMessageListener() { + window.addEventListener("message", this.handleMessage.bind(this)); + } + handleMessage(event) { + const data = event.data; + if (!data || typeof data !== "object") return; + if (data.type === "tron_response") { + this.handleResponse(data); + } else if (data.type === "tron_event") { + this.handleEvent(data); + } + } + handleResponse(message) { + const pending = this.pendingRequests.get(message.id); + if (!pending) return; + this.pendingRequests.delete(message.id); + if (message.success) { + pending.resolve(message.result); + } else { + const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: "Unknown error" }; + pending.reject(createProviderError(error.code, error.message, error.data)); + } + } + handleEvent(message) { + this.events.emit(message.event, ...message.args); + } + generateId() { + return `tron_${Date.now()}_${++this.requestIdCounter}`; + } + postMessage(message) { + if (window.parent === window) { + console.warn("[TronLinkProvider] Not running in iframe, cannot communicate with host"); + return; + } + window.parent.postMessage(message, this.targetOrigin); + } + /** + * TronLink request method (EIP-1193 style) + */ + async request(args) { + const { method, params } = args; + const paramsArray = Array.isArray(params) ? params : params !== void 0 ? [params] : []; + const id = this.generateId(); + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { + resolve, + reject + }); + this.postMessage({ + type: "tron_request", + id, + method, + params: paramsArray + }); + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, "Request timeout")); + } + }, 5 * 60 * 1e3); + }); + } + on(event, handler) { + this.events.on(event, handler); + return this; + } + off(event, handler) { + this.events.off(event, handler); + return this; + } + } + class TronWebProvider { + tronLink; + _ready = false; + _defaultAddress = { base58: "", hex: "" }; + /** TRX operations */ + trx; + constructor(tronLink) { + this.tronLink = tronLink; + this.trx = new TronWebTrx(tronLink); + tronLink.on("accountsChanged", (accounts) => { + if (Array.isArray(accounts) && accounts.length > 0) { + const addr = accounts[0]; + this._defaultAddress = addr; + this._ready = true; + } else { + this._defaultAddress = { base58: "", hex: "" }; + this._ready = false; + } + }); + } + /** Whether TronWeb is ready (connected) */ + get ready() { + return this._ready; + } + /** Current default address */ + get defaultAddress() { + return this._defaultAddress; + } + /** + * Set default address (called by host after connection) + */ + setAddress(address) { + this._defaultAddress = address; + this._ready = true; + } + /** + * Check if an address is valid + */ + isAddress(address) { + if (address.startsWith("T")) { + return address.length === 34; + } + if (address.startsWith("41")) { + return address.length === 42; + } + return false; + } + /** + * Convert address to hex format + */ + address = { + toHex: (base58) => { + return base58; + }, + fromHex: (hex) => { + return hex; + } + }; + } + class TronWebTrx { + tronLink; + constructor(tronLink) { + this.tronLink = tronLink; + } + /** + * Sign a transaction + */ + async sign(transaction) { + return this.tronLink.request({ + method: "tron_signTransaction", + params: transaction + }); + } + /** + * Send raw transaction (broadcast) + */ + async sendRawTransaction(signedTransaction) { + return this.tronLink.request({ + method: "tron_sendRawTransaction", + params: signedTransaction + }); + } + /** + * Get account balance + */ + async getBalance(address) { + return this.tronLink.request({ + method: "tron_getBalance", + params: address + }); + } + /** + * Get account info + */ + async getAccount(address) { + return this.tronLink.request({ + method: "tron_getAccount", + params: address + }); + } + } + function initTronProvider(targetOrigin = "*") { + if (typeof window === "undefined") { + throw new Error("[TronProvider] Cannot initialize: window is not defined"); + } + if (window.tronLink && window.tronWeb) { + console.warn("[TronProvider] Providers already exist, returning existing instances"); + return { tronLink: window.tronLink, tronWeb: window.tronWeb }; + } + const tronLink = new TronLinkProvider(targetOrigin); + const tronWeb = new TronWebProvider(tronLink); + window.tronLink = tronLink; + window.tronWeb = tronWeb; + console.log("[TronProvider] Providers initialized"); + return { tronLink, tronWeb }; + } + const EVM_CHAIN_IDS = { + ethereum: 1, + binance: 56 + // Future chains + // polygon: 137, + // arbitrum: 42161, + // optimism: 10, + }; + const EVM_CHAIN_ID_TO_KEYAPP = Object.fromEntries( + Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key]) + ); + const API_CHAIN_TO_KEYAPP = { + ETH: "ethereum", + BSC: "binance", + TRON: "tron", + // Lowercase variants + eth: "ethereum", + bsc: "binance", + tron: "tron" + }; + const CHAIN_DISPLAY_NAMES = { + ethereum: "Ethereum", + binance: "BNB Smart Chain", + tron: "Tron", + bfmeta: "BFMeta", + bfchain: "BFChain" + }; + function toHexChainId(chainId) { + return `0x${chainId.toString(16)}`; + } + function parseHexChainId(hexChainId) { + if (!hexChainId.startsWith("0x")) { + throw new Error(`Invalid hex chain ID: ${hexChainId}`); + } + return parseInt(hexChainId, 16); + } + function getKeyAppChainId(hexChainId) { + const decimal = parseHexChainId(hexChainId); + return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null; + } + function getEvmChainId(keyAppChainId) { + const decimal = EVM_CHAIN_IDS[keyAppChainId]; + return decimal ? toHexChainId(decimal) : null; + } + function isEvmChain(chainId) { + return chainId in EVM_CHAIN_IDS; + } + function normalizeChainId(chainName) { + return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase(); + } function initBioProvider(targetOrigin = "*") { if (typeof window === "undefined") { throw new Error("[BioSDK] Cannot initialize: window is not defined"); @@ -161,18 +580,45 @@ console.log("[BioSDK] Provider initialized"); return provider; } + function initAllProviders(targetOrigin = "*") { + const bio = initBioProvider(targetOrigin); + const ethereum = initEthereumProvider(targetOrigin); + const { tronLink, tronWeb } = initTronProvider(targetOrigin); + return { bio, ethereum, tronLink, tronWeb }; + } if (typeof window !== "undefined") { + const init = () => { + initBioProvider(); + initEthereumProvider(); + initTronProvider(); + }; if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => initBioProvider()); + document.addEventListener("DOMContentLoaded", init); } else { - initBioProvider(); + init(); } } + exports2.API_CHAIN_TO_KEYAPP = API_CHAIN_TO_KEYAPP; exports2.BioErrorCodes = BioErrorCodes; exports2.BioProviderImpl = BioProviderImpl; + exports2.CHAIN_DISPLAY_NAMES = CHAIN_DISPLAY_NAMES; + exports2.EVM_CHAIN_IDS = EVM_CHAIN_IDS; + exports2.EVM_CHAIN_ID_TO_KEYAPP = EVM_CHAIN_ID_TO_KEYAPP; + exports2.EthereumProvider = EthereumProvider; exports2.EventEmitter = EventEmitter; + exports2.TronLinkProvider = TronLinkProvider; + exports2.TronWebProvider = TronWebProvider; exports2.createProviderError = createProviderError; + exports2.getEvmChainId = getEvmChainId; + exports2.getKeyAppChainId = getKeyAppChainId; + exports2.initAllProviders = initAllProviders; exports2.initBioProvider = initBioProvider; + exports2.initEthereumProvider = initEthereumProvider; + exports2.initTronProvider = initTronProvider; + exports2.isEvmChain = isEvmChain; + exports2.normalizeChainId = normalizeChainId; + exports2.parseHexChainId = parseHexChainId; + exports2.toHexChainId = toHexChainId; Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" }); })); //# sourceMappingURL=index.umd.js.map diff --git a/packages/bio-sdk/dist/index.umd.js.map b/packages/bio-sdk/dist/index.umd.js.map index 19761307f..f3ad67750 100644 --- a/packages/bio-sdk/dist/index.umd.js.map +++ b/packages/bio-sdk/dist/index.umd.js.map @@ -1 +1 @@ -{"version":3,"file":"index.umd.js","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects `window.bio` provider similar to `window.ethereum` in Web3 DApps.\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // Now window.bio is available\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\n\n// Extend Window interface\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => initBioProvider())\n } else {\n initBioProvider()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.umd.js","sources":["../src/types.ts","../src/events.ts","../src/provider.ts","../src/ethereum-provider.ts","../src/tron-provider.ts","../src/chain-id.ts","../src/index.ts"],"sourcesContent":["/**\n * Bio SDK Types\n * EIP-1193 style provider interface for Bio ecosystem\n */\n\n/** Account information */\nexport interface BioAccount {\n address: string\n chain: string\n name?: string\n /** Public key (hex encoded) */\n publicKey: string\n}\n\n/** Transfer parameters */\nexport interface TransferParams {\n from: string\n to: string\n amount: string\n chain: string\n asset?: string\n}\n\n/** Unsigned transaction payload (chain-specific) */\nexport interface BioUnsignedTransaction {\n chainId: string\n data: unknown\n}\n\n/** Signed transaction payload (chain-specific) */\nexport interface BioSignedTransaction {\n chainId: string\n data: unknown\n signature: string\n}\n\n/** Provider request arguments */\nexport interface RequestArguments {\n method: string\n params?: unknown[]\n}\n\n/** Provider RPC error */\nexport interface ProviderRpcError extends Error {\n code: number\n data?: unknown\n}\n\n/** Event handler type */\nexport type EventHandler = (...args: T[]) => void\n\n/**\n * Bio Provider Interface (EIP-1193 style)\n */\nexport interface BioProvider {\n /** Make a request to the provider */\n request(args: RequestArguments): Promise\n\n /** Subscribe to an event */\n on(event: string, handler: EventHandler): void\n\n /** Unsubscribe from an event */\n off(event: string, handler: EventHandler): void\n\n /** Check if connected */\n isConnected(): boolean\n}\n\n/**\n * Bio method definitions\n */\nexport interface BioMethods {\n /** Request wallet accounts (shows connection UI) */\n bio_requestAccounts: () => Promise\n\n /** Get connected accounts (no UI) */\n bio_accounts: () => Promise\n\n /** Select an account (shows account picker UI) */\n bio_selectAccount: (opts?: { chain?: string }) => Promise\n\n /** Pick another wallet address (shows wallet picker UI) */\n bio_pickWallet: (opts?: { chain?: string; exclude?: string }) => Promise\n\n /** Sign a message, returns signature and public key (hex) */\n bio_signMessage: (params: { message: string; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Sign typed data, returns signature and public key (hex) */\n bio_signTypedData: (params: { data: object; address: string }) => Promise<{ signature: string; publicKey: string }>\n\n /** Create an unsigned transaction (no signature, no broadcast) */\n bio_createTransaction: (params: TransferParams) => Promise\n\n /** Sign an unsigned transaction (requires user confirmation) */\n bio_signTransaction: (params: { from: string; chain: string; unsignedTx: BioUnsignedTransaction }) => Promise\n\n /** Send a transaction */\n bio_sendTransaction: (params: TransferParams) => Promise<{ txHash: string }>\n\n /** Get current chain ID */\n bio_chainId: () => Promise\n\n /** Get balance */\n bio_getBalance: (params: { address: string; chain: string }) => Promise\n\n /** Close splash screen (indicates app is ready) */\n bio_closeSplashScreen: () => Promise\n}\n\n/**\n * Bio event definitions\n */\nexport interface BioEvents {\n /** Emitted when accounts change */\n accountsChanged: (accounts: BioAccount[]) => void\n\n /** Emitted when chain changes */\n chainChanged: (chainId: string) => void\n\n /** Emitted when connected */\n connect: (info: { chainId: string }) => void\n\n /** Emitted when disconnected */\n disconnect: (error: { code: number; message: string }) => void\n}\n\n/** Method names */\nexport type BioMethodName = keyof BioMethods\n\n/** Event names */\nexport type BioEventName = keyof BioEvents\n\n/** RPC error codes */\nexport const BioErrorCodes = {\n USER_REJECTED: 4001,\n UNAUTHORIZED: 4100,\n UNSUPPORTED_METHOD: 4200,\n DISCONNECTED: 4900,\n CHAIN_DISCONNECTED: 4901,\n INTERNAL_ERROR: -32603,\n INVALID_PARAMS: -32602,\n METHOD_NOT_FOUND: -32601,\n} as const\n\n/** Create a provider RPC error */\nexport function createProviderError(code: number, message: string, data?: unknown): ProviderRpcError {\n const error = new Error(message) as ProviderRpcError\n error.code = code\n error.data = data\n return error\n}\n","/**\n * Event emitter for Bio SDK\n */\n\nimport type { EventHandler } from './types'\n\nexport class EventEmitter {\n private handlers = new Map>()\n\n on(event: string, handler: EventHandler): void {\n let handlers = this.handlers.get(event)\n if (!handlers) {\n handlers = new Set()\n this.handlers.set(event, handlers)\n }\n handlers.add(handler)\n }\n\n off(event: string, handler: EventHandler): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this.handlers.delete(event)\n }\n }\n }\n\n emit(event: string, ...args: unknown[]): void {\n const handlers = this.handlers.get(event)\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args)\n } catch (error) {\n console.error(`[BioSDK] Error in event handler for \"${event}\":`, error)\n }\n })\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.handlers.delete(event)\n } else {\n this.handlers.clear()\n }\n }\n}\n","/**\n * Bio Provider Implementation\n * Communicates with KeyApp host via postMessage\n */\n\nimport type { BioProvider, RequestArguments, EventHandler } from './types'\nimport { BioErrorCodes, createProviderError } from './types'\nimport { EventEmitter } from './events'\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'bio_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'bio_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'bio_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\nexport class BioProviderImpl implements BioProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n this.connect()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'bio_response') {\n this.handleResponse(data)\n } else if (data.type === 'bio_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n } else if (message.event === 'disconnect') {\n this.connected = false\n }\n }\n\n private connect(): void {\n // Send handshake to host\n this.postMessage({\n type: 'bio_request',\n id: this.generateId(),\n method: 'bio_connect',\n params: [],\n })\n }\n\n private generateId(): string {\n return `bio_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[BioSDK] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n async request(args: RequestArguments): Promise {\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'bio_request',\n id,\n method: args.method,\n params: args.params,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: EventHandler): void {\n this.events.on(event, handler)\n }\n\n off(event: string, handler: EventHandler): void {\n this.events.off(event, handler)\n }\n\n isConnected(): boolean {\n return this.connected\n }\n}\n","/**\n * Ethereum Provider (EIP-1193 Compatible)\n *\n * Provides window.ethereum for EVM-compatible dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError, type ProviderRpcError } from './types'\nimport { toHexChainId, parseHexChainId, getKeyAppChainId, EVM_CHAIN_IDS } from './chain-id'\n\n/** EIP-1193 Request Arguments */\nexport interface EthRequestArguments {\n method: string\n params?: unknown[] | Record\n}\n\n/** EIP-1193 Provider Connect Info */\nexport interface ProviderConnectInfo {\n chainId: string\n}\n\n/** EIP-1193 Provider Message */\nexport interface ProviderMessage {\n type: string\n data: unknown\n}\n\n/** Transaction request (eth_sendTransaction) */\nexport interface TransactionRequest {\n from: string\n to?: string\n value?: string\n data?: string\n gas?: string\n gasPrice?: string\n maxFeePerGas?: string\n maxPriorityFeePerGas?: string\n nonce?: string\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'eth_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'eth_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'eth_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * EIP-1193 Ethereum Provider Implementation\n */\nexport class EthereumProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private connected = false\n private currentChainId: string | null = null\n private accounts: string[] = []\n private readonly targetOrigin: string\n\n // EIP-1193 required properties\n readonly isMetaMask = false\n readonly isKeyApp = true\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'eth_response') {\n this.handleResponse(data)\n } else if (data.type === 'eth_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n\n // Handle built-in events\n if (message.event === 'connect') {\n this.connected = true\n const info = message.args[0] as ProviderConnectInfo\n this.currentChainId = info?.chainId ?? null\n } else if (message.event === 'disconnect') {\n this.connected = false\n this.accounts = []\n } else if (message.event === 'chainChanged') {\n this.currentChainId = message.args[0] as string\n } else if (message.event === 'accountsChanged') {\n this.accounts = message.args[0] as string[]\n }\n }\n\n private generateId(): string {\n return `eth_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[EthereumProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * EIP-1193 request method\n */\n async request(args: EthRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'eth_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes (for user interactions)\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n /**\n * Subscribe to an event\n */\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n /**\n * Unsubscribe from an event\n */\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n\n /**\n * Alias for off (Node.js EventEmitter compatibility)\n */\n removeListener(event: string, handler: (...args: unknown[]) => void): this {\n return this.off(event, handler)\n }\n\n /**\n * Add listener that fires only once\n */\n once(event: string, handler: (...args: unknown[]) => void): this {\n const wrapper = (...args: unknown[]) => {\n this.off(event, wrapper)\n handler(...args)\n }\n this.on(event, wrapper)\n return this\n }\n\n /**\n * EIP-1193 isConnected method\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * Get current chain ID (cached)\n */\n get chainId(): string | null {\n return this.currentChainId\n }\n\n /**\n * Get selected address (first account)\n */\n get selectedAddress(): string | null {\n return this.accounts[0] ?? null\n }\n\n // ============================================\n // Legacy methods (for backwards compatibility)\n // ============================================\n\n /**\n * @deprecated Use request({ method: 'eth_requestAccounts' })\n */\n async enable(): Promise {\n return this.request({ method: 'eth_requestAccounts' })\n }\n\n /**\n * @deprecated Use request()\n */\n send(method: string, params?: unknown[]): Promise {\n return this.request({ method, params })\n }\n\n /**\n * @deprecated Use request()\n */\n sendAsync(\n payload: { method: string; params?: unknown[]; id?: number },\n callback: (error: Error | null, result?: { result: unknown }) => void\n ): void {\n this.request({ method: payload.method, params: payload.params })\n .then((result) => callback(null, { result }))\n .catch((error) => callback(error))\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n ethereum?: EthereumProvider\n }\n}\n\n/**\n * Initialize and inject the Ethereum provider into window.ethereum\n */\nexport function initEthereumProvider(targetOrigin = '*'): EthereumProvider {\n if (typeof window === 'undefined') {\n throw new Error('[EthereumProvider] Cannot initialize: window is not defined')\n }\n\n if (window.ethereum) {\n console.warn('[EthereumProvider] Provider already exists, returning existing instance')\n return window.ethereum\n }\n\n const provider = new EthereumProvider(targetOrigin)\n window.ethereum = provider\n\n console.log('[EthereumProvider] Provider initialized')\n return provider\n}\n","/**\n * Tron Provider (TronLink Compatible)\n *\n * Provides window.tronWeb and window.tronLink for Tron dApps.\n * Communicates with KeyApp host via postMessage.\n */\n\nimport { EventEmitter } from './events'\nimport { BioErrorCodes, createProviderError } from './types'\n\n/** Tron address format */\nexport interface TronAddress {\n base58: string\n hex: string\n}\n\n/** TronLink request arguments (EIP-1193 style) */\nexport interface TronRequestArguments {\n method: string\n params?: unknown\n}\n\n/** Message sent to host */\ninterface RequestMessage {\n type: 'tron_request'\n id: string\n method: string\n params?: unknown[]\n}\n\n/** Response from host */\ninterface ResponseMessage {\n type: 'tron_response'\n id: string\n success: boolean\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n/** Event from host */\ninterface EventMessage {\n type: 'tron_event'\n event: string\n args: unknown[]\n}\n\ntype HostMessage = ResponseMessage | EventMessage\n\n/**\n * TronLink-compatible Provider\n */\nexport class TronLinkProvider {\n private events = new EventEmitter()\n private pendingRequests = new Map void\n reject: (error: Error) => void\n }>()\n private requestIdCounter = 0\n private readonly targetOrigin: string\n\n constructor(targetOrigin = '*') {\n this.targetOrigin = targetOrigin\n this.setupMessageListener()\n }\n\n private setupMessageListener(): void {\n window.addEventListener('message', this.handleMessage.bind(this))\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = event.data as HostMessage\n if (!data || typeof data !== 'object') return\n\n if (data.type === 'tron_response') {\n this.handleResponse(data)\n } else if (data.type === 'tron_event') {\n this.handleEvent(data)\n }\n }\n\n private handleResponse(message: ResponseMessage): void {\n const pending = this.pendingRequests.get(message.id)\n if (!pending) return\n\n this.pendingRequests.delete(message.id)\n\n if (message.success) {\n pending.resolve(message.result)\n } else {\n const error = message.error ?? { code: BioErrorCodes.INTERNAL_ERROR, message: 'Unknown error' }\n pending.reject(createProviderError(error.code, error.message, error.data))\n }\n }\n\n private handleEvent(message: EventMessage): void {\n this.events.emit(message.event, ...message.args)\n }\n\n private generateId(): string {\n return `tron_${Date.now()}_${++this.requestIdCounter}`\n }\n\n private postMessage(message: RequestMessage): void {\n if (window.parent === window) {\n console.warn('[TronLinkProvider] Not running in iframe, cannot communicate with host')\n return\n }\n window.parent.postMessage(message, this.targetOrigin)\n }\n\n /**\n * TronLink request method (EIP-1193 style)\n */\n async request(args: TronRequestArguments): Promise {\n const { method, params } = args\n const paramsArray = Array.isArray(params) ? params : params !== undefined ? [params] : []\n\n const id = this.generateId()\n\n return new Promise((resolve, reject) => {\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject,\n })\n\n this.postMessage({\n type: 'tron_request',\n id,\n method,\n params: paramsArray,\n })\n\n // Timeout after 5 minutes\n setTimeout(() => {\n if (this.pendingRequests.has(id)) {\n this.pendingRequests.delete(id)\n reject(createProviderError(BioErrorCodes.INTERNAL_ERROR, 'Request timeout'))\n }\n }, 5 * 60 * 1000)\n })\n }\n\n on(event: string, handler: (...args: unknown[]) => void): this {\n this.events.on(event, handler)\n return this\n }\n\n off(event: string, handler: (...args: unknown[]) => void): this {\n this.events.off(event, handler)\n return this\n }\n}\n\n/**\n * TronWeb-compatible API\n * Provides the subset of TronWeb API that KeyApp supports\n */\nexport class TronWebProvider {\n private tronLink: TronLinkProvider\n private _ready = false\n private _defaultAddress: TronAddress = { base58: '', hex: '' }\n\n /** TRX operations */\n readonly trx: TronWebTrx\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n this.trx = new TronWebTrx(tronLink)\n\n // Listen for account changes\n tronLink.on('accountsChanged', (accounts: unknown) => {\n if (Array.isArray(accounts) && accounts.length > 0) {\n const addr = accounts[0] as TronAddress\n this._defaultAddress = addr\n this._ready = true\n } else {\n this._defaultAddress = { base58: '', hex: '' }\n this._ready = false\n }\n })\n }\n\n /** Whether TronWeb is ready (connected) */\n get ready(): boolean {\n return this._ready\n }\n\n /** Current default address */\n get defaultAddress(): TronAddress {\n return this._defaultAddress\n }\n\n /**\n * Set default address (called by host after connection)\n */\n setAddress(address: TronAddress): void {\n this._defaultAddress = address\n this._ready = true\n }\n\n /**\n * Check if an address is valid\n */\n isAddress(address: string): boolean {\n // Basic validation: base58 starts with T, hex starts with 41\n if (address.startsWith('T')) {\n return address.length === 34\n }\n if (address.startsWith('41')) {\n return address.length === 42\n }\n return false\n }\n\n /**\n * Convert address to hex format\n */\n address = {\n toHex: (base58: string): string => {\n // This is a stub - actual conversion requires TronWeb library\n // KeyApp will handle the conversion on the host side\n return base58\n },\n fromHex: (hex: string): string => {\n return hex\n },\n }\n}\n\n/**\n * TronWeb.trx operations\n */\nclass TronWebTrx {\n private tronLink: TronLinkProvider\n\n constructor(tronLink: TronLinkProvider) {\n this.tronLink = tronLink\n }\n\n /**\n * Sign a transaction\n */\n async sign(transaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_signTransaction',\n params: transaction,\n })\n }\n\n /**\n * Send raw transaction (broadcast)\n */\n async sendRawTransaction(signedTransaction: unknown): Promise {\n return this.tronLink.request({\n method: 'tron_sendRawTransaction',\n params: signedTransaction,\n })\n }\n\n /**\n * Get account balance\n */\n async getBalance(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getBalance',\n params: address,\n })\n }\n\n /**\n * Get account info\n */\n async getAccount(address: string): Promise {\n return this.tronLink.request({\n method: 'tron_getAccount',\n params: address,\n })\n }\n}\n\n// Extend Window interface\ndeclare global {\n interface Window {\n tronLink?: TronLinkProvider\n tronWeb?: TronWebProvider\n }\n}\n\n/**\n * Initialize and inject the Tron providers\n */\nexport function initTronProvider(targetOrigin = '*'): { tronLink: TronLinkProvider; tronWeb: TronWebProvider } {\n if (typeof window === 'undefined') {\n throw new Error('[TronProvider] Cannot initialize: window is not defined')\n }\n\n if (window.tronLink && window.tronWeb) {\n console.warn('[TronProvider] Providers already exist, returning existing instances')\n return { tronLink: window.tronLink, tronWeb: window.tronWeb }\n }\n\n const tronLink = new TronLinkProvider(targetOrigin)\n const tronWeb = new TronWebProvider(tronLink)\n\n window.tronLink = tronLink\n window.tronWeb = tronWeb\n\n console.log('[TronProvider] Providers initialized')\n return { tronLink, tronWeb }\n}\n","/**\n * Chain ID utilities\n * Maps between KeyApp internal chain IDs and standard chain IDs\n */\n\n/** EVM Chain ID mapping (decimal) */\nexport const EVM_CHAIN_IDS: Record = {\n ethereum: 1,\n binance: 56,\n // Future chains\n // polygon: 137,\n // arbitrum: 42161,\n // optimism: 10,\n} as const\n\n/** Reverse mapping: EVM chainId -> KeyApp chain ID */\nexport const EVM_CHAIN_ID_TO_KEYAPP: Record = Object.fromEntries(\n Object.entries(EVM_CHAIN_IDS).map(([key, value]) => [value, key])\n)\n\n/** API chain name to KeyApp chain ID mapping */\nexport const API_CHAIN_TO_KEYAPP: Record = {\n ETH: 'ethereum',\n BSC: 'binance',\n TRON: 'tron',\n // Lowercase variants\n eth: 'ethereum',\n bsc: 'binance',\n tron: 'tron',\n} as const\n\n/** KeyApp chain ID to display name */\nexport const CHAIN_DISPLAY_NAMES: Record = {\n ethereum: 'Ethereum',\n binance: 'BNB Smart Chain',\n tron: 'Tron',\n bfmeta: 'BFMeta',\n bfchain: 'BFChain',\n} as const\n\n/**\n * Convert decimal chain ID to hex string (EIP-155 format)\n * @example toHexChainId(56) => '0x38'\n */\nexport function toHexChainId(chainId: number): string {\n return `0x${chainId.toString(16)}`\n}\n\n/**\n * Parse hex chain ID to decimal\n * @example parseHexChainId('0x38') => 56\n */\nexport function parseHexChainId(hexChainId: string): number {\n if (!hexChainId.startsWith('0x')) {\n throw new Error(`Invalid hex chain ID: ${hexChainId}`)\n }\n return parseInt(hexChainId, 16)\n}\n\n/**\n * Get KeyApp chain ID from EVM hex chain ID\n * @example getKeyAppChainId('0x38') => 'binance'\n */\nexport function getKeyAppChainId(hexChainId: string): string | null {\n const decimal = parseHexChainId(hexChainId)\n return EVM_CHAIN_ID_TO_KEYAPP[decimal] ?? null\n}\n\n/**\n * Get EVM hex chain ID from KeyApp chain ID\n * @example getEvmChainId('binance') => '0x38'\n */\nexport function getEvmChainId(keyAppChainId: string): string | null {\n const decimal = EVM_CHAIN_IDS[keyAppChainId]\n return decimal ? toHexChainId(decimal) : null\n}\n\n/**\n * Check if a chain is EVM compatible\n */\nexport function isEvmChain(chainId: string): boolean {\n return chainId in EVM_CHAIN_IDS\n}\n\n/**\n * Normalize API chain name to KeyApp chain ID\n * @example normalizeChainId('BSC') => 'binance'\n */\nexport function normalizeChainId(chainName: string): string {\n return API_CHAIN_TO_KEYAPP[chainName] ?? chainName.toLowerCase()\n}\n","/**\n * Bio SDK - Client SDK for Bio Ecosystem MiniApps\n *\n * Injects providers for multi-chain dApp support:\n * - `window.bio` - BioChain + KeyApp wallet features\n * - `window.ethereum` - EVM-compatible chains (ETH, BSC)\n * - `window.tronWeb` / `window.tronLink` - Tron chain\n *\n * @example\n * ```typescript\n * import '@biochain/bio-sdk'\n *\n * // BioChain operations\n * const accounts = await window.bio.request({ method: 'bio_requestAccounts' })\n *\n * // EVM operations (ETH, BSC)\n * const ethAccounts = await window.ethereum.request({ method: 'eth_requestAccounts' })\n *\n * // Tron operations\n * const tronAccounts = await window.tronLink.request({ method: 'tron_requestAccounts' })\n * ```\n */\n\nimport { BioProviderImpl } from './provider'\nimport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nimport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\nimport type { BioProvider } from './types'\n\n// Re-export types\nexport * from './types'\nexport * from './chain-id'\nexport { EventEmitter } from './events'\nexport { BioProviderImpl } from './provider'\nexport { EthereumProvider, initEthereumProvider } from './ethereum-provider'\nexport { TronLinkProvider, TronWebProvider, initTronProvider } from './tron-provider'\n\n// Extend Window interface (bio is declared in types.ts already for ethereum/tron)\ndeclare global {\n interface Window {\n bio?: BioProvider\n }\n}\n\n/**\n * Initialize and inject the Bio provider into window.bio\n */\nexport function initBioProvider(targetOrigin = '*'): BioProvider {\n if (typeof window === 'undefined') {\n throw new Error('[BioSDK] Cannot initialize: window is not defined')\n }\n\n if (window.bio) {\n console.warn('[BioSDK] Provider already exists, returning existing instance')\n return window.bio\n }\n\n const provider = new BioProviderImpl(targetOrigin)\n window.bio = provider\n\n console.log('[BioSDK] Provider initialized')\n return provider\n}\n\n/**\n * Initialize all providers (bio, ethereum, tron)\n */\nexport function initAllProviders(targetOrigin = '*'): {\n bio: BioProvider\n ethereum: EthereumProvider\n tronLink: TronLinkProvider\n tronWeb: TronWebProvider\n} {\n const bio = initBioProvider(targetOrigin)\n const ethereum = initEthereumProvider(targetOrigin)\n const { tronLink, tronWeb } = initTronProvider(targetOrigin)\n\n return { bio, ethereum, tronLink, tronWeb }\n}\n\n// Auto-initialize if running in browser\nif (typeof window !== 'undefined') {\n const init = () => {\n initBioProvider()\n initEthereumProvider()\n initTronProvider()\n }\n\n // Use a slight delay to ensure DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init)\n } else {\n init()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/services/ecosystem/handlers/context.ts b/src/services/ecosystem/handlers/context.ts index 4ecfb205a..0e602d547 100644 --- a/src/services/ecosystem/handlers/context.ts +++ b/src/services/ecosystem/handlers/context.ts @@ -83,12 +83,12 @@ export interface HandlerCallbacks { showSignTransactionDialog: (params: SignTransactionParams) => Promise // EVM (Ethereum/BSC) callbacks - showEvmWalletPicker?: (opts: { chainId: string }) => Promise + showEvmWalletPicker?: (opts: { chainId: string; app?: MiniappInfo }) => Promise showEvmSigningDialog?: (params: EvmSigningParams) => Promise<{ signature: string } | null> showEvmTransactionDialog?: (params: EvmTransactionParams) => Promise<{ txHash: string } | null> // TRON callbacks - showTronWalletPicker?: () => Promise + showTronWalletPicker?: (opts?: { app?: MiniappInfo }) => Promise showTronSigningDialog?: (params: TronSigningParams) => Promise<{ signedTransaction: TronTransaction } | null> } diff --git a/src/services/ecosystem/handlers/evm.ts b/src/services/ecosystem/handlers/evm.ts index 355417474..6a75619d9 100644 --- a/src/services/ecosystem/handlers/evm.ts +++ b/src/services/ecosystem/handlers/evm.ts @@ -16,7 +16,7 @@ import { EVM_CHAIN_IDS, } from '@biochain/bio-sdk' -import type { EvmTransactionRequest } from './context' +import type { EvmTransactionRequest, MiniappInfo } from './context' // Re-export for convenience export type { EvmTransactionRequest } from './context' @@ -59,7 +59,7 @@ const appAccountState = new Map() // Callback setters (for UI integration) // ============================================ -let _showEvmWalletPicker: ((opts: { chainId: string }) => Promise) | null = null +let _showEvmWalletPicker: ((opts: { chainId: string; app?: MiniappInfo }) => Promise) | null = null let _showChainSwitchConfirm: ((opts: { fromChainId: string; toChainId: string; appName: string }) => Promise) | null = null let _showEvmSigningDialog: ((opts: { message: string; address: string; appName: string }) => Promise<{ signature: string } | null>) | null = null let _showEvmTransactionDialog: ((opts: { tx: EvmTransactionRequest; appName: string }) => Promise<{ txHash: string } | null>) | null = null @@ -133,7 +133,7 @@ export const handleEthRequestAccounts: MethodHandler = async (_params, context) } const chainId = getCurrentChainId(context.appId) - const wallet = await showWalletPicker({ chainId }) + const wallet = await showWalletPicker({ chainId, app: { name: context.appName } }) if (!wallet) { throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) } diff --git a/src/services/ecosystem/handlers/tron.ts b/src/services/ecosystem/handlers/tron.ts index f7fc5c009..e910b1564 100644 --- a/src/services/ecosystem/handlers/tron.ts +++ b/src/services/ecosystem/handlers/tron.ts @@ -7,7 +7,7 @@ import type { MethodHandler, BioAccount } from '../types' import { BioErrorCodes } from '../types' -import { HandlerContext, type TronTransaction } from './context' +import { HandlerContext, type TronTransaction, type MiniappInfo } from './context' // Re-export for convenience export type { TronTransaction } from './context' @@ -33,7 +33,7 @@ const appAddressState = new Map() // Callback setters (for UI integration) // ============================================ -let _showTronWalletPicker: (() => Promise) | null = null +let _showTronWalletPicker: ((opts?: { app?: MiniappInfo }) => Promise) | null = null let _showTronSigningDialog: ((opts: { transaction: TronTransaction; appName: string }) => Promise<{ signedTransaction: TronTransaction } | null>) | null = null export function setTronWalletPicker(picker: typeof _showTronWalletPicker): void { @@ -90,7 +90,7 @@ export const handleTronRequestAccounts: MethodHandler = async (_params, context) throw Object.assign(new Error('Wallet picker not available'), { code: BioErrorCodes.INTERNAL_ERROR }) } - const wallet = await showWalletPicker() + const wallet = await showWalletPicker({ app: { name: context.appName } }) if (!wallet) { throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED }) } diff --git a/src/stackflow/activities/MainTabsActivity.tsx b/src/stackflow/activities/MainTabsActivity.tsx index a60dbd5c2..e3f687791 100644 --- a/src/stackflow/activities/MainTabsActivity.tsx +++ b/src/stackflow/activities/MainTabsActivity.tsx @@ -11,12 +11,19 @@ import type { BioAccount, BioSignedTransaction, TransferParams } from "@/service import { getBridge, initBioProvider, + setChainSwitchConfirm, + setEvmSigningDialog, + setEvmTransactionDialog, + setEvmWalletPicker, setGetAccounts, setSigningDialog, setSignTransactionDialog, + setTronWalletPicker, setTransferDialog, setWalletPicker, } from "@/services/ecosystem"; +import { getKeyAppChainId } from "@biochain/bio-sdk"; +import { formatUnits } from "viem"; import { walletSelectors, walletStore, type ChainAddress } from "@/stores"; import { miniappRuntimeStore } from "@/services/miniapp-runtime"; @@ -182,9 +189,160 @@ export const MainTabsActivity: ActivityComponentType = ({ params }); }); + setEvmWalletPicker(async (opts) => { + const appName = opts.app?.name; + + return new Promise((resolve) => { + const timeout = window.setTimeout(() => resolve(null), 30_000); + + const cleanup = () => { + window.clearTimeout(timeout); + window.removeEventListener("wallet-picker-select", handleSelect); + window.removeEventListener("wallet-picker-cancel", handleCancel); + }; + + const handleSelect = (e: Event) => { + cleanup(); + const detail = (e as CustomEvent).detail as { address: string; chain: string; name?: string }; + resolve({ address: detail.address, chain: detail.chain, name: detail.name }); + }; + + const handleCancel = () => { + cleanup(); + resolve(null); + }; + + window.addEventListener("wallet-picker-select", handleSelect); + window.addEventListener("wallet-picker-cancel", handleCancel); + + push("WalletPickerJob", { + chain: opts.chainId, + ...(appName ? { appName } : {}), + }); + }); + }); + + setChainSwitchConfirm(async (opts) => { + return new Promise((resolve) => { + const timeout = window.setTimeout(() => resolve(false), 30_000); + + const handleResult = (e: Event) => { + window.clearTimeout(timeout); + const detail = (e as CustomEvent).detail as { approved?: boolean } | undefined; + resolve(detail?.approved === true); + }; + + window.addEventListener("chain-switch-confirm", handleResult, { once: true }); + + push("ChainSwitchConfirmJob", { + fromChainId: opts.fromChainId, + toChainId: opts.toChainId, + appName: opts.appName, + }); + }); + }); + + setEvmSigningDialog(async (params) => { + return new Promise<{ signature: string } | null>((resolve) => { + const timeout = window.setTimeout(() => resolve(null), 60_000); + + const handleResult = (e: Event) => { + window.clearTimeout(timeout); + const detail = (e as CustomEvent).detail as + | { confirmed?: boolean; signature?: string } + | undefined; + if (detail?.confirmed && detail.signature) { + resolve({ signature: detail.signature }); + return; + } + resolve(null); + }; + + window.addEventListener("signing-confirm", handleResult, { once: true }); + push("SigningConfirmJob", { + message: params.message, + address: params.address, + appName: params.appName, + chainName: "ethereum", + }); + }); + }); + + setEvmTransactionDialog(async (params) => { + const { from, to, value, chainId } = params.tx; + if (!from || !to) { + return null; + } + + const valueBigInt = BigInt(value ?? "0x0"); + const amount = formatUnits(valueBigInt, 18); + const keyAppChainId = chainId ? getKeyAppChainId(chainId) : null; + + return new Promise<{ txHash: string } | null>((resolve) => { + const timeout = window.setTimeout(() => resolve(null), 60_000); + + const handleResult = (e: Event) => { + window.clearTimeout(timeout); + const detail = (e as CustomEvent).detail as { confirmed?: boolean; txHash?: string } | undefined; + if (detail?.confirmed && detail.txHash) { + resolve({ txHash: detail.txHash }); + return; + } + resolve(null); + }; + + window.addEventListener("miniapp-transfer-confirm", handleResult, { once: true }); + push("MiniappTransferConfirmJob", { + appName: params.appName, + from, + to, + amount, + chain: keyAppChainId ?? "ethereum", + }); + }); + }); + + setTronWalletPicker(async (opts) => { + const appName = opts?.app?.name; + + return new Promise((resolve) => { + const timeout = window.setTimeout(() => resolve(null), 30_000); + + const cleanup = () => { + window.clearTimeout(timeout); + window.removeEventListener("wallet-picker-select", handleSelect); + window.removeEventListener("wallet-picker-cancel", handleCancel); + }; + + const handleSelect = (e: Event) => { + cleanup(); + const detail = (e as CustomEvent).detail as { address: string; chain: string; name?: string }; + resolve({ address: detail.address, chain: detail.chain, name: detail.name }); + }; + + const handleCancel = () => { + cleanup(); + resolve(null); + }; + + window.addEventListener("wallet-picker-select", handleSelect); + window.addEventListener("wallet-picker-cancel", handleCancel); + + push("WalletPickerJob", { + chain: "tron", + ...(appName ? { appName } : {}), + }); + }); + }); + return () => { getBridge().setPermissionRequestCallback(null); setWalletPicker(null); + setEvmWalletPicker(null); + setChainSwitchConfirm(null); + setEvmSigningDialog(null); + setEvmTransactionDialog(null); + setTronWalletPicker(null); setGetAccounts(null); setSigningDialog(null); setTransferDialog(null); diff --git a/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx b/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx index f96e81de2..cc194f1c0 100644 --- a/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx +++ b/src/stackflow/activities/sheets/__stories__/ChainSwitchConfirmJob.stories.tsx @@ -1,12 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react' -import { useState, useEffect } from 'react' -import { ChainSwitchConfirmJob } from '../ChainSwitchConfirmJob' -import { ActivityParamsProvider } from '../../../hooks' -import { BottomSheet } from '@/components/layout/bottom-sheet' +import { useState } from 'react' +import { getKeyAppChainId } from '@biochain/bio-sdk' -const meta: Meta = { +const meta: Meta = { title: 'Sheets/ChainSwitchConfirmJob', - component: ChainSwitchConfirmJob, parameters: { layout: 'fullscreen', }, @@ -20,73 +17,75 @@ const meta: Meta = { } export default meta -type Story = StoryObj +type Story = StoryObj -/** Demo wrapper to handle events */ -function ChainSwitchDemo({ +function ChainSwitchPreview({ fromChainId, toChainId, appName, - appIcon, }: { fromChainId: string toChainId: string appName?: string - appIcon?: string }) { - const [result, setResult] = useState<{ approved: boolean; toChainId?: string } | null>(null) - const [isOpen, setIsOpen] = useState(true) + const [result, setResult] = useState<'approved' | 'rejected' | null>(null) + const from = getKeyAppChainId(fromChainId) ?? fromChainId + const to = getKeyAppChainId(toChainId) ?? toChainId - useEffect(() => { - const handleConfirm = (e: CustomEvent) => { - setResult(e.detail) - setIsOpen(false) - } - window.addEventListener('chain-switch-confirm', handleConfirm as EventListener) - return () => window.removeEventListener('chain-switch-confirm', handleConfirm as EventListener) - }, []) + return ( +
+
+
+ +

切换网络确认

+

+ {appName || '未知 DApp'} 请求切换网络 +

- if (!isOpen) { - return ( -
-
- {result?.approved ? '✅ 已确认切换' : '❌ 已取消'} +
+
+ 当前网络 + {from} +
+
+ 目标网络 + {to} +
- {result?.approved && ( -
- 切换到链: {result.toChainId} + + {result && ( +
+ 结果:{result === 'approved' ? '已确认' : '已取消'}
)} - -
- ) - } - return ( - - - - - +
+ + +
+
+
+
) } /** BSC 切换到 Ethereum */ export const BSCToEthereum: Story = { render: () => ( - ), } @@ -94,11 +93,10 @@ export const BSCToEthereum: Story = { /** Ethereum 切换到 BSC */ export const EthereumToBSC: Story = { render: () => ( - ), } @@ -106,7 +104,7 @@ export const EthereumToBSC: Story = { /** 无应用信息 */ export const NoAppInfo: Story = { render: () => ( - @@ -116,7 +114,7 @@ export const NoAppInfo: Story = { /** 未知链 ID */ export const UnknownChain: Story = { render: () => ( - Date: Mon, 5 Jan 2026 13:32:21 +0800 Subject: [PATCH 4/8] fix(e2e): capture miniapp screenshots via ecosystem urls --- .../forge-01-connect.png | Bin 23661 -> 215443 bytes .../forge-02-connect-dark.png | Bin 23784 -> 215443 bytes .../forge-03-green-theme.png | Bin 23738 -> 215443 bytes .../forge-04-dark-purple-theme.png | Bin 23731 -> 215443 bytes .../teleport-01-connect.png | Bin 23661 -> 25882 bytes .../teleport-02-connect-dark.png | Bin 23784 -> 25882 bytes .../teleport-03-connect-blue-theme.png | Bin 23710 -> 25882 bytes .../teleport-04-green-theme.png | Bin 23752 -> 25882 bytes .../forge-01-connect.png | Bin 23661 -> 215443 bytes .../forge-02-connect-dark.png | Bin 23784 -> 215443 bytes .../forge-03-green-theme.png | Bin 23738 -> 215443 bytes .../forge-04-dark-purple-theme.png | Bin 23731 -> 215443 bytes .../teleport-01-connect.png | Bin 23661 -> 25882 bytes .../teleport-02-connect-dark.png | Bin 23784 -> 25882 bytes .../teleport-03-connect-blue-theme.png | Bin 23710 -> 25882 bytes .../teleport-04-green-theme.png | Bin 23752 -> 25882 bytes e2e/miniapp-ui.mock.spec.ts | 64 ++++++++++-------- 17 files changed, 35 insertions(+), 29 deletions(-) diff --git a/e2e/__screenshots__/Desktop-Chrome/miniapp-ui.mock.spec.ts/forge-01-connect.png b/e2e/__screenshots__/Desktop-Chrome/miniapp-ui.mock.spec.ts/forge-01-connect.png index 36ff3dc5dff0086866da25432ba3b1b7d8a4be81..03517901a101dd5fcd3a7c777ea8d50944ec022d 100644 GIT binary patch literal 215443 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

G1wjEaP0B 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