Skip to content

Commit

Permalink
Merge branch 'main' into test/sats-connect-connector
Browse files Browse the repository at this point in the history
  • Loading branch information
zoruka authored Nov 27, 2024
2 parents f187723 + afa6825 commit 3441bf2
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 293 deletions.
2 changes: 1 addition & 1 deletion apps/laboratory/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const cspHeader = `
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src * 'self' data: blob: https://walletconnect.org https://walletconnect.com https://secure.walletconnect.com https://secure.walletconnect.org https://tokens-data.1inch.io https://tokens.1inch.io https://ipfs.io https://appkit-lab.reown.org;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://react-wallet.walletconnect.com https://rpc.walletconnect.com https://rpc.walletconnect.org https://relay.walletconnect.com https://relay.walletconnect.org wss://relay.walletconnect.com wss://relay.walletconnect.org https://pulse.walletconnect.com https://pulse.walletconnect.org https://api.web3modal.com https://api.web3modal.org wss://www.walletlink.org https://o1095249.ingest.sentry.io https://quote-api.jup.ag;
connect-src 'self' https://react-wallet.walletconnect.com https://rpc.walletconnect.com https://rpc.walletconnect.org https://relay.walletconnect.com https://relay.walletconnect.org wss://relay.walletconnect.com wss://relay.walletconnect.org https://pulse.walletconnect.com https://pulse.walletconnect.org https://api.web3modal.com https://api.web3modal.org wss://www.walletlink.org https://o1095249.ingest.sentry.io https://quote-api.jup.ag https://mempool.space;
frame-src 'self' https://verify.walletconnect.com https://verify.walletconnect.org https://secure.walletconnect.com https://secure.walletconnect.org https://secure.reown.com https://widget.solflare.com/ ${secureSiteDomain};
object-src 'none';
base-uri 'self';
Expand Down
3 changes: 2 additions & 1 deletion apps/laboratory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@reown/appkit": "workspace:*",
"@reown/appkit-adapter-bitcoin": "workspace:*",
"@reown/appkit-adapter-ethers": "workspace:*",
"@reown/appkit-adapter-ethers5": "workspace:*",
"@reown/appkit-adapter-solana": "workspace:*",
"@reown/appkit-adapter-wagmi": "workspace:*",
"@reown/appkit-adapter-bitcoin": "workspace:*",
"@reown/appkit-experimental": "workspace:*",
"@reown/appkit-siwe": "workspace:*",
"@reown/appkit-siwx": "workspace:*",
Expand All @@ -95,6 +95,7 @@
"@walletconnect/universal-provider": "2.17.0",
"@walletconnect/utils": "2.17.0",
"axios": "1.7.2",
"bitcoinjs-lib": "7.0.0-rc.0",
"bs58": "6.0.0",
"date-fns": "4.1.0",
"ethers": "6.13.2",
Expand Down
52 changes: 51 additions & 1 deletion apps/laboratory/src/pages/library/bitcoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
createAppKit,
useAppKitAccount,
useAppKitProvider,
type CaipNetwork
type CaipNetwork,
useAppKitNetwork
} from '@reown/appkit/react'
import { ThemeStore } from '../../utils/StoreUtil'
import { ConstantsUtil } from '../../utils/ConstantsUtil'
Expand All @@ -12,6 +13,7 @@ import { AppKitButtons } from '../../components/AppKitButtons'
import { BitcoinAdapter, type BitcoinConnector } from '@reown/appkit-adapter-bitcoin'
import { Button, Stack, useToast } from '@chakra-ui/react'
import { useState } from 'react'
import { BitcoinUtil } from '../../utils/BitcoinUtil'

const networks = ConstantsUtil.BitcoinNetworks

Expand All @@ -38,6 +40,8 @@ ThemeStore.setModal(appkit)
export default function MultiChainBitcoinAdapterOnly() {
const { walletProvider } = useAppKitProvider<BitcoinConnector>('bip122')
const { address } = useAppKitAccount()
const { caipNetwork } = useAppKitNetwork()

const [loading, setLoading] = useState(false)

const toast = useToast()
Expand Down Expand Up @@ -94,6 +98,49 @@ export default function MultiChainBitcoinAdapterOnly() {
}
}

async function signPSBT() {
if (!walletProvider || !address || !caipNetwork) {
toast({
title: 'No connection detected',
status: 'error',
isClosable: true
})

return
}

if (caipNetwork.chainNamespace !== 'bip122') {
toast({
title: 'The selected chain is not bip122',
status: 'error',
isClosable: true
})

return
}

try {
const utxos = await BitcoinUtil.getUTXOs(address, caipNetwork.caipNetworkId)
const feeRate = await BitcoinUtil.getFeeRate()

const params = BitcoinUtil.createSignPSBTParams({
amount: 100,
feeRate,
network: caipNetwork,
recipientAddress: address,
senderAddress: address,
utxos
})

const signature = await walletProvider.signPSBT(params)
toast({ title: 'PSBT Signature', description: signature.psbt, status: 'success' })
} catch (error) {
toast({ title: 'Error', description: (error as Error).message, status: 'error' })
} finally {
setLoading(false)
}
}

return (
<>
<AppKitButtons />
Expand All @@ -105,6 +152,9 @@ export default function MultiChainBitcoinAdapterOnly() {
<Button data-testid="send-transfer-button" onClick={sendTransfer} isDisabled={loading}>
Send Transfer
</Button>
<Button data-testid="sign-psbt-button" onClick={signPSBT} isDisabled={loading}>
Sign PSBT
</Button>
</Stack>
)}
</>
Expand Down
141 changes: 141 additions & 0 deletions apps/laboratory/src/utils/BitcoinUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { BitcoinConnector } from '@reown/appkit-adapter-bitcoin'
import type { CaipNetwork, CaipNetworkId } from '@reown/appkit'
import * as networks from '@reown/appkit/networks'
import * as bitcoin from 'bitcoinjs-lib'

export const BitcoinUtil = {
createSignPSBTParams(params: BitcoinUtil.CreateSignPSBTParams): BitcoinConnector.SignPSBTParams {
const network = this.getBitcoinNetwork(params.network.caipNetworkId)

const psbt = new bitcoin.Psbt({ network })
const payment = bitcoin.payments.p2wpkh({ address: params.senderAddress, network })

if (!payment.output) {
throw new Error('Invalid payment output')
}

const change = this.calculateChange(params.utxos, params.amount, params.feeRate)

if (change < 0) {
throw new Error('Insufficient funds')
} else if (change > 0) {
psbt.addOutput({
address: params.senderAddress,
value: BigInt(change)
})
}

for (const utxo of params.utxos) {
psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
witnessUtxo: {
script: payment.output,
value: BigInt(utxo.value)
}
})
}

psbt.addOutput({
address: params.recipientAddress,
value: BigInt(params.amount)
})

if (params.memo) {
const data = Buffer.from(params.memo, 'utf8')
const embed = bitcoin.payments.embed({ data: [data] })

if (!embed.output) {
throw new Error('Invalid embed output')
}

psbt.addOutput({
script: embed.output,
value: BigInt(0)
})
}

return {
psbt: psbt.toBase64(),
signInputs: [],
broadcast: false
}
},

async getUTXOs(address: string, networkId: CaipNetworkId): Promise<BitcoinUtil.UTXO[]> {
const isTestnet = this.isTestnet(networkId)
// Make chain dynamic

const response = await fetch(
`https://mempool.space${isTestnet ? '/testnet' : ''}/api/address/${address}/utxo`
)

return await response.json()
},

async getFeeRate() {
const defaultFeeRate = 2
try {
const response = await fetch('https://mempool.space/api/v1/fees/recommended')
if (response.ok) {
const data = await response.json()

if (data?.fastestFee) {
return parseInt(data.fastestFee, 10)
}
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error fetching fee rate', e)
}

return defaultFeeRate
},

calculateChange(utxos: BitcoinUtil.UTXO[], amount: number, feeRate: number): number {
const inputSum = utxos.reduce((sum, utxo) => sum + utxo.value, 0)
/**
* 10 bytes: This is an estimated fixed overhead for the transaction.
* 148 bytes: This is the average size of each input (UTXO).
* 34 bytes: This is the size of each output.
* The multiplication by 2 indicates that there are usually two outputs in a typical transaction (one for the recipient and one for change)
*/
const estimatedSize = 10 + 148 * utxos.length + 34 * 2
const fee = estimatedSize * feeRate
const change = inputSum - amount - fee

return change
},

isTestnet(networkId: CaipNetworkId): boolean {
return networkId === networks.bitcoinTestnet.caipNetworkId
},

getBitcoinNetwork(networkId: CaipNetworkId): bitcoin.Network {
return this.isTestnet(networkId) ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
}
}

export namespace BitcoinUtil {
export type CreateSignPSBTParams = {
senderAddress: string
recipientAddress: string
network: CaipNetwork
amount: number
utxos: UTXO[]
feeRate: number
memo?: string
}

export type UTXO = {
txid: string
vout: number
value: number
status: {
confirmed: boolean
block_height: number
block_hash: string
block_time: number
}
}
}
5 changes: 3 additions & 2 deletions apps/laboratory/src/utils/ConstantsUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
unichainSepolia,
hedera,
aurora,
bitcoin
bitcoin,
bitcoinTestnet
} from '@reown/appkit/networks'
import type { AppKitNetwork } from '@reown/appkit/networks'
import { getLocalStorageItem } from './LocalStorage'
Expand Down Expand Up @@ -87,7 +88,7 @@ const SolanaNetworks = [solana, solanaTestnet, solanaDevnet, solanaNotExist] as
...AppKitNetwork[]
]

const BitcoinNetworks = [bitcoin] as [AppKitNetwork, ...AppKitNetwork[]]
const BitcoinNetworks = [bitcoin, bitcoinTestnet] as [AppKitNetwork, ...AppKitNetwork[]]

export const ConstantsUtil = {
SigningSucceededToastTitle: 'Signing Succeeded',
Expand Down
8 changes: 8 additions & 0 deletions apps/laboratory/tests/shared/validators/ModalValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ export class ModalValidator {
await expect(title).toBeVisible()
}

async expectSwitchedNetworkWithNetworkView() {
const switchNetworkViewLocator = this.page.locator('w3m-network-switch-view')
await expect(switchNetworkViewLocator).toBeVisible()
await expect(switchNetworkViewLocator).not.toBeVisible({
timeout: 20_000
})
}

async expectSwitchedNetworkOnNetworksView(name: string) {
const networkOptions = this.page.getByTestId(`w3m-network-switch-${name}`)
await expect(networkOptions.locator('wui-icon')).toBeVisible()
Expand Down
3 changes: 2 additions & 1 deletion apps/laboratory/tests/siwe-sa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ smartAccountSiweTest('it should upgrade wallet', async () => {
})

smartAccountSiweTest('it should switch to a smart account enabled network and sign', async () => {
const targetChain = 'Sepolia'
const targetChain = 'Base'
await page.switchNetwork(targetChain)
await validator.expectSwitchedNetworkWithNetworkView()
await page.promptSiwe()
await page.approveSign()

Expand Down
19 changes: 19 additions & 0 deletions packages/adapters/bitcoin/src/connectors/SatsConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,25 @@ export class SatsConnectConnector extends ProviderEventEmitter implements Bitcoi
return res.signature
}

public async signPSBT(
params: BitcoinConnector.SignPSBTParams
): Promise<BitcoinConnector.SignPSBTResponse> {
const signInputs = params.signInputs.reduce<Record<string, number[]>>((acc, input) => {
const currentIndexes = acc[input.address] || []
currentIndexes.push(input.index)

return { ...acc, [input.address]: currentIndexes }
}, {})

const res = await this.internalRequest('signPsbt', {
psbt: params.psbt,
broadcast: params.broadcast,
signInputs
})

return res
}

public async sendTransfer({
amount,
recipient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ export class WalletStandardConnector extends ProviderEventEmitter implements Bit
return Buffer.from(response.signature).toString('base64')
}

async signPSBT(
_params: BitcoinConnector.SignPSBTParams
): Promise<BitcoinConnector.SignPSBTResponse> {
return Promise.reject(new Error('Method not implemented.'))
}

async sendTransfer(_params: BitcoinConnector.SendTransferParams): Promise<string> {
return Promise.resolve('txid')
}
Expand Down
16 changes: 16 additions & 0 deletions packages/adapters/bitcoin/src/utils/BitcoinConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface BitcoinConnector extends ChainAdapterConnector, Provider {
getAccountAddresses(): Promise<BitcoinConnector.AccountAddress[]>
signMessage(params: BitcoinConnector.SignMessageParams): Promise<string>
sendTransfer(params: BitcoinConnector.SendTransferParams): Promise<string>
signPSBT(params: BitcoinConnector.SignPSBTParams): Promise<BitcoinConnector.SignPSBTResponse>
}

export namespace BitcoinConnector {
Expand All @@ -28,4 +29,19 @@ export namespace BitcoinConnector {
amount: string
recipient: string
}

export type SignPSBTParams = {
psbt: string
signInputs: {
address: string
index: number
sighashTypes: number[]
}[]
broadcast?: boolean
}

export type SignPSBTResponse = {
psbt: string
txid?: string
}
}
Loading

0 comments on commit 3441bf2

Please sign in to comment.