Skip to content

feat: add bitcoin wallet to browser extension for testing purposes #4200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 15, 2025
20 changes: 18 additions & 2 deletions apps/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"description": "",
"scripts": {
"build": "webpack",
"dev": "webpack --watch --mode=production"
"dev": "webpack --watch --mode=production",
"start": "webpack serve --mode=development --open"
},
"keywords": [],
"author": "",
Expand All @@ -14,6 +15,7 @@
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7",
"@reown/appkit-ui-new": "workspace:*",
"@reown/appkit-common": "workspace:*",
"@solana/wallet-standard": "1.1.2",
"@solana/web3.js": "1.98.0",
"@tanstack/react-query": "5.56.2",
Expand All @@ -25,24 +27,37 @@
"@wallet-standard/core": "1.1.0",
"babel-loader": "9.1.3",
"big.js": "6.2.2",
"bip32": "^4.0.0",
"bip39": "3.1.0",
"bitcoinjs-lib": "6.1.7",
"bs58": "6.0.0",
"buffer": "^6.0.3",
"clsx": "2.1.0",
"copy-webpack-plugin": "12.0.2",
"crypto": "1.0.1",
"crypto-browserify": "3.12.1",
"css-loader": "7.1.2",
"ecpair": "3.0.0",
"eventemitter3": "5.0.1",
"html-webpack-plugin": "5.6.0",
"mini-css-extract-plugin": "2.9.2",
"mipd": "0.0.7",
"process": "0.11.10",
"react": "19.0.0",
"react-dom": "19.0.0",
"stream-browserify": "^3.0.0",
"tiny-secp256k1": "2.2.3",
"tweetnacl": "1.0.3",
"uuid": "10.0.0",
"viem": "2.23.13",
"vm-browserify": "^1.1.2",
"wagmi": "2.14.16",
"wasm-loader": "1.3.0",
"webpack": "5.94.0"
},
"devDependencies": {
"@types/big.js": "6.2.2",
"@types/bitcoinjs-lib": "5.0.4",
"@types/chrome": "0.0.268",
"@types/node": "22.13.4",
"@types/react": "19.0.0",
Expand All @@ -51,6 +66,7 @@
"dotenv-webpack": "8.1.0",
"ts-loader": "9.5.1",
"typescript": "5.7.3",
"webpack-cli": "5.1.4"
"webpack-cli": "5.1.4",
"webpack-dev-server": "^5.2.1"
}
}
Binary file added apps/browser-extension/src/assets/images/btc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/browser-extension/src/assets/images/eth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/browser-extension/src/assets/images/sol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions apps/browser-extension/src/components/ChainTabs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useState } from 'react'

import { ChainNamespace, ConstantsUtil } from '@reown/appkit-common'

import { sprinkles } from '../../css/sprinkless.css'
import { Btc } from '../Icons/Btc'
import { Eth } from '../Icons/Eth'
import { Sol } from '../Icons/Sol'

const Tab = ({ onTabClick }: { onTabClick: (tab: ChainNamespace) => void }) => {
const [activeTab, setActiveTab] = useState<ChainNamespace>('eip155')

const tabs: ChainNamespace[] = ['eip155', 'solana', 'bip122']
const icons: React.ReactNode[] = [<Eth />, <Sol />, <Btc />]

const handleTabClick = (tab: ChainNamespace) => {
setActiveTab(tab)
onTabClick(tab)
}

return (
<div
className={sprinkles({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '2',
width: 'full',
background: 'neutrals900',
borderRadius: '16'
})}
>
{tabs.map((tab, index) => (
<button
key={tab}
onClick={() => handleTabClick(tab)}
className={sprinkles({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '1',
width: 'auto',
flex: '1',
padding: '2',
borderRadius: '8',
border: 'none',
background: activeTab === tab ? 'neutrals800' : 'neutrals900',
color: 'white',
cursor: 'pointer',
transition: 'default'
})}
>
{icons[index]}
{ConstantsUtil.CHAIN_NAME_MAP[tab].replaceAll('EVM Networks', 'Ethereum')}
</button>
))}
</div>
)
}

export default Tab
10 changes: 10 additions & 0 deletions apps/browser-extension/src/components/Icons/Btc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function Btc({ width, height }: { width?: number; height?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M16.2615 8.62828C16.5389 6.76025 15.127 5.75611 13.1962 5.08625L13.8226 2.55584L12.2934 2.17203L11.6836 4.63581C11.2816 4.53482 10.8688 4.43965 10.4584 4.34531L11.0726 1.86526L9.54428 1.48145L8.9176 4.01103C8.58492 3.93474 8.25816 3.85933 7.94115 3.77988L7.94292 3.77192L5.83408 3.24148L5.42728 4.88666C5.42728 4.88666 6.56184 5.14862 6.53793 5.16474C7.15719 5.32042 7.26917 5.73333 7.25057 6.06061L6.53712 8.94334C6.57976 8.95425 6.63507 8.97003 6.6961 8.99473C6.64508 8.98197 6.59079 8.96804 6.53446 8.95446L5.53443 12.9928C5.45875 13.1823 5.26666 13.4667 4.83371 13.3587C4.84904 13.381 3.72225 13.0793 3.72225 13.0793L2.96301 14.8425L4.95305 15.3422C5.32327 15.4357 5.68606 15.5335 6.04334 15.6255L5.41053 18.185L6.93799 18.5688L7.56466 16.0365C7.98195 16.1506 8.3869 16.2559 8.78335 16.3551L8.15878 18.8754L9.68808 19.2592L10.3208 16.7046C12.9285 17.2017 14.8892 17.0013 15.7145 14.6255C16.3796 12.7127 15.6814 11.6095 14.3095 10.89C15.3088 10.6579 16.0614 9.99592 16.2621 8.62848L16.2616 8.62814L16.2615 8.62828ZM12.7676 13.5633C12.295 15.476 9.09771 14.442 8.0611 14.1828L8.90085 10.792C9.93739 11.0526 13.2615 11.5684 12.7677 13.5633H12.7676ZM13.2405 8.60056C12.8094 10.3404 10.1483 9.45649 9.28503 9.23975L10.0464 6.16449C10.9096 6.38123 13.6896 6.78577 13.2407 8.60056H13.2405Z"
fill="white"
/>
</svg>
)
}
28 changes: 28 additions & 0 deletions apps/browser-extension/src/components/Icons/Eth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export function Eth({ width, height }: { width?: number; height?: number }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M9.99839 1.48145V8.05333L15.5523 10.5356L9.99839 1.48145Z"
fill="white"
fillOpacity="0.9"
/>
<path d="M9.99839 1.48145L4.44446 10.5356L9.99839 8.05333V1.48145Z" fill="white" />
<path
d="M9.99839 14.7937V19.2592L15.5556 11.5693L9.99839 14.7937Z"
fill="white"
fillOpacity="0.9"
/>
<path d="M9.99839 19.2592V14.7937L4.44446 11.5693L9.99839 19.2592Z" fill="white" />
<path
d="M9.99839 13.76L15.5523 10.5356L9.99839 8.05333V13.76Z"
fill="white"
fillOpacity="0.8"
/>
<path
d="M4.44446 10.5356L9.99839 13.76V8.05333L4.44446 10.5356Z"
fill="white"
fillOpacity="0.9"
/>
</svg>
)
}
10 changes: 10 additions & 0 deletions apps/browser-extension/src/components/Icons/Sol.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function Sol() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M17.7015 13.6321L15.2559 16.1248C15.2027 16.179 15.1384 16.2221 15.0669 16.2517C14.9954 16.2812 14.9183 16.2964 14.8404 16.2963H3.24693C3.19161 16.2963 3.1375 16.281 3.09124 16.2521C3.04498 16.2233 3.00859 16.1823 2.98654 16.134C2.96449 16.0858 2.95774 16.0325 2.96713 15.9807C2.97651 15.9289 3.00161 15.8808 3.03935 15.8424L5.48682 13.3497C5.53985 13.2957 5.60396 13.2526 5.67521 13.2231C5.74646 13.1936 5.82333 13.1783 5.90105 13.1782H17.4939C17.5492 13.1782 17.6033 13.1935 17.6496 13.2223C17.6958 13.2512 17.7322 13.2922 17.7543 13.3404C17.7763 13.3887 17.7831 13.4419 17.7737 13.4938C17.7643 13.5456 17.7392 13.5937 17.7015 13.6321ZM15.2559 8.61244C15.2027 8.55829 15.1384 8.51512 15.0669 8.48561C14.9954 8.45612 14.9183 8.44091 14.8404 8.44095H3.24693C3.19161 8.44095 3.1375 8.45631 3.09124 8.48514C3.04498 8.51398 3.00859 8.55502 2.98654 8.60323C2.96449 8.65145 2.95774 8.70474 2.96713 8.75656C2.97651 8.80836 3.00161 8.85644 3.03935 8.89489L5.48682 11.3876C5.53985 11.4416 5.60396 11.4847 5.67521 11.5142C5.74646 11.5437 5.82333 11.559 5.90105 11.5591H17.4939C17.5492 11.5591 17.6033 11.5438 17.6496 11.5149C17.6958 11.4861 17.7322 11.445 17.7543 11.3968C17.7763 11.3486 17.7831 11.2953 17.7737 11.2435C17.7643 11.1917 17.7392 11.1436 17.7015 11.1052L15.2559 8.61244ZM3.24693 6.8219H14.8404C14.9183 6.82193 14.9954 6.80674 15.0669 6.77723C15.1384 6.74772 15.2027 6.70455 15.2559 6.6504L17.7015 4.15767C17.7392 4.11923 17.7643 4.07115 17.7737 4.01934C17.7831 3.96752 17.7763 3.91424 17.7543 3.86602C17.7322 3.8178 17.6958 3.77676 17.6496 3.74793C17.6033 3.7191 17.5492 3.70374 17.4939 3.70374H5.90105C5.82333 3.70386 5.74646 3.71915 5.67521 3.74865C5.60396 3.77815 5.53985 3.82123 5.48682 3.87524L3.03998 6.36797C3.00228 6.40636 2.97719 6.4544 2.96778 6.50615C2.95838 6.5579 2.96508 6.61115 2.98705 6.65935C3.00902 6.70754 3.04531 6.74858 3.09148 6.77746C3.13764 6.80634 3.19167 6.82178 3.24693 6.8219Z"
fill="white"
/>
</svg>
)
}
20 changes: 15 additions & 5 deletions apps/browser-extension/src/components/Token/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ChainNamespace } from '@reown/appkit-common'

import { touchableStyles } from '../../css/touchableStyles'
import { Box } from '../Box'
import { Text } from '../Text'

const tokens = {
ethereum: {
const tokens: Record<ChainNamespace, { title: string; symbol: string; src: string }> = {
eip155: {
title: 'Ethereum',
symbol: 'ETH',
src: '/assets/images/eth.png'
Expand All @@ -12,13 +14,21 @@ const tokens = {
title: 'Solana',
symbol: 'SOL',
src: '/assets/images/sol.png'
},
bip122: {
title: 'Bitcoin',
symbol: 'BTC',
src: '/assets/images/btc.png'
},
polkadot: {
title: 'Polkadot',
symbol: 'DOT',
src: '/assets/images/dot.png'
}
}

export type TokenKey = keyof typeof tokens

interface TokenProps {
token: TokenKey
token: ChainNamespace
balance: string
}

Expand Down
44 changes: 44 additions & 0 deletions apps/browser-extension/src/core/BitcoinProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import BIP32Factory, { BIP32Interface } from 'bip32'
import * as bip39 from 'bip39'
import * as bitcoin from 'bitcoinjs-lib'
import * as ecc from 'tiny-secp256k1'

import { ConstantsUtil } from '../utils/ConstantsUtil'

bitcoin.initEccLib(ecc)

const bip32 = BIP32Factory(ecc)
const privateKey = '0x0000000000000000000000000000000000000000000000000000000000000000'
const mnemonic = privateKey ? privateKey : bip39.generateMnemonic()
const seed = bip39.mnemonicToSeedSync(mnemonic)
const root = bip32.fromSeed(seed)

export class BitcoinProvider {
name = 'Reown'
version = '1.0.0' as const
icon = ConstantsUtil.IconRaw as `data:image/png;base64,${string}`

private account: BIP32Interface = bip32.fromBase58(root.toBase58())
private address: string

constructor() {
const path = `m/84'/0'/0'/1/0`
const child = this.account.derivePath(path)
this.address = bitcoin.payments.p2tr({
pubkey: child.publicKey.slice(1),
network: bitcoin.networks.bitcoin
}).address!
}

connect() {
return Promise.resolve(this.address)
}

getBalance(address: string) {
return 0
}

getAddress() {
return this.address
}
}
27 changes: 22 additions & 5 deletions apps/browser-extension/src/hooks/useBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import Big from 'big.js'
import { Address, formatEther } from 'viem'
import { useBalance as useWagmiBalance } from 'wagmi'

export function useBalance(chain: 'ethereum' | 'solana', account: string) {
import { ChainNamespace } from '@reown/appkit-common'

import { BitcoinProvider } from '../core/BitcoinProvider'

const bitcoinProvider = new BitcoinProvider()

export function useBalance(chain: ChainNamespace, account: string) {
const { data: ethereumBalance } = useWagmiBalance({
address: account as Address,
query: {
enabled: chain === 'ethereum'
enabled: chain === 'eip155'
}
})

Expand All @@ -25,7 +31,18 @@ export function useBalance(chain: 'ethereum' | 'solana', account: string) {
enabled: chain === 'solana'
})

return chain === 'ethereum'
? formatEther(ethereumBalance?.value ?? BigInt(0))
: solanaBalance.toString()
function getBalance() {
switch (chain) {
case 'eip155':
return formatEther(ethereumBalance?.value ?? BigInt(0))
case 'solana':
return solanaBalance.toString()
case 'bip122':
return bitcoinProvider.getBalance(account).toString()
default:
return '0'
}
}

return getBalance()
}
3 changes: 3 additions & 0 deletions apps/browser-extension/src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
"unlimitedStorage",
"notifications"
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"content_scripts": [
{
"all_frames": true,
Expand Down
Loading
Loading