Skip to content
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

feat(bitcoin): Add phantom wallet adapter #1267

Merged
merged 19 commits into from
Dec 26, 2024
5 changes: 5 additions & 0 deletions .changeset/mighty-dogs-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ant-design/web3-bitcoin': minor
---

feat(bitcoin): Add phantom wallet adapter
3 changes: 2 additions & 1 deletion packages/bitcoin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"dependencies": {
"@ant-design/web3-common": "workspace:*",
"@ant-design/web3-icons": "workspace:*",
"sats-connect": "^2.4.0"
"sats-connect": "~2.4.0",
"uint8array-tools": "~0.0.9"
},
"devDependencies": {
"father": "^4.4.4",
Expand Down
1 change: 1 addition & 0 deletions packages/bitcoin/src/adapter/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './unisat';
export * from './xverse';
export * from './okx';
export * from './phantom';
131 changes: 131 additions & 0 deletions packages/bitcoin/src/adapter/wallets/phantom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* v8 ignore start */
import type { Account } from '@ant-design/web3-common';
import { fromHex, fromUtf8, toBase64, toHex } from 'uint8array-tools';

import { NoAddressError, NoProviderError, NotImplementedError } from '../../error';
import { getBalanceByMempool, getInscriptionsByAddress } from '../../helpers';
import type { SignPsbtParams, TransferParams } from '../../types';
import type { BitcoinWallet } from '../useBitcoinWallet';

type AccountType = {
address: string;
addressType: string;
publicKey: string;
purpose: string;
};

/**
* @link https://docs.phantom.app/bitcoin/provider-api-reference#options-parameters
*/
type PhantomSignPsbtOptions = {
sigHash?: number;
address: string;
signingIndexes: number[];
}[];

export class PhantomBitcoinWallet implements BitcoinWallet {
name: string;
provider?: any;
account?: Account;
payment?: string;

constructor(name: string) {
this.name = name;
this.provider = window.phantom?.bitcoin;
gin-lsl marked this conversation as resolved.
Show resolved Hide resolved
this.account = undefined;
}

connect = async () => {
if (!this.provider) {
throw new NoProviderError();
}

try {
const accounts: AccountType[] = await this.provider.requestAccounts();
const ordinals = accounts.find((acc) => acc.purpose === 'ordinals');
const payment = accounts.find((acc) => acc.purpose === 'payment');

this.account = ordinals ? { address: ordinals.address } : undefined;
this.payment = payment?.address;
} catch (error) {
throw error;
}
};

getBalance = async () => {
if (!this.payment) {
throw new NoAddressError();
}

const balance = await getBalanceByMempool(this.payment);
return balance;
};

signMessage = async (message: string) => {
if (!this.provider) {
throw new NoProviderError();
}

if (!this.account?.address) {
throw new NoAddressError();
}

const { signature } = await this.provider.signMessage(this.account.address, fromUtf8(message));

return toBase64(signature);
};

signPsbt = async ({ psbt, options = {} }: SignPsbtParams) => {
if (!this.provider) {
throw new NoProviderError();
}

if (!this.account?.address) {
throw new NoAddressError();
}

const serializedPsbt = fromHex(psbt);

const {
// `broadcast` not supported
// broadcast,
signInputs,
signHash,
} = options;
const inputsToSign: PhantomSignPsbtOptions = [];

if (signInputs) {
for (const address in signInputs) {
inputsToSign.push({
sigHash: signHash,
address,
signingIndexes: signInputs[address],
});
}
}

const signedPsbt = await this.provider.signPSBT(serializedPsbt, { inputsToSign });
const hexPsbt = toHex(signedPsbt);

return {
psbt: hexPsbt,
};
};

sendTransfer = async (params: TransferParams) => {
throw new NotImplementedError();
};

getInscriptions = async (offset = 0, limit = 20) => {
if (!this.account?.address) {
throw new NoAddressError();
}

const inscriptions = await getInscriptionsByAddress({
address: this.account.address,
offset,
limit,
});
return inscriptions;
};
}
9 changes: 9 additions & 0 deletions packages/bitcoin/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ export class NoInscriptionError extends Error {
this.name = this.constructor.name;
}
}

export class NotImplementedError extends Error {
name: string;

constructor(message = 'Not implemented') {
super(message);
this.name = this.constructor.name;
}
}
1 change: 1 addition & 0 deletions packages/bitcoin/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ declare interface Window {
unisat?: Unisat.Provider;
// TODO: 与其他 okx 冲突
okxwallet?: any;
phantom?: any;
}

declare namespace Unisat {
Expand Down
1 change: 1 addition & 0 deletions packages/bitcoin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './provider';
export * from './wallets';
export * from './types';
export * from './error';
export { useBitcoinWallet } from './adapter/useBitcoinWallet';
15 changes: 13 additions & 2 deletions packages/bitcoin/src/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
/* v8 ignore start */
import { metadata_OkxWallet, metadata_Unisat, metadata_Xverse } from '@ant-design/web3-assets';
import {
metadata_OkxWallet,
metadata_Phantom,
metadata_Unisat,
metadata_Xverse,
} from '@ant-design/web3-assets';

import { OkxBitcoinWallet, UnisatBitcoinWallet, XverseBitcoinWallet } from '../adapter';
import {
OkxBitcoinWallet,
PhantomBitcoinWallet,
UnisatBitcoinWallet,
XverseBitcoinWallet,
} from '../adapter';
import { WalletFactory } from './factory';

export const UnisatWallet = () => WalletFactory(UnisatBitcoinWallet, metadata_Unisat);
export const XverseWallet = () => WalletFactory(XverseBitcoinWallet, metadata_Xverse);
export const OkxWallet = () => WalletFactory(OkxBitcoinWallet, metadata_OkxWallet);
export const PhantomWallet = () => WalletFactory(PhantomBitcoinWallet, metadata_Phantom);
5 changes: 4 additions & 1 deletion packages/web3/src/bitcoin/demos/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConnectButton, Connector } from '@ant-design/web3';
import {
BitcoinWeb3ConfigProvider,
OkxWallet,
PhantomWallet,
UnisatWallet,
XverseWallet,
} from '@ant-design/web3-bitcoin';
Expand All @@ -12,7 +13,9 @@ import {
*/
const App: React.FC = () => {
return (
<BitcoinWeb3ConfigProvider autoConnect wallets={[XverseWallet(), UnisatWallet(), OkxWallet()]}>
<BitcoinWeb3ConfigProvider
wallets={[XverseWallet(), UnisatWallet(), OkxWallet(), PhantomWallet()]}
>
<Connector
modalProps={{
group: false,
Expand Down
5 changes: 4 additions & 1 deletion packages/web3/src/bitcoin/demos/get-inscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConnectButton, Connector, NFTImage } from '@ant-design/web3';
import {
BitcoinWeb3ConfigProvider,
OkxWallet,
PhantomWallet,
UnisatWallet,
useBitcoinWallet,
XverseWallet,
Expand Down Expand Up @@ -52,7 +53,9 @@ const GetInscriptions: React.FC = () => {
*/
const App: React.FC = () => {
return (
<BitcoinWeb3ConfigProvider wallets={[UnisatWallet(), XverseWallet(), OkxWallet()]}>
<BitcoinWeb3ConfigProvider
wallets={[UnisatWallet(), XverseWallet(), OkxWallet(), PhantomWallet()]}
>
<Space direction="vertical">
<Connector>
<ConnectButton />
Expand Down
12 changes: 11 additions & 1 deletion packages/web3/src/bitcoin/demos/send-transfer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ConnectButton, Connector } from '@ant-design/web3';
import {
BitcoinWeb3ConfigProvider,
NotImplementedError,
OkxWallet,
PhantomWallet,
UnisatWallet,
useBitcoinWallet,
XverseWallet,
Expand All @@ -25,6 +27,11 @@ const SendBitcoin: React.FC = () => {
sats: 10000,
});
} catch (error) {
if (error instanceof NotImplementedError) {
console.log('Not implemented');
return;
}

console.log('sign message error:', error);
}
}}
Expand All @@ -40,7 +47,10 @@ const SendBitcoin: React.FC = () => {
*/
const App: React.FC = () => {
return (
<BitcoinWeb3ConfigProvider wallets={[XverseWallet(), UnisatWallet(), OkxWallet()]} balance>
<BitcoinWeb3ConfigProvider
wallets={[XverseWallet(), UnisatWallet(), OkxWallet(), PhantomWallet()]}
balance
>
<Space>
<Connector>
<ConnectButton />
Expand Down
26 changes: 17 additions & 9 deletions packages/web3/src/bitcoin/demos/sign.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import React from 'react';
import { ConnectButton, Connector } from '@ant-design/web3';
import { metadata_Phantom } from '@ant-design/web3-assets';
import {
BitcoinWeb3ConfigProvider,
OkxWallet,
PhantomWallet,
UnisatWallet,
useBitcoinWallet,
XverseWallet,
} from '@ant-design/web3-bitcoin';
import { Button, Space } from 'antd';

const PSBT_FOR_PHANTOM =
'70736274ff0100fd940102000000048a841e4104389e3b5b04c1522ed7bfc0e64c3760cb801a2ee2406d1c2373d81d0300000000ffffffff8a841e4104389e3b5b04c1522ed7bfc0e64c3760cb801a2ee2406d1c2373d81d0400000000ffffffffaf3341cffbd7ce8b4a51dec0f358a21809fad67be1a11bf70b86e83ec4567ace0100000000ffffffff8eceb072b7c47ebd9c1aa2e17c2541145835c70a8667eb21fa2ff3cac503170e0600000000ffffffff07b004000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed04a01000000000000225120b1a548f1672b6bc666e23943b0b138a4216e2ce5f9bc687469e0f52a917bbf274b0100000000000017a91433ab469b293fa7700f0954c96ec630895892f189874402000000000000160014c015c65276d5f38d599d445c4cb03aa7aa0dc3655802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed072fe050000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed000000000000100fdf50102000000000101a1f8bb2bde4e13b2f397a82a8a101b378e90af13354710b93742be79b773e3840000000000ffffffff0b5802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed00d4c070000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed00247304402204d2e9da8a14537a1c37b59e6a7eb828816249747dbec041315ade14a181c826a022044dcc381227cef6ab18431e692dccc7c27e2a10fed4d3131e0523094819d1dda0121028b8437ac47a4434d4b86d19ab7eeb255887f75cfa43ca135168bfc8281ae8bb90000000001011f5802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed0000100fdf50102000000000101a1f8bb2bde4e13b2f397a82a8a101b378e90af13354710b93742be79b773e3840000000000ffffffff0b5802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed00d4c070000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed00247304402204d2e9da8a14537a1c37b59e6a7eb828816249747dbec041315ade14a181c826a022044dcc381227cef6ab18431e692dccc7c27e2a10fed4d3131e0523094819d1dda0121028b8437ac47a4434d4b86d19ab7eeb255887f75cfa43ca135168bfc8281ae8bb90000000001011f5802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed0000100fd6f0302000000000104a81c37519eca22bd78a4705230dcc471fb59f285bb2f22165c803de5bbe10b4c08000000171600147e3a872889766d0e3aebacf19f7936fd659b2605ffffffffa81c37519eca22bd78a4705230dcc471fb59f285bb2f22165c803de5bbe10b4c03000000171600147e3a872889766d0e3aebacf19f7936fd659b2605ffffffff33ec9dbdfa3ecb3654683b54855eac430d41b9b87b028f8cc84c6766bf7cc3870100000000ffffffff14e64f84c47029f5b8687519707c6e18080f675f37323746c695a681b0fdfece06000000171600147e3a872889766d0e3aebacf19f7936fd659b2605ffffffff07b00400000000000017a91433ab469b293fa7700f0954c96ec630895892f189874a01000000000000225120a9af1fae42dbe9b0604e0ed64ae6e26d56d1c9bbcc4e1e66326914bdb5149606591a000000000000225120cfb72754409e197bce4ea0670bd4f977104bcc86f7ea65d8981609dc66e984454402000000000000160014c015c65276d5f38d599d445c4cb03aa7aa0dc365580200000000000017a91433ab469b293fa7700f0954c96ec630895892f18987580200000000000017a91433ab469b293fa7700f0954c96ec630895892f18987d8c300000000000017a91433ab469b293fa7700f0954c96ec630895892f189870247304402204c291efdec8e7442aef45ad4751938ae21b847bedde8ce8c9eb208f7a29537880220247a6767215d12c0c5ebc751b6386c50c3a9d5ba0792e0396e2ae420b736a5ab0121032765788cbacba654f3cdf679026db3cdb5a5d0310f870f4da223926a2813259902483045022100bceed8f98e1fbed310cfc01e2bdd3dcbd146064c6fc9d423e787367602d8df7c02204e300e2c9e6c4be50f2c9db54c2f6aeb5eae1897db38bf6406f16ee2e3de4f5e0121032765788cbacba654f3cdf679026db3cdb5a5d0310f870f4da223926a2813259901414045d5e077660865bde16a0af320b426f636b49ea7674f1cc1a9d7c86643638cdbb21c9a281c79aeaa118d833840eca4ec0b3cc6a807f1900bb7395ab4eb72ff830247304402204fdc2e31b88b42081c42dc6c249297aeffe1147071fa6994f95ded6257d93d9602205bf9c30fc7cf0d2b78a1e0027e1cc4eae6e45aef7281017fac932058ee1674d70121032765788cbacba654f3cdf679026db3cdb5a5d0310f870f4da223926a281325990000000001012b4a01000000000000225120a9af1fae42dbe9b0604e0ed64ae6e26d56d1c9bbcc4e1e66326914bdb51496060117207f003df863d4c17579f7b22f7498e39525b81c947aef234859b0466d3dd8dfb3000100fd2603020000000001048a841e4104389e3b5b04c1522ed7bfc0e64c3760cb801a2ee2406d1c2373d81d0800000000ffffffff8a841e4104389e3b5b04c1522ed7bfc0e64c3760cb801a2ee2406d1c2373d81d0500000000ffffffff9584f0b205109f6790fbf08f782166892f22facb8e2598b705cd9136b25aabc30000000000ffffffff4f4b0bbc053b9e3cb6261cb83c063481eba8a8a32a448c63f1c1218d6fe330380600000000ffffffff07b004000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed02202000000000000225120b1a548f1672b6bc666e23943b0b138a4216e2ce5f9bc687469e0f52a917bbf2775120000000000002251208ef171ead3a8f91d9dca9ae4b645537aafada16559f9f714cbe524ef672ab4144402000000000000160014c015c65276d5f38d599d445c4cb03aa7aa0dc3655802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed05802000000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed04057060000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed002483045022100a5cf71b0115db7980055fa9158c24fca9e3cc09cca0d1a0c54f17e8fa92d3e2402201b7dce27ff09e06c40a87b54aa2db693f49291e2f1a32f055155a3750607d01e0121028b8437ac47a4434d4b86d19ab7eeb255887f75cfa43ca135168bfc8281ae8bb902473044022049edbc2650ed737bad10d567c072da54c6b592fcfe2240fabcd49d8aa176422102206c8101ba6f8d6a155ed916ca7b25bf69395d76c020cb6ed9cf8293d65870346e0121028b8437ac47a4434d4b86d19ab7eeb255887f75cfa43ca135168bfc8281ae8bb901410a4a56764a5f9ed3fee4a869f6edee7b9b72f87bdba96d08310191a6df94bcf56f32152e63a836844cea71611886e3f96f74a2cc06d88ddf508184857f5fb4138302473044022075b779781668f4a487c2308aca9217125b897d089f617d831122612db02a672402200ef61ec6ddb6cac999e528e9e4fe27acb0a1cba4f8b33204b180ee458a8d82790121028b8437ac47a4434d4b86d19ab7eeb255887f75cfa43ca135168bfc8281ae8bb90000000001011f4057060000000000160014b2c9b35e4b02e8b16236ae5c0540cf2d56605ed00000000000000000';

const PSBT =
'cHNidP8BAF4CAAAAAa/v4ZPYjm+iJc1pB3IybYY6wPpScPDlxvHmNE557J2vAQAAAAD/////AWqKAAAAAAAAIlEgZDcUdAs/gCZIkazJyMw1I54n2QGxN1W2ph6m+4zYHBkAAAAACPwCbWUDc2lnQE+yrULMRi3UwxQDf8idtfykJVzjE08jIP9fdU/6yvEfdlqCAWNwXFgSx1Nb7jrfPFYlY7gLaQ87EpcDpwaLdzQL/AJtZQZzaWdleHAIQnj3E+QqoAAAAQErIgIAAAAAAAAiUSBkNxR0Cz+AJkiRrMnIzDUjnifZAbE3VbamHqb7jNgcGQEDBIMAAAABFyCauIGVY+9bxYwyEp3poW+sSayOhwQuSrI4DnH80/zCuwAA';

/**
* Component to sign a message.
* @returns {JSX.Element | null} The rendered component.
*/
const SignMessage: React.FC = () => {
const { signMessage, account } = useBitcoinWallet();

return account ? (
<Button
onClick={async () => {
Expand All @@ -32,19 +41,17 @@ const SignMessage: React.FC = () => {

/**
* Component to sign a PSBT (Partially Signed Bitcoin Transaction).
* @returns {JSX.Element | null} The rendered component.
*/
const SignPsbt: React.FC = () => {
const { signPsbt, account } = useBitcoinWallet();
const { signPsbt, account, name: walletName } = useBitcoinWallet();

// You can use libraries like 'bitcoinjs-lib' or 'scure-btc-signer' to create PSBTs.
// This is a real PSBT base64 data for example.
const psbt =
'cHNidP8BAF4CAAAAAa/v4ZPYjm+iJc1pB3IybYY6wPpScPDlxvHmNE557J2vAQAAAAD/////AWqKAAAAAAAAIlEgZDcUdAs/gCZIkazJyMw1I54n2QGxN1W2ph6m+4zYHBkAAAAACPwCbWUDc2lnQE+yrULMRi3UwxQDf8idtfykJVzjE08jIP9fdU/6yvEfdlqCAWNwXFgSx1Nb7jrfPFYlY7gLaQ87EpcDpwaLdzQL/AJtZQZzaWdleHAIQnj3E+QqoAAAAQErIgIAAAAAAAAiUSBkNxR0Cz+AJkiRrMnIzDUjnifZAbE3VbamHqb7jNgcGQEDBIMAAAABFyCauIGVY+9bxYwyEp3poW+sSayOhwQuSrI4DnH80/zCuwAA';
return account ? (
<Button
onClick={async () => {
try {
// You can use libraries like 'bitcoinjs-lib' or 'scure-btc-signer' to create PSBTs.
// This is a real PSBT base64 data for example.
const psbt = walletName === metadata_Phantom.name ? PSBT_FOR_PHANTOM : PSBT;
const result = await signPsbt?.({ psbt });
console.log('sign psbt success!', result);
} catch (error) {
Expand All @@ -59,11 +66,12 @@ const SignPsbt: React.FC = () => {

/**
* Main application component that sets up the BitcoinWeb3ConfigProvider and Connector.
* @returns {JSX.Element} The rendered application component.
*/
const App: React.FC = () => {
return (
<BitcoinWeb3ConfigProvider wallets={[XverseWallet(), UnisatWallet(), OkxWallet()]}>
<BitcoinWeb3ConfigProvider
wallets={[XverseWallet(), UnisatWallet(), OkxWallet(), PhantomWallet()]}
>
<Space>
<Connector>
<ConnectButton />
Expand Down
5 changes: 5 additions & 0 deletions packages/web3/src/bitcoin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@ The wallets currently supported are as follows, and we also welcome you to submi
- [Xverse](https://docs.xverse.app/sats-connect)
- [OKX](https://www.okx.com/web3/build/docs/sdks/chains/bitcoin/provider)
- [Unisat](https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet)
- [Phantom](https://docs.phantom.app/bitcoin/integrating-phantom)

## WalletConnect

<code src="./demos/basic.tsx"></code>

## SignMessage / SignPsbt

> Phantom currently only provides partial support for PSBT, please test it fully before using it.

<code src="./demos/sign.tsx"></code>

## SendTransfer

> If the wallet does not yet support `sendTransfer`, a `NotImplementedError` will be thrown.

<code src="./demos/send-transfer.tsx"></code>

## GetInscriptions
Expand Down
5 changes: 5 additions & 0 deletions packages/web3/src/bitcoin/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@ Ant Design Web3 官方提供了 `@ant-design/web3-bitcoin` 来适配比特币,
- [Xverse](https://docs.xverse.app/sats-connect)
- [OKX](https://www.okx.com/web3/build/docs/sdks/chains/bitcoin/provider)
- [Unisat](https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet)
- [Phantom](https://docs.phantom.app/bitcoin/integrating-phantom)

## 连接钱包

<code src="./demos/basic.tsx"></code>

## 签名 / PSBT

> Phantom 目前仅对 PSBT 提供部分支持,使用之前请充分测试。
gin-lsl marked this conversation as resolved.
Show resolved Hide resolved

<code src="./demos/sign.tsx"></code>

## 发送交易

> 如果钱包暂未支持 `sendTransfer`,将会抛出 `NotImplementedError`。

<code src="./demos/send-transfer.tsx"></code>

## 获取铭文信息展示
Expand Down
Loading
Loading