├── README.md
├── brc20-market-demo
├── .gitignore
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── ApiKeyInput.tsx
│ │ ├── Brc20Item.tsx
│ │ ├── BuyConfirmAlert.tsx
│ │ ├── ConnectWallet.tsx
│ │ ├── InscribeTransfer.tsx
│ │ └── NetworkSwitch.tsx
│ ├── index.tsx
│ ├── page
│ │ ├── Assets.tsx
│ │ └── Listed.tsx
│ ├── provider
│ │ ├── MarketProvider.tsx
│ │ ├── NetworkProvider.tsx
│ │ └── UniSatProvider.tsx
│ ├── react-app-env.d.ts
│ ├── style
│ │ ├── App.css
│ │ └── index.css
│ ├── types.d.ts
│ └── utils
│ │ ├── brc20Api.ts
│ │ ├── httpUtils.ts
│ │ ├── marketApi.ts
│ │ ├── networkUtils.ts
│ │ ├── unisatUtils.ts
│ │ └── utils.ts
├── tsconfig.json
└── yarn.lock
├── brc20-swap-demo
├── .gitignore
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── ApiKeyInput.tsx
│ │ ├── ConfirmSwap.tsx
│ │ ├── CreatePools.tsx
│ │ ├── Deposit.tsx
│ │ └── Swap.tsx
│ ├── constants.ts
│ ├── index.tsx
│ ├── logo.svg
│ ├── provider
│ │ └── UniSatProvider.tsx
│ ├── styles
│ │ ├── App.css
│ │ └── index.css
│ ├── types.d.ts
│ └── utils
│ │ ├── brc20Api.ts
│ │ ├── httpUtils.ts
│ │ ├── swapApi.ts
│ │ ├── unisatUtils.ts
│ │ └── utils.ts
├── tsconfig.json
└── yarn.lock
├── unisat-inscribe-demo
├── .gitignore
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── ApiKeyInput.tsx
│ │ ├── FeeDetail.tsx
│ │ ├── InscribeFileInput.tsx
│ │ ├── InscribeTextInput.tsx
│ │ ├── NetworkSwitch.tsx
│ │ ├── OrderDetail.tsx
│ │ ├── OrderFiles.tsx
│ │ ├── OrderInscribing.tsx
│ │ ├── OrderPay.tsx
│ │ ├── OrderStatus.tsx
│ │ └── SimpleRow.tsx
│ ├── index.tsx
│ ├── logo.svg
│ ├── pages
│ │ ├── Inscribe.tsx
│ │ └── OrderList.tsx
│ ├── provider
│ │ └── UniSatProvider.tsx
│ ├── react-app-env.d.ts
│ ├── style
│ │ ├── App.css
│ │ └── index.css
│ ├── types.ts
│ └── utils
│ │ ├── api-types.ts
│ │ ├── api.ts
│ │ ├── httpUtils.ts
│ │ ├── orderUtils.ts
│ │ ├── unisatUtils.ts
│ │ └── utils.ts
├── tsconfig.json
└── yarn.lock
├── unisat-web3-demo
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ └── setupTests.ts
└── tsconfig.json
└── wallet-sdk-examples
├── .babelrc.js
├── .eslintrc.cjs
├── .gitignore
├── LICENSE
├── README.md
├── example
├── brc20-5byte-mint.ts
├── create-bid.ts
├── create-put-on.ts
├── mempool-api
│ └── index.ts
├── open-api
│ ├── index.ts
│ └── types.ts
├── send-btc.ts
└── send-inscription.ts
├── gulpfile.js
├── package.json
├── tsconfig.json
└── yarn.lock
/README.md:
--------------------------------------------------------------------------------
1 | ## UniSat Developer Service
2 |
3 | UniSat Developer Service is open to community developers, allowing you to explore the world of Bitcoin and Ordinals. You can deploy your own inscribing services, build wallet applications, develop browsers, and much more using the API.
4 |
5 | - [Dev Documentation](https://docs.unisat.io/dev/unisat-developer-service): OpenAPI, Game Framework, etc...
6 | - [**Getting an API Key**](https://docs.unisat.io/dev/unisat-developer-service#getting-an-api-key)
7 | - [Dev Support Telegram](https://t.me/+w3I7K-OLj4JmODM1)
8 |
9 | ### Interactive Examples for UniSat APIs
10 |
11 | #### UniSat Marketplace API Demo
12 |
13 | - [Documentation](https://docs.unisat.io/dev/unisat-developer-service/unisat-marketplace)
14 | - [Demo](https://demo-market.unisat.io)
15 | - [Source](./brc20-market-demo)
16 |
17 | #### UniSat Inscribe API Demo
18 |
19 | - [Documentation](https://docs.unisat.io/dev/unisat-developer-service/unisat-inscribe)
20 | - [Demo](https://demo-inscribe.unisat.io)
21 | + [Source](./unisat-inscribe-demo)
22 |
23 | #### UniSat Wallet API Demo
24 |
25 | - [Documentation](https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet)
26 | - [Demo](https://demo.unisat.io)
27 | + [Source](./unisat-web3-demo)
28 |
29 | #### brc20-swap API Demo
30 |
31 | - [Documentation](https://docs.unisat.io/dev/unisat-developer-service/brc20-swap)
32 | - [Demo](https://demo-swap.unisat.io)
33 | + [Source](./brc20-swap-demo)
34 |
35 |
--------------------------------------------------------------------------------
/brc20-market-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/brc20-market-demo/README.md:
--------------------------------------------------------------------------------
1 | ## UniSat Marketplace API Demo
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ### Run the demo
6 |
7 | #### Install dependencies
8 |
9 | ```bash
10 | yarn
11 | ```
12 |
13 | #### Run the app
14 |
15 | ```bash
16 | yarn start
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/brc20-market-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "brc20-market-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@types/jest": "^27.0.1",
7 | "@types/node": "^16.7.13",
8 | "@types/react": "^18.0.0",
9 | "@types/react-dom": "^18.0.0",
10 | "ahooks": "^3.7.10",
11 | "antd": "^5.13.3",
12 | "axios": "^1.6.7",
13 | "buffer": "^6.0.3",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-scripts": "5.0.1",
17 | "typescript": "^4.4.2"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/brc20-market-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-market-demo/public/favicon.ico
--------------------------------------------------------------------------------
/brc20-market-demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/brc20-market-demo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-market-demo/public/logo192.png
--------------------------------------------------------------------------------
/brc20-market-demo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-market-demo/public/logo512.png
--------------------------------------------------------------------------------
/brc20-market-demo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/brc20-market-demo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style/App.css';
3 | import {ApiKeyInput} from "./components/ApiKeyInput";
4 | import {NetworkSwitch} from "./components/NetworkSwitch";
5 | import {Card, Input, Layout, Space, Tabs, Typography} from "antd";
6 | import {ConnectWallet} from "./components/ConnectWallet";
7 | import {Assets} from "./page/Assets";
8 | import {useMarket} from "./provider/MarketProvider";
9 | import {isTicketValid} from "./utils/utils";
10 | import {Listed} from "./page/Listed";
11 | import {useLocalStorageState} from "ahooks";
12 |
13 | function App() {
14 | const {tick, setTick} = useMarket();
15 |
16 |
17 | const [apiKey, setApiKey] = useLocalStorageState('apiKey',
18 | {defaultValue: '', deserializer: (val) => val, serializer: (val) => val})
19 |
20 |
21 | return (
22 |
23 |
24 |
64 |
65 |
66 | );
67 | }
68 |
69 | export default App;
70 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/ApiKeyInput.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Input} from "antd";
2 | import React from "react";
3 | import {setApiKey} from "../utils/httpUtils";
4 |
5 | export function ApiKeyInput({apiKey,setKey}:{
6 | apiKey: string |undefined,
7 | setKey: (apiKey: string) => void
8 | }) {
9 |
10 | return
11 |
12 | Set API key:
13 |
14 |
15 | {
19 | const key = e.target.value;
20 | setKey(key);
21 | setApiKey(key);
22 | }}
23 | />
24 |
25 |
26 |
27 |
28 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/Brc20Item.tsx:
--------------------------------------------------------------------------------
1 | import {Space, Statistic} from "antd";
2 |
3 | export function Brc20Item({tick, amount}: {
4 | tick: string,
5 | amount: string | number,
6 | }) {
7 | return
14 |
18 |
19 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/BuyConfirmAlert.tsx:
--------------------------------------------------------------------------------
1 | import {BRC20ListItem, CreateBidPrepareRes, marketApi} from "../utils/marketApi";
2 | import {Col, Flex, message, Modal, Row, Skeleton, Statistic} from "antd";
3 | import {Brc20Item} from "./Brc20Item";
4 | import {useEffect, useState} from "react";
5 | import {useUnisat} from "../provider/UniSatProvider";
6 | import {handleError} from "../utils/utils";
7 |
8 |
9 | export function BuyConfirmAlert({item, close, onComplete}: {
10 | item: BRC20ListItem,
11 | close: () => void,
12 | onComplete?: () => void
13 | }) {
14 | const {address, pubkey, signPsbt} = useUnisat();
15 | const [messageApi] = message.useMessage();
16 |
17 | const [isLoading, setIsLoading] = useState(false);
18 | const [bidPrepare, setBidPrepare] = useState();
19 |
20 | useEffect(() => {
21 | if (!address || !pubkey || !item)
22 | return;
23 | // create bid prepare
24 | marketApi.createBidPrepare({
25 | auctionId: item.auctionId,
26 | bidPrice: item.price,
27 | address,
28 | pubkey
29 | }).then(setBidPrepare).catch(handleError)
30 |
31 | }, [address, item, pubkey]);
32 |
33 | async function buy() {
34 | try {
35 | setIsLoading(true)
36 |
37 | // create bid to get bidId/psbtBid
38 | const {bidId, psbtBid} = await marketApi.createBid({
39 | auctionId: item.auctionId,
40 | bidPrice: item.price,
41 | address,
42 | pubkey,
43 | })
44 |
45 | // sign psbt
46 | const signedPsbt = await signPsbt(psbtBid)
47 |
48 | // confirm
49 | await marketApi.confirmBid({
50 | auctionId: item.auctionId,
51 | bidId,
52 | psbtBid: signedPsbt,
53 | psbtBid2: '',
54 | psbtSettle: '',
55 | })
56 |
57 | messageApi.success('Buy Success');
58 |
59 | onComplete && onComplete()
60 | } catch (e) {
61 | handleError(e)
62 | } finally {
63 | setIsLoading(false)
64 | }
65 | }
66 |
67 | return
74 | {
75 | item && <>
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {
86 | !bidPrepare ? : <>
87 |
88 |
91 |
92 | >
93 | }
94 |
95 |
96 | >
97 |
98 | }
99 |
100 |
101 | }
102 |
103 | function KeyValue({label, value, suffix, fontSize = 14, valueColor}: {
104 | label: string,
105 | value: string | number,
106 | suffix?: string,
107 | fontSize?: number,
108 | valueColor?: string
109 | }) {
110 | return <>
111 |
112 | {label}
113 |
114 |
115 |
119 |
120 | >
121 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/ConnectWallet.tsx:
--------------------------------------------------------------------------------
1 | import {useUnisat} from "../provider/UniSatProvider";
2 | import {Button, Card} from "antd";
3 |
4 | export function ConnectWallet() {
5 | const {address, connect} = useUnisat();
6 |
7 | return
8 | {
9 | address
10 | ? Address: {address}
11 | :
12 | }
13 |
14 |
15 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/InscribeTransfer.tsx:
--------------------------------------------------------------------------------
1 | import {handleError, sleep} from "../utils/utils";
2 | import {unisatUtils} from "../utils/unisatUtils";
3 | import {Button} from "antd";
4 |
5 | export function InscribeTransfer({tick, onInscribed}: { tick: string, onInscribed: () => void }) {
6 |
7 |
8 | async function inscribeTransfer() {
9 | try {
10 | await unisatUtils.inscribeTransfer(tick)
11 | await sleep(5000)
12 | onInscribed()
13 | } catch (e) {
14 | handleError(e)
15 | }
16 | }
17 |
18 | return
21 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/components/NetworkSwitch.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Radio} from "antd";
2 | import {NetworkType, networkUtils} from "../utils/networkUtils";
3 | import {useNetwork} from "../provider/NetworkProvider";
4 |
5 |
6 | export function NetworkSwitch() {
7 | const {network, setNetwork} = useNetwork()
8 | return
9 | {
10 | networkUtils.setNetworkType(v.target.value)
11 | setNetwork(v.target.value)
12 | }} defaultValue={network}>
13 | Testnet
14 | Mainnet
15 |
16 |
17 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './style/index.css';
4 | import App from './App';
5 | import {ConfigProvider} from "antd";
6 | import NetworkProvider from "./provider/NetworkProvider";
7 | import UnisatProvider from "./provider/UniSatProvider";
8 | import MarketProvider from "./provider/MarketProvider";
9 |
10 | const root = ReactDOM.createRoot(
11 | document.getElementById('root') as HTMLElement
12 | );
13 | root.render(
14 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/page/Assets.tsx:
--------------------------------------------------------------------------------
1 | import {useUnisat} from "../provider/UniSatProvider";
2 | import {useCallback, useEffect, useMemo, useState} from "react";
3 | import {brc20Api, Brc20InscriptionsItem} from "../utils/brc20Api";
4 | import {handleError, isTicketValid, sleep} from "../utils/utils";
5 | import {useMarket} from "../provider/MarketProvider";
6 | import {Button, Col, Flex, InputNumber, message, Modal, Row, Skeleton, Space} from "antd";
7 | import {Brc20Item} from "../components/Brc20Item";
8 | import {InscribeTransfer} from "../components/InscribeTransfer";
9 | import {InscriptionType, ListItem, marketApi} from "../utils/marketApi";
10 | import {unisatUtils} from "../utils/unisatUtils";
11 |
12 | export function Assets() {
13 |
14 | const {address, signPsbt} = useUnisat();
15 | const {tick} = useMarket();
16 |
17 | const [list, setList] = useState();
18 | const [isLoading, setIsLoading] = useState(false);
19 |
20 | const [putOnItem, setPutOnItem] = useState();
21 | const [unitPrice, setUnitPrice] = useState('');
22 |
23 | const [listedMap, setListedMap] = useState<{ [inscriptionId: string]: ListItem }>({});
24 |
25 | const refreshTransfer = useCallback(() => {
26 | if (address && tick && isTicketValid(tick)) {
27 | //got transfer-inscription from the user
28 | setList(undefined);
29 | brc20Api.getAddressTransferable({
30 | address,
31 | tick,
32 | start: 0,
33 | limit: 200,
34 | }).then(res => {
35 | setList(res.detail)
36 | }).catch(handleError)
37 | } else {
38 | setList([])
39 | }
40 | }, [address, tick]);
41 |
42 | useEffect(() => {
43 | refreshTransfer()
44 | }, [refreshTransfer]);
45 |
46 | const refreshListed = useCallback(() => {
47 | setListedMap({})
48 | if (tick && isTicketValid(tick) && list) {
49 | // get listed to show unlist button
50 | marketApi.listBrc20({
51 | filter: {tick, nftType: InscriptionType.brc20, address, isEnd: false},
52 | sort: {unitPrice: 1},
53 | start: 0,
54 | limit: 99,
55 | }).then(res => {
56 | setListedMap(res.list.reduce((acc: any, cur) => {
57 | acc[cur.inscriptionId] = cur;
58 | return acc;
59 | }, {}))
60 | }).catch(handleError)
61 | }
62 | }, [address, list, tick]);
63 |
64 | useEffect(() => {
65 | refreshListed()
66 | }, [refreshListed]);
67 |
68 |
69 | const totalPrice = useMemo(() => {
70 | if (!unitPrice) return '';
71 | if (!putOnItem) return '0';
72 | return (Number(putOnItem.data.amt) * Number(unitPrice)).toFixed(0);
73 | }, [putOnItem, unitPrice]);
74 |
75 |
76 | async function putOn() {
77 | try {
78 | if (!putOnItem) return;
79 | setIsLoading(true)
80 |
81 | const pubkey = await unisatUtils.getPublicKey();
82 |
83 | const {auctionId, psbt} = await marketApi.createPutOn({
84 | inscriptionId: putOnItem.inscriptionId,
85 | nftType: InscriptionType.brc20,
86 | initPrice: totalPrice,
87 | marketType: 'fixedPrice',
88 | pubkey,
89 | unitPrice,
90 | })
91 |
92 | const signedPsbt = await signPsbt(psbt)
93 |
94 | await marketApi.confirmPutOn({
95 | auctionId,
96 | psbt: signedPsbt,
97 | })
98 |
99 | message.success('Put on success');
100 |
101 | setPutOnItem(undefined);
102 | setUnitPrice('');
103 |
104 | await sleep(1000);
105 | refreshListed();
106 |
107 | } catch (e) {
108 | handleError(e)
109 | } finally {
110 | setIsLoading(false)
111 | }
112 | }
113 |
114 | async function putOff(item: ListItem) {
115 | try {
116 | setIsLoading(true)
117 |
118 | const {psbt} = await marketApi.createPutOff({
119 | auctionId: item.auctionId,
120 | })
121 |
122 | const signedPsbt = await signPsbt(psbt)
123 |
124 | await marketApi.confirmPutOff({
125 | auctionId: item.auctionId,
126 | psbt: signedPsbt,
127 | })
128 |
129 | message.success('Put off success');
130 |
131 | await sleep(3000)
132 |
133 | refreshTransfer()
134 | } catch (e) {
135 | handleError(e)
136 | } finally {
137 | setIsLoading(false)
138 | }
139 | }
140 |
141 | if (!address) {
142 | return Wait for Connect
143 | }
144 |
145 | if (!list) {
146 | return
147 | }
148 |
149 | return
150 |
151 |
152 |
153 | Transferable inscriptions:
154 |
155 |
156 | {
157 | list.map((item, index) => {
158 | const listed = listedMap[item.inscriptionId];
159 |
160 | return
161 |
162 | {
163 | listed
164 | ?
166 | :
167 | }
168 |
169 | })
170 | }
171 |
172 | {
179 | if (!isLoading)
180 | setPutOnItem(undefined)
181 | }}
182 | >
183 | {
184 | putOnItem && <>
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Unit Price:
193 |
194 |
195 | {
201 | setUnitPrice(v || '')
202 | }}
203 | addonAfter={`sats(BTC)/${putOnItem?.data.tick}`}
204 | />
205 |
206 |
207 |
208 | Total Price
209 |
210 |
211 |
218 |
219 |
220 | >
221 | }
222 |
223 |
224 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/page/Listed.tsx:
--------------------------------------------------------------------------------
1 | import {useMarket} from "../provider/MarketProvider";
2 | import {useCallback, useEffect, useState} from "react";
3 | import {BRC20ListItem, InscriptionType, marketApi} from "../utils/marketApi";
4 | import {handleError, isTicketValid} from "../utils/utils";
5 | import {Button, Flex, Pagination, Skeleton, Space, Statistic} from "antd";
6 | import {BuyConfirmAlert} from "../components/BuyConfirmAlert";
7 | import {useNetwork} from "../provider/NetworkProvider";
8 |
9 | const pageSize = 10;
10 |
11 | export function Listed() {
12 |
13 | const {network} = useNetwork();
14 | const {tick,} = useMarket();
15 |
16 | const [total, setTotal] = useState(0); // total listed
17 | const [page, setPage] = useState(1);
18 | const [list, setList] = useState();
19 |
20 | const [buyItem, setBuyItem] = useState();
21 |
22 | const refreshData = useCallback(() => {
23 | if (tick && isTicketValid(tick)) {
24 | // get listed
25 | setList(undefined)
26 | marketApi.listBrc20({
27 | filter: {
28 | tick,
29 | nftType: InscriptionType.brc20,
30 | isEnd: false // only show listed
31 | },
32 | sort: {unitPrice: 1},
33 | start: (page - 1) * pageSize,
34 | limit: pageSize
35 | }).then(res => {
36 | setTotal(res.total)
37 | setList(res.list)
38 | }).catch(handleError)
39 | } else {
40 | setList([])
41 | }
42 | }, [page, tick]);
43 |
44 | useEffect(() => {
45 | refreshData()
46 | }, [refreshData, network]);
47 |
48 |
49 | if (!list) return
50 |
51 | return <>
52 |
53 | {list.map(item => {
54 | return
62 |
67 |
73 |
78 |
79 |
80 | })}
81 |
82 | {
88 | setPage(page)
89 | }}
90 | />
91 | {
92 | buyItem && {
95 | setBuyItem(undefined)
96 | }}
97 | onComplete={refreshData}
98 | />
99 | }
100 | >
101 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/provider/MarketProvider.tsx:
--------------------------------------------------------------------------------
1 | import {createContext, useContext, useState} from "react";
2 |
3 | interface MarketContentType {
4 | tick: string;
5 | setTick: (tick: string) => void;
6 | }
7 |
8 | const MarketContext = createContext({
9 | tick: '',
10 | setTick: () => {
11 | }
12 | })
13 |
14 | export function useMarket() {
15 | const context = useContext(MarketContext);
16 | if (!context) {
17 | throw Error('Feature flag hooks can only be used by children of MarketProvider.');
18 | } else {
19 | return context;
20 | }
21 | }
22 |
23 | export default function MarketProvider({children}: {
24 | children: React.ReactNode
25 | }) {
26 | const [tick, setTick] = useState('sats')
27 |
28 | return
32 | {children}
33 |
34 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/provider/NetworkProvider.tsx:
--------------------------------------------------------------------------------
1 | import {NetworkType} from "../utils/networkUtils";
2 | import {createContext, ReactNode, useContext, useState} from "react";
3 |
4 | interface NetworkContextType {
5 | network: NetworkType;
6 | setNetwork: (network: NetworkType) => void;
7 | }
8 |
9 | const NetworkContext = createContext({
10 | network: NetworkType.testnet,
11 | setNetwork: () => {}
12 | })
13 |
14 | export function useNetwork() {
15 | const context = useContext(NetworkContext);
16 | if (!context) {
17 | throw Error('Feature flag hooks can only be used by children of NetworkProvider.');
18 | } else {
19 | return context;
20 | }
21 | }
22 |
23 | export default function NetworkProvider({children}: {
24 | children: ReactNode
25 | }) {
26 | const [network, setNetwork] = useState(NetworkType.testnet)
27 |
28 | return
32 | {children}
33 |
34 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/provider/UniSatProvider.tsx:
--------------------------------------------------------------------------------
1 | import {createContext, ReactNode, useCallback, useContext, useEffect, useState} from "react";
2 | import {handleError, sleep} from "../utils/utils";
3 | import {unisatUtils} from "../utils/unisatUtils";
4 | import {useNetwork} from "./NetworkProvider";
5 |
6 | interface UnisatContextType {
7 | isInstalled: boolean;
8 | isConnected: boolean;
9 | address: string;
10 | pubkey: string;
11 | connect: () => void;
12 | signMessage: (msg: string) => Promise;
13 | signPsbt: (psbt: string) => Promise;
14 | }
15 |
16 | const UnisatContext = createContext({
17 | isInstalled: false,
18 | isConnected: false,
19 | address: '',
20 | pubkey: '',
21 | connect: () => {
22 | },
23 | signMessage: (msg: string) => Promise.resolve(''),
24 | signPsbt: (psbt: string) => Promise.resolve('')
25 | })
26 |
27 |
28 | export function useUnisat() {
29 | const context = useContext(UnisatContext);
30 | if (!context) {
31 | throw Error('Feature flag hooks can only be used by children of UnisatProvider.');
32 | } else {
33 | return context;
34 | }
35 | }
36 |
37 | export default function UnisatProvider({children}: {
38 | children: ReactNode
39 | }) {
40 | const {network} = useNetwork();
41 | const [isInstalled, setIsInstalled] = useState(false)
42 | const [isConnected, setIsConnected] = useState(false)
43 | const [address, setAddress] = useState('')
44 | const [pubkey, setPubkey] = useState('')
45 |
46 | useEffect(() => {
47 |
48 | async function init() {
49 | let install = !!window.unisat;
50 | setIsInstalled(install);
51 |
52 | // 额外检查
53 | for (let i = 0; i < 10 && !install; i += 1) {
54 | await sleep(100 + i * 100);
55 | install = !!window.unisat;
56 | if (install) {
57 | setIsInstalled(install);
58 | break;
59 | }
60 | }
61 |
62 | if (install) {
63 | const address = await unisatUtils.getAccounts()
64 | if (address) {
65 | setPubkey(await unisatUtils.getPublicKey())
66 | // connected
67 | setIsConnected(true)
68 | setAddress(address)
69 | }
70 | }
71 | }
72 |
73 | init().then().catch(handleError);
74 |
75 | }, []);
76 |
77 | const connect = useCallback(async () => {
78 | try {
79 | await unisatUtils.checkNetwork(network);
80 | const address = await unisatUtils.requestAccounts();
81 | if (address) {
82 | setPubkey(await unisatUtils.getPublicKey())
83 | setIsConnected(true)
84 | setAddress(address)
85 | }
86 | } catch (e) {
87 | handleError(e)
88 | }
89 |
90 | }, [network])
91 |
92 | useEffect(() => {
93 | async function onAppNetworkChange() {
94 | try {
95 | await unisatUtils.checkNetwork(network);
96 | const address = await unisatUtils.getAccounts()
97 | if (address) {
98 | setAddress(address)
99 | }
100 | } catch (e) {
101 | handleError(e)
102 | }
103 | }
104 |
105 | if (isConnected) {
106 | onAppNetworkChange().then()
107 | }
108 | }, [isConnected, network]);
109 |
110 | const signMessage = useCallback((msg: string) => {
111 | return unisatUtils.signMessage(msg, 'bip322-simple')
112 | }, [])
113 |
114 | const signPsbt = useCallback((psbt: string) => {
115 | return unisatUtils.signPsbt(psbt)
116 | }, []);
117 |
118 | const value = {
119 | isInstalled,
120 | isConnected,
121 | address,
122 | pubkey,
123 | connect,
124 | signMessage,
125 | signPsbt,
126 | }
127 |
128 | return
129 | {children}
130 |
131 |
132 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/style/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/style/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | --main-color: #ebb94c;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export type UnisatWalletInterface = {
2 | getAccounts(): Promise;
3 | requestAccounts(): Promise;
4 | getNetwork(): Promise;
5 | switchNetwork(network: string): Promise;
6 | sendBitcoin(address: string, amount: number, options: any): Promise;
7 | on(event: string, listener: Function): void;
8 | removeListener(event: string, listener: Function): void;
9 | signMessage(message: string, type?: string): Promise;
10 | signPsbt(psbt: string, opt: { autoFinalized: boolean }): Promise;
11 | getPublicKey(): Promise;
12 | getBalance(): Promise<{
13 | confirmed: number,
14 | unconfirmed: number,
15 | total: number
16 | }>;
17 | inscribeTransfer(tick: string, amount?: number | string): Promise<{
18 | amount: string,
19 | inscriptionId: string
20 | inscriptionNumber: number
21 | ticker: string
22 | }>;
23 | getInscriptions(num: number): Promise;
24 | }
25 |
26 | declare global {
27 | interface Window {
28 | unisat: UnisatWalletInterface;
29 | }
30 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/brc20Api.ts:
--------------------------------------------------------------------------------
1 | import {get} from "./httpUtils";
2 | import {stringToHex} from "./utils";
3 |
4 | export const brc20Api = {
5 | summary({
6 | address, limit = 20, start = 0,
7 | }: { address: string, limit?: number, start?: number }): Promise<{
8 | total: number,
9 | start: number,
10 | height: number,
11 | detail: AddressBrc20Balance[]
12 | }> {
13 | return get(`/v1/indexer/address/${address}/brc20/summary`, {limit, start})
14 | },
15 |
16 | getAddressTransferable({
17 | address, tick, start = 0, limit = 512,
18 | }: {
19 | address: string,
20 | tick: string,
21 | start?: number,
22 | limit?: number
23 | }): Promise {
24 | return get(`/v1/indexer/address/${address}/brc20/${stringToHex(tick)}/transferable-inscriptions`, {
25 | limit,
26 | start,
27 | })
28 | },
29 | }
30 |
31 |
32 | export type AddressBrc20Balance = {
33 | ticker: string
34 | overallBalance: string // transferableBalance + availableBalance
35 | transferableBalance: string
36 | availableBalance: string
37 | }
38 |
39 |
40 | export type Brc20Data = {
41 | amt: string,
42 | decimal: string,
43 | lim: string,
44 | max: string,
45 | minted: string,
46 | op: string,
47 | tick: string,
48 | to: string
49 | }
50 | export type Brc20InscriptionsItem = {
51 | data: Brc20Data,
52 | inscriptionId: string,
53 | inscriptionNumber: number,
54 | confirmations: number,
55 | isPutOn?: boolean,
56 | };
57 |
58 | export type Brc20AddressTransferable = {
59 | start: number,
60 | total: number,
61 | detail: Brc20InscriptionsItem[]
62 | };
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/httpUtils.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {networkUtils} from "./networkUtils";
3 |
4 | let apiKey = localStorage.getItem('apiKey') || '';
5 |
6 | export function setApiKey(key: string) {
7 | apiKey = key;
8 | }
9 |
10 | function createApi(baseURL: string) {
11 | const api = axios.create({
12 | baseURL,
13 | timeout: 10000,
14 | headers: {
15 | "Content-Type": "application/json",
16 | },
17 | })
18 |
19 | api.interceptors.request.use((config) => {
20 | if (!apiKey) {
21 | throw new Error("input apiKey and reload page");
22 | }
23 | config.headers.Authorization = `Bearer ${apiKey}`;
24 | return config;
25 | });
26 | return api;
27 | }
28 |
29 | const mainnetApi = createApi("https://open-api.unisat.io");
30 | const testnetApi = createApi("https://open-api-testnet.unisat.io");
31 |
32 | function getApi() {
33 | return networkUtils.isTestnet() ? testnetApi : mainnetApi;
34 | }
35 |
36 |
37 | export const get = async (url: string, params?: any) => {
38 | const res = await getApi().get(url, {params});
39 | if (res.status !== 200) {
40 | throw new Error(res.statusText);
41 | }
42 |
43 | const responseData = res.data;
44 |
45 | if (responseData.code !== 0) {
46 | throw new Error(responseData.msg);
47 | }
48 | return responseData.data;
49 | };
50 |
51 | export const post = async (url: string, data?: any) => {
52 | const res = await getApi().post(url, data,);
53 | if (res.status !== 200) {
54 | throw new Error(res.statusText);
55 | }
56 |
57 | const responseData = res.data;
58 |
59 | if (responseData.code !== 0) {
60 | throw new Error(responseData.msg);
61 | }
62 |
63 | return responseData.data;
64 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/marketApi.ts:
--------------------------------------------------------------------------------
1 | import {post} from "./httpUtils";
2 |
3 | export const marketApi = {
4 | listBrc20(req: ListReq): Promise {
5 | return post('/v3/market/brc20/auction/list', req,)
6 | },
7 |
8 | createPutOn(req: CreatePutOnReq): Promise {
9 | return post('/v3/market/brc20/auction/create_put_on', req);
10 | },
11 | confirmPutOn(req: ConfirmPutOnReq): Promise {
12 | return post('/v3/market/brc20/auction/confirm_put_on', req);
13 | },
14 |
15 | createBidPrepare(req: CreateBidPrepareReq): Promise {
16 | return post('/v3/market/brc20/auction/create_bid_prepare', req);
17 | },
18 | createBid(req: CreateBidReq): Promise {
19 | return post('/v3/market/brc20/auction/create_bid', req);
20 | },
21 | confirmBid(req: ConfirmBidReq): Promise {
22 | return post('/v3/market/brc20/auction/confirm_bid', req);
23 | },
24 | createPutOff(req: CreatePutOffReq): Promise {
25 | return post('/v3/market/brc20/auction/create_put_off', req);
26 | },
27 | confirmPutOff(req: ConfirmPutOffReq): Promise {
28 | return post('/v3/market/brc20/auction/confirm_put_off', req);
29 | },
30 | }
31 |
32 |
33 | export enum InscriptionType {
34 | brc20 = "brc20",
35 | domain = "domain",
36 | collection = "collection",
37 | arc20 = "arc20",
38 | }
39 |
40 |
41 | export type ListReqFilter = {
42 | address?: string;
43 | nftConfirm?: boolean;
44 | minPrice?: number;
45 | maxPrice?: number;
46 | isEnd?: boolean;
47 |
48 | nftType?: InscriptionType;
49 |
50 | // brc20
51 | tick?: string;
52 | }
53 |
54 | export type ListReq = {
55 | filter: ListReqFilter;
56 | sort: {
57 | unitPrice?: -1 | 1;
58 | onSaleTime?: -1 | 1;
59 | };
60 | start: number;
61 | limit: number;
62 | };
63 |
64 |
65 | export type ListItem = {
66 | auctionId: string;
67 | inscriptionId: string;
68 | inscriptionName: string;
69 | inscriptionNumber: number;
70 | marketType: 'fixedPrice';
71 | nftType: InscriptionType;
72 | initPrice: number;
73 | curPrice: number;
74 | minBidPrice: number;
75 | endTime: number;
76 | address: string;
77 | onSaleTime: number;
78 | price: number;
79 |
80 | };
81 |
82 | export type BRC20ListItem = ListItem & {
83 | tick: string;
84 | limit: number;
85 | amount: number;
86 | unitPrice: number;
87 | };
88 |
89 | export type BRC20ListRes = {
90 | list: BRC20ListItem[];
91 | total: number;
92 | };
93 |
94 |
95 | export type CreatePutOnReq = {
96 | inscriptionId: string;
97 | initPrice: string;
98 | pubkey: string;
99 | marketType: 'fixedPrice';
100 | auctionTime?: number;
101 | maxPrice?: number;
102 | unitPrice?: string;
103 | btcAddress?: string;
104 | nftType: InscriptionType;
105 | };
106 |
107 | export type CreatePutOnRes = {
108 | auctionId: string;
109 | psbt: string;
110 | signIndexes: number[];
111 | };
112 |
113 | export type ConfirmPutOnReq = {
114 | auctionId: string;
115 | psbt: string;
116 | fromBase64?: boolean;
117 | };
118 |
119 | export type ConfirmPutOnRes = {};
120 |
121 |
122 | export type CreateBidPrepareReq = {
123 | auctionId: string;
124 | bidPrice: number;
125 | address: string;
126 | pubkey: string;
127 | };
128 |
129 | export type CreateBidPrepareRes = {
130 | serverFee: number;
131 | serverReal: number;
132 | serverFeeRate: number;
133 | txSize: number;
134 | nftValue: number;
135 | feeRate: number;
136 | discounts: {
137 | name: string;
138 | percent: number;
139 | }[];
140 | inscriptionCount: number;
141 | availableBalance: number;
142 | };
143 |
144 |
145 | export type CreateBidReq = {
146 | auctionId: string;
147 | bidPrice: number;
148 | address: string;
149 | pubkey: string;
150 | feeRate?: number;
151 | nftAddress?: string;
152 | sign?: string;
153 | };
154 |
155 |
156 | export type CreateBidRes = {
157 | bidId: string;
158 | psbtBid: string;
159 | psbtBid2: string;
160 | psbtSettle: string;
161 |
162 | networkFee: number;
163 | feeRate: number;
164 | serverFee: number;
165 | nftValue: number;
166 |
167 | bidSignIndexes: number[];
168 |
169 | };
170 |
171 | export type ConfirmBidReq = {
172 | auctionId: string;
173 | bidId: string;
174 | psbtBid: string;
175 | psbtBid2: string;
176 | psbtSettle: string;
177 | fromBase64?: boolean;
178 |
179 | };
180 |
181 | export type ConfirmBidRes = {
182 | txid: string;
183 | };
184 |
185 |
186 | export type CreatePutOffReq = {
187 | auctionId: string;
188 | btcPubkey?: string;
189 | nftAddress?: string;
190 | };
191 |
192 | export type CreatePutOffRes = {
193 | psbt: string;
194 | btcSignIndexes: number[];
195 | nftSignIndexes: number[];
196 | };
197 |
198 | export type ConfirmPutOffReq = {
199 | auctionId: string;
200 | psbt: string;
201 | fromBase64?: boolean;
202 | };
203 |
204 | export type ConfirmPutOffRes = {
205 | txid: string;
206 | };
207 |
208 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/networkUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | export enum NetworkType {
3 | livenet = 'livenet',
4 | testnet = 'testnet',
5 | }
6 |
7 | class NetworkUtils {
8 | private network: NetworkType = NetworkType.testnet;
9 | setNetworkType(type: NetworkType) {
10 | this.network = type;
11 | }
12 | isTestnet() {
13 | return this.network === NetworkType.testnet;
14 | }
15 | }
16 |
17 | export const networkUtils = new NetworkUtils();
18 |
19 |
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/unisatUtils.ts:
--------------------------------------------------------------------------------
1 | import {handleError} from "./utils";
2 |
3 | export const unisatUtils = {
4 | async getAccounts() {
5 | try {
6 | const accounts = await window.unisat.getAccounts();
7 | if (accounts && accounts.length > 0) {
8 | return accounts[0];
9 | }
10 | } catch (e: any) {
11 | handleError(e);
12 | }
13 | return '';
14 | },
15 | async requestAccounts() {
16 | try {
17 | const accounts = await window.unisat.requestAccounts();
18 | if (accounts && accounts.length > 0) {
19 | return accounts[0];
20 | }
21 | } catch (e: any) {
22 | handleError(e);
23 | }
24 | return '';
25 | },
26 | signMessage(message: string, type?: string) {
27 | return window.unisat.signMessage(message, type);
28 | },
29 | signPsbt(psbt: string) {
30 | return window.unisat.signPsbt(psbt, {autoFinalized: false});
31 | },
32 | getNetwork(): Promise {
33 | return window.unisat.getNetwork();
34 | },
35 | switchNetwork(network: string): Promise {
36 | return window.unisat.switchNetwork(network);
37 | },
38 |
39 | async checkNetwork(network: string): Promise {
40 | if (network !== await this.getNetwork()) {
41 | await this.switchNetwork(network);
42 | }
43 | },
44 | inscribeTransfer(tick: string, amount?: number | string) {
45 | return window.unisat.inscribeTransfer(tick, amount)
46 | },
47 | getPublicKey() {
48 | return window.unisat.getPublicKey();
49 | },
50 |
51 | }
--------------------------------------------------------------------------------
/brc20-market-demo/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import {Buffer} from "buffer";
2 | import {message} from "antd";
3 |
4 | export function sleep(ms: number) {
5 | return new Promise((resolve) => setTimeout(resolve, ms));
6 | }
7 |
8 | export function stringToHex(stringToEncode: string) {
9 | return Buffer.from(stringToEncode).toString('hex')
10 | }
11 |
12 | export function isTicketValid(ticket: string) {
13 | return Buffer.from(ticket).length === 4
14 | }
15 |
16 | export function handleError(e: any) {
17 | message.error((e && e.message) || e)
18 | }
--------------------------------------------------------------------------------
/brc20-market-demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/brc20-swap-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/brc20-swap-demo/README.md:
--------------------------------------------------------------------------------
1 | ## brc20-swap Demo
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ### Run the demo
6 |
7 | #### Install dependencies
8 |
9 | ```bash
10 | yarn
11 | ```
12 |
13 | #### Run the app
14 |
15 | ```bash
16 | yarn start
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/brc20-swap-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "brc20-swap-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^5.2.6",
7 | "antd": "^5.13.2",
8 | "axios": "^1.6.5",
9 | "buffer": "^6.0.3",
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0"
12 | },
13 | "devDependencies": {
14 | "@testing-library/jest-dom": "^5.14.1",
15 | "@testing-library/react": "^13.0.0",
16 | "@testing-library/user-event": "^13.2.1",
17 | "@types/jest": "^27.0.1",
18 | "@types/node": "^16.7.13",
19 | "@types/react": "^18.0.0",
20 | "@types/react-dom": "^18.0.0",
21 | "react-scripts": "5.0.1",
22 | "typescript": "^4.4.2"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/brc20-swap-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-swap-demo/public/favicon.ico
--------------------------------------------------------------------------------
/brc20-swap-demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/brc20-swap-demo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-swap-demo/public/logo192.png
--------------------------------------------------------------------------------
/brc20-swap-demo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/brc20-swap-demo/public/logo512.png
--------------------------------------------------------------------------------
/brc20-swap-demo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/brc20-swap-demo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './styles/App.css';
3 | import {Card, Tabs} from "antd";
4 | import {Swap} from "./components/Swap";
5 | import {ApiKeyInput} from "./components/ApiKeyInput";
6 | import {Deposit} from "./components/Deposit";
7 |
8 | function App() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | - Input your Api Key and refresh page
16 | - Connect UniSat wallet.
17 | - Click 'Deposit' tab, click 'Mint sats' to mint some sats.(get tFB)
19 |
20 | - Choose 'sats' and click 'inscribe transfer' to create transfer-inscription
21 | - Choose one transfer-inscription to deposit(it may need some confirmations).
22 | - Click 'swap' tab, input your pay sats then click swap
23 |
24 |
25 |
26 |
27 | },
32 | {key: "2", label: "Deposit", children: },
33 | // {key: "3", label: "Pools", children: },
34 | ]}
35 | />
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | export default App;
43 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/components/ApiKeyInput.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Input} from "antd";
2 | import React, {useEffect, useState} from "react";
3 | import {setApiKey} from "../utils/httpUtils";
4 |
5 | export function ApiKeyInput() {
6 | const [key, setKey] = useState("")
7 |
8 | useEffect(() => {
9 | const temp = localStorage.getItem("apiKey") || "";
10 | if (temp) {
11 | setKey(temp)
12 | setApiKey(temp)
13 | }
14 | }, []);
15 |
16 | return
17 |
18 | Set API key:
19 |
20 |
21 | {
25 | const key = e.target.value;
26 | setKey(key);
27 | setApiKey(key);
28 | localStorage.setItem("apiKey", key);
29 | }}
30 | />
31 |
32 |
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/components/ConfirmSwap.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from "react";
2 | import {Col, message, Modal, Row, Skeleton, Statistic} from "antd";
3 | import {ArrowDownOutlined} from "@ant-design/icons";
4 | import {ExactType, PreRes, swapApi, SwapReq} from "../utils/swapApi";
5 | import {useUnisat} from "../provider/UniSatProvider";
6 |
7 | type ConfirmSwapProps = {
8 | showConfirm: boolean,
9 | setShowConfirm: (showConfirm: boolean) => void
10 | tickIn: string,
11 | tickOut: string,
12 | amountIn: string,
13 | amountOut: string,
14 | slippage: string,
15 | address: string,
16 | onSuccess?: () => void,
17 | }
18 |
19 | export function ConfirmSwap({
20 | showConfirm, setShowConfirm,
21 | tickIn, tickOut, amountIn, amountOut, slippage, address, onSuccess
22 | }: ConfirmSwapProps) {
23 |
24 | const {signMessage} = useUnisat()
25 | const [swapReqParams, setSwapReqParams] = useState();
26 | const [preSwap, setPreSwap] = useState()
27 |
28 | const [isLoading, setIsLoading] = useState(false);
29 |
30 |
31 | useEffect(() => {
32 | if (showConfirm) {
33 | const ts = Math.floor(Date.now() / 1000);
34 | const params: SwapReq = {
35 | address,
36 | tickIn,
37 | tickOut,
38 | amountIn,
39 | amountOut,
40 | slippage,
41 | exactType: ExactType.exactIn,
42 | ts,
43 | feeTick:'test_sats',
44 | payType:'tick',
45 | }
46 | swapApi.preSwap(params).then(res => {
47 | setSwapReqParams(params);
48 | setPreSwap(res)
49 | }).catch((e) => {
50 | message.error((e && e.message) || e)
51 | })
52 | } else {
53 | setSwapReqParams(undefined);
54 | setPreSwap(undefined)
55 | }
56 | }, [address, amountIn, amountOut, showConfirm, slippage, tickIn, tickOut]);
57 |
58 |
59 | async function swap() {
60 | if (!preSwap || !swapReqParams) return;
61 | setIsLoading(true);
62 | try {
63 | const {signMsgs} = preSwap;
64 |
65 | //sign message
66 | let sigs = [];
67 | for (let i = 0; i < preSwap.signMsgs.length; i += 1) {
68 | const signMsg = preSwap.signMsgs[i];
69 |
70 | const sig = await signMessage(signMsg);
71 |
72 | sigs.push(sig);
73 | }
74 |
75 |
76 | const params = {
77 | ...swapReqParams,
78 | sigs,
79 | feeAmount: preSwap.feeAmount,
80 | feeTickPrice: preSwap.feeTickPrice,
81 | }
82 |
83 | await swapApi.swap(params);
84 |
85 | message.success('Swap success');
86 |
87 | onSuccess && onSuccess();
88 |
89 | setShowConfirm(false);
90 |
91 |
92 | } catch (e: any) {
93 | message.error((e && e.message) || e)
94 | } finally {
95 | setIsLoading(false);
96 | }
97 | }
98 |
99 | return {
101 | setShowConfirm(false)
102 | }}
103 | onOk={swap}
104 | confirmLoading={isLoading}
105 | >
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {
118 | !preSwap
119 | ?
120 | : <>
121 |
122 |
128 |
129 |
130 |
131 | >
132 | }
133 |
134 |
135 |
136 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/components/CreatePools.tsx:
--------------------------------------------------------------------------------
1 | // import {Button, message} from "antd";
2 | // import {AddLiqReq, DeployPoolReq, swapApi} from "../utils/swapApi";
3 | // import {useUnisat} from "../provider/UniSatProvider";
4 | // import {handleError} from "../utils/utils";
5 | //
6 | // export function CreatePools() {
7 | //
8 | // const {address, signMessage} = useUnisat();
9 | //
10 | // async function create() {
11 | // try {
12 | //
13 | // const ts = Math.floor(Date.now() / 1000);
14 | // const params: DeployPoolReq = {
15 | // address,
16 | // tick0: "sats",
17 | // tick1: "ordi",
18 | // ts,
19 | // }
20 | //
21 | // const {signMsg} = await swapApi.preDeployPool(params)
22 | //
23 | // params.sig = await signMessage(signMsg)
24 | //
25 | // await swapApi.deployPool(params)
26 | //
27 | // message.success("Create pool success")
28 | // } catch (e) {
29 | // handleError(e)
30 | // }
31 | // }
32 | //
33 | // async function addLiq() {
34 | // try {
35 | // const ts = Math.floor(Date.now() / 1000);
36 | // const params: AddLiqReq = {
37 | // address,
38 | // tick0: "sats",
39 | // tick1: "ordi",
40 | // amount0: "10",
41 | // amount1: "100",
42 | // slippage: "0.005",
43 | // lp: "31.622776601683793",
44 | // ts,
45 | // }
46 | //
47 | // const {signMsg} = await swapApi.preAddLiquidity(params)
48 | //
49 | // params.sig = await signMessage(signMsg)
50 | //
51 | // await swapApi.addLiquidity(params)
52 | //
53 | // message.success("Add liquidity success")
54 | // } catch (e) {
55 | // handleError(e)
56 | // }
57 | // }
58 | //
59 | // return <>
60 | //
63 | //
66 | // >
67 | // }
68 |
69 | export function CreatePools() {
70 |
71 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/components/Deposit.tsx:
--------------------------------------------------------------------------------
1 | import React, {useCallback, useEffect, useState} from "react";
2 | import {useUnisat} from "../provider/UniSatProvider";
3 | import {Button, Empty, message, Segmented, Skeleton, Statistic} from "antd";
4 | import {AddressBrc20Balance, brc20Api, Brc20InscriptionsItem} from "../utils/brc20Api";
5 | import {handleError, sleep} from "../utils/utils";
6 | import {PlusOutlined, ReloadOutlined} from "@ant-design/icons";
7 | import {unisatUtils} from "../utils/unisatUtils";
8 | import {swapApi} from "../utils/swapApi";
9 |
10 | export function Deposit() {
11 |
12 | const {address, connect, signPsbt} = useUnisat();
13 | const [addressSummary, setAddressSummary] = useState(null);
14 | const [currentTicker, setCurrentTicker] = useState('');
15 | const [transferInscriptions, setTransferInscriptions] = useState(null);
16 | const [selectedInscription, setSelectedInscription] = useState(null);
17 | const [isDepositing, setIsDepositing] = useState(false);
18 |
19 | useEffect(() => {
20 | if (address) {
21 | // get address brc20 summary
22 | brc20Api.summary({address}).then(res => {
23 | setAddressSummary(res.detail)
24 | if (res.detail.length > 0) {
25 | setCurrentTicker(res.detail[0].ticker)
26 | }
27 | }).catch(handleError)
28 | } else {
29 | setAddressSummary(null)
30 | }
31 | }, [address]);
32 |
33 | const refreshTransferable = useCallback(() => {
34 | if (currentTicker) {
35 | // get transfer-inscription
36 | setTransferInscriptions(null)
37 | brc20Api.getAddressTransferable({address, tick: currentTicker}).then(res => {
38 | setTransferInscriptions(res.detail);
39 | }).catch(handleError)
40 | } else {
41 | setTransferInscriptions(null)
42 | setSelectedInscription(null)
43 | }
44 | }, [address, currentTicker]);
45 |
46 | useEffect(() => {
47 | refreshTransferable()
48 | }, [refreshTransferable]);
49 |
50 | async function inscribeTransfer() {
51 | try {
52 | await unisatUtils.inscribeTransfer(currentTicker)
53 | setTransferInscriptions(null)
54 | await sleep(5000)
55 | refreshTransferable()
56 | } catch (e) {
57 | handleError(e)
58 | }
59 | }
60 |
61 | async function deposit() {
62 | if (!selectedInscription) return;
63 | try {
64 | setIsDepositing(true)
65 | const {inscriptionId} = selectedInscription;
66 |
67 | const pubkey = await unisatUtils.getPublicKey();
68 | const {psbt} = await swapApi.createDeposit({
69 | inscriptionId,
70 | address,
71 | pubkey,
72 | })
73 |
74 | const signed = await signPsbt(psbt);
75 |
76 | await swapApi.confirmDeposit({
77 | inscriptionId,
78 | psbt: signed
79 | })
80 |
81 | await sleep(3000)
82 | message.success('Deposit Success');
83 | await sleep(2000)
84 |
85 | refreshTransferable();
86 | } catch (e) {
87 | handleError(e)
88 | } finally {
89 | setIsDepositing(false);
90 | }
91 | }
92 |
93 | if (!address) {
94 | return
95 | }
96 |
97 | if (!addressSummary) {
98 | return
99 | }
100 |
101 | return <>
102 | {
103 |
106 | }
107 | {
108 | addressSummary.length <= 0 ? : {
112 | setCurrentTicker(v.toString())
113 | }}
114 | options={addressSummary.map(item => {
115 | return {
116 | label:
117 |
{item.ticker}
118 |
119 | ,
120 | value: item.ticker
121 | }
122 | })}/>
123 | }
124 | {
125 | currentTicker && <>
126 |
127 | Choose a transfer-inscription to Deposit
128 |
130 | {
131 |
132 | {
133 | !transferInscriptions ?
: <>
134 |
135 |
136 | Inscribe
137 | TRANSFER
138 |
139 | {
140 |
141 | transferInscriptions.map(item => {
142 | return
{
146 | if (!isDepositing)
147 | setSelectedInscription(item);
148 | }}>
149 |
{item.data.tick}
150 |
152 |
#{item.inscriptionNumber}
153 |
154 | })
155 | }
156 | >
157 | }
158 |
159 | }
160 |
164 | >
165 | }
166 |
167 |
168 | >
169 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/components/Swap.tsx:
--------------------------------------------------------------------------------
1 | import {Button, Col, InputNumber, Row, Spin, Statistic} from "antd";
2 | import React, {useCallback, useEffect, useState} from "react";
3 | import {AllAddressBalanceRes, ExactType, swapApi} from "../utils/swapApi";
4 | import {ConfirmSwap} from "./ConfirmSwap";
5 | import {useUnisat} from "../provider/UniSatProvider";
6 | import {handleError} from "../utils/utils";
7 | import {ReloadOutlined} from "@ant-design/icons";
8 |
9 |
10 | const tickIn = 'test_sats';
11 | const tickOut = 'test_ordi';
12 | const slippage = '0.005'; // 0.5%
13 |
14 |
15 | export function Swap() {
16 |
17 | const {address, connect} = useUnisat();
18 | const [swapBalanceMap, setSwapBalanceMap] = useState({});
19 |
20 | const [amountIn, setAmountIn] = React.useState('');
21 | const [amountOut, setAmountOut] = React.useState('');
22 | const [isLoading, setIsLoading] = React.useState(false);
23 |
24 | const [showConfirm, setShowConfirm] = React.useState(false);
25 |
26 | useEffect(() => {
27 |
28 | if (!address) return;
29 | if (!amountIn) {
30 | setAmountOut('');
31 | return;
32 | }
33 | setIsLoading(true)
34 | // 500ms delay
35 | const timer = setTimeout(() => {
36 | // quote swap out
37 | swapApi.quoteSwap({
38 | address,
39 | tickIn,
40 | tickOut,
41 | amount: amountIn,
42 | exactType: ExactType.exactIn,
43 | }).then(({expect}) => {
44 | setAmountOut(expect);
45 | }).catch(e => {
46 | handleError(e)
47 | }).finally(() => {
48 | setIsLoading(false)
49 | })
50 | }, 500);
51 |
52 | return () => clearTimeout(timer);
53 | }, [address, amountIn]);
54 |
55 | const refreshBalance = useCallback(() => {
56 | if (address) {
57 | swapApi.getAllBalance({address}).then(setSwapBalanceMap).catch(handleError)
58 | } else {
59 | setSwapBalanceMap({})
60 | }
61 | }, [address]);
62 |
63 | useEffect(() => {
64 | refreshBalance()
65 | }, [refreshBalance]);
66 |
67 |
68 | return <>
69 |
70 |
71 | Address:
72 |
73 |
74 | {
75 | address ? address :
78 | }
79 |
80 |
81 |
82 | Balance:
83 |
84 |
85 |
90 | {
91 | // wait for confirm balance
92 | (parseFloat(swapBalanceMap[tickIn]?.balance.pendingSwap) || 0) > 0 &&
93 | (+{swapBalanceMap[tickIn]?.balance.pendingSwap})
94 |
95 | }
96 |
97 |
98 |
103 | {
104 | // wait for confirm balance
105 | (parseFloat(swapBalanceMap[tickOut]?.balance.pendingSwap) || 0) > 0 &&
106 |
107 | (+{swapBalanceMap[tickOut]?.balance.pendingSwap})
108 |
109 | }
110 |
111 |
112 |
154 | {
155 | showConfirm &&
166 | }
167 | >
168 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/constants.ts:
--------------------------------------------------------------------------------
1 | export enum NetworkType {
2 | livenet = 'livenet',
3 | testnet = 'testnet',
4 | }
5 |
6 | // export const NETWORK = NetworkType.testnet
7 | export const NETWORK = NetworkType.testnet
--------------------------------------------------------------------------------
/brc20-swap-demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './styles/index.css';
4 | import App from './App';
5 | import {ConfigProvider} from "antd";
6 | import UnisatProvider from "./provider/UniSatProvider";
7 |
8 | const root = ReactDOM.createRoot(
9 | document.getElementById('root') as HTMLElement
10 | );
11 | root.render(
12 |
13 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/provider/UniSatProvider.tsx:
--------------------------------------------------------------------------------
1 | import {createContext, ReactNode, useCallback, useContext, useEffect, useState} from "react";
2 | import {handleError, sleep} from "../utils/utils";
3 | import {unisatUtils} from "../utils/unisatUtils";
4 | import {NETWORK} from "../constants";
5 |
6 | interface UnisatContextType {
7 | isInstalled: boolean;
8 | isConnected: boolean;
9 | address: string;
10 | connect: () => void;
11 | signMessage: (msg: string) => Promise;
12 | signPsbt: (psbt: string) => Promise;
13 | }
14 |
15 | const UnisatContext = createContext({
16 | isInstalled: false,
17 | isConnected: false,
18 | address: '',
19 | connect: () => {
20 | },
21 | signMessage: (msg: string) => Promise.resolve(''),
22 | signPsbt: (psbt: string) => Promise.resolve('')
23 | })
24 |
25 |
26 | export function useUnisat() {
27 | const context = useContext(UnisatContext);
28 | if (!context) {
29 | throw Error('Feature flag hooks can only be used by children of UnisatProvider.');
30 | } else {
31 | return context;
32 | }
33 | }
34 |
35 | export default function UnisatProvider({children}: {
36 | children: ReactNode
37 | }) {
38 | const [isInstalled, setIsInstalled] = useState(false)
39 | const [isConnected, setIsConnected] = useState(false)
40 | const [address, setAddress] = useState('')
41 |
42 |
43 | useEffect(() => {
44 |
45 | async function init() {
46 | let install = !!window.unisat;
47 | setIsInstalled(install);
48 |
49 | // 额外检查
50 | for (let i = 0; i < 10 && !install; i += 1) {
51 | await sleep(100 + i * 100);
52 | install = !!window.unisat;
53 | if (install) {
54 | setIsInstalled(install);
55 | break;
56 | }
57 | }
58 |
59 | if (install) {
60 | await unisatUtils.switchChain('FRACTAL_BITCOIN_TESTNET')
61 |
62 | const address = await unisatUtils.getAccounts()
63 | if (address) {
64 | // connected
65 | setIsConnected(true)
66 | setAddress(address)
67 |
68 | }
69 | }
70 | }
71 |
72 | init().then();
73 |
74 | }, []);
75 |
76 | const connect = useCallback(async () => {
77 | try {
78 | await unisatUtils.switchChain('FRACTAL_BITCOIN_TESTNET')
79 |
80 | const address = await unisatUtils.requestAccounts();
81 | if (address) {
82 | setIsConnected(true)
83 | setAddress(address)
84 | }
85 | } catch (e) {
86 | handleError(e)
87 | }
88 |
89 | }, [])
90 |
91 | const signMessage = useCallback((msg: string) => {
92 | return unisatUtils.signMessage(msg, 'bip322-simple')
93 | }, [])
94 |
95 | const signPsbt = useCallback((psbt: string) => {
96 | return unisatUtils.signPsbt(psbt)
97 | }, []);
98 |
99 | const value = {
100 | isInstalled,
101 | isConnected,
102 | address,
103 | connect,
104 | signMessage,
105 | signPsbt,
106 | }
107 |
108 | return
109 | {children}
110 |
111 |
112 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | padding: 16px;
3 | }
4 |
5 | .main-container {
6 | max-width: 640px;
7 | margin: 0 auto;
8 | }
9 |
10 | .font-16 {
11 | font-size: 16px;
12 | }
13 |
14 | .p-8 {
15 | padding: 8px;
16 | }
17 |
18 | .mt-16 {
19 | margin-top: 16px;
20 | }
21 |
22 | .mt-8 {
23 | margin-top: 8px;
24 | }
25 |
26 | .ml-8 {
27 | margin-left: 8px;
28 | }
29 |
30 | .flex-row-v-center {
31 | display: flex;
32 | flex-direction: row;
33 | align-items: center;
34 | }
35 |
36 | .gap-16 {
37 | gap: 16px;
38 | }
39 |
40 | .flex-1 {
41 | flex: 1;
42 | }
43 |
44 | .bold {
45 | font-weight: bold;
46 | }
47 |
48 | .nft-item {
49 | min-width: 88px;
50 | padding: 8px;
51 | border-radius: 12px;
52 | overflow: hidden;
53 | border: 1px solid #fff;
54 | box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
55 | cursor: pointer;
56 |
57 | transition: all .2s ease-in-out;
58 | }
59 |
60 | .nft-item:hover {
61 | border: 1px solid var(--main-color);
62 | box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.1);
63 |
64 | }
65 |
66 | .nft-item.selected {
67 | border: 1px solid var(--main-color);
68 | }
69 |
70 | .nft-item.inscribe-transfer {
71 | display: flex;
72 | flex-direction: column;
73 | align-items: center;
74 | }
75 |
76 | .inscribe-transfer .plus-icon {
77 | font-size: 32px;
78 | color: var(--main-color);
79 |
80 | }
81 |
82 | .nft-item.selected .inscription-num {
83 | background: var(--main-color);
84 | color: white;
85 | }
86 |
87 | .nft-item .tick {
88 | font-size: 12px;
89 | color: #8a8a8a;
90 | }
91 |
92 | .nft-item .inscription-num {
93 | background: #ddd;
94 | padding: 4px;
95 | margin: 8px -8px -8px;
96 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 |
9 | --main-color: #EBB94C;
10 |
11 | background-color: rgb(240, 242, 245);
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/brc20-swap-demo/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
3 |
4 | export type UnisatWalletInterface = {
5 | getAccounts(): Promise;
6 | requestAccounts(): Promise;
7 | getNetwork(): Promise;
8 | switchNetwork(network: string): Promise;
9 | sendBitcoin(address: string, amount: number, options: any): Promise;
10 | on(event: string, listener: Function): void;
11 | removeListener(event: string, listener: Function): void;
12 | signMessage(message: string, type?: string): Promise;
13 | signPsbt(psbt: string, opt: { autoFinalized: boolean }): Promise;
14 | getPublicKey(): Promise;
15 | getBalance(): Promise<{
16 | confirmed: number,
17 | unconfirmed: number,
18 | total: number
19 | }>;
20 | inscribeTransfer(tick: string, amount?: number | string): Promise<{
21 | amount: string,
22 | inscriptionId: string
23 | inscriptionNumber: number
24 | ticker: string
25 | }>;
26 | getInscriptions(num: number): Promise;
27 | switchChain(network: string): Promise;
28 | }
29 |
30 | declare global {
31 | interface Window {
32 | unisat: UnisatWalletInterface;
33 | }
34 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/utils/brc20Api.ts:
--------------------------------------------------------------------------------
1 | import {get} from "./httpUtils";
2 | import {stringToHex} from "./utils";
3 |
4 | export const brc20Api = {
5 | summary({
6 | address, limit = 20, start = 0,
7 | }: { address: string, limit?: number, start?: number }): Promise<{
8 | total: number,
9 | start: number,
10 | height: number,
11 | detail: AddressBrc20Balance[]
12 | }> {
13 | return get(`/v1/indexer/address/${address}/brc20/summary`, {limit, start})
14 | },
15 |
16 | getAddressTransferable({
17 | address, tick, start = 0, limit = 512,
18 | }: {
19 | address: string,
20 | tick: string,
21 | start?: number,
22 | limit?: number
23 | }): Promise {
24 | return get(`/v1/indexer/address/${address}/brc20/${(tick)}/transferable-inscriptions`, {
25 | limit,
26 | start,
27 | })
28 | },
29 | }
30 |
31 |
32 | export type AddressBrc20Balance = {
33 | ticker: string
34 | overallBalance: string // transferableBalance + availableBalance
35 | transferableBalance: string
36 | availableBalance: string
37 | }
38 |
39 |
40 | export type Brc20Data = {
41 | amt: string,
42 | decimal: string,
43 | lim: string,
44 | max: string,
45 | minted: string,
46 | op: string,
47 | tick: string,
48 | to: string
49 | }
50 | export type Brc20InscriptionsItem = {
51 | data: Brc20Data,
52 | inscriptionId: string,
53 | inscriptionNumber: number,
54 | confirmations: number,
55 | isPutOn?: boolean,
56 | };
57 |
58 | export type Brc20AddressTransferable = {
59 | start: number,
60 | total: number,
61 | detail: Brc20InscriptionsItem[]
62 | };
--------------------------------------------------------------------------------
/brc20-swap-demo/src/utils/httpUtils.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {isTestnet} from "./utils";
3 |
4 | let apiKey = localStorage.getItem('apiKey') || '';
5 |
6 | export function setApiKey(key: string) {
7 | apiKey = key;
8 | }
9 |
10 | const api = axios.create({
11 | baseURL: isTestnet ? "https://open-api-fractal-testnet.unisat.io" : "https://open-api.unisat.io/",
12 | timeout: 10000,
13 | headers: {
14 | "Content-Type": "application/json",
15 | },
16 | });
17 |
18 | api.interceptors.request.use((config) => {
19 | config.headers.Authorization = `Bearer ${apiKey}`;
20 | return config;
21 | })
22 |
23 | export const get = async (url: string, params?: any) => {
24 | const res = await api.get(url, {params});
25 | if (res.status !== 200) {
26 | throw new Error(res.statusText);
27 | }
28 |
29 | const responseData = res.data;
30 |
31 | if (responseData.code !== 0) {
32 | throw new Error(responseData.msg);
33 | }
34 | return responseData.data;
35 | };
36 |
37 | export const post = async (url: string, data?: any) => {
38 | const res = await api.post(url, data,);
39 | if (res.status !== 200) {
40 | throw new Error(res.statusText);
41 | }
42 |
43 | const responseData = res.data;
44 |
45 | if (responseData.code !== 0) {
46 | throw new Error(responseData.msg);
47 | }
48 |
49 | return responseData.data;
50 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/utils/swapApi.ts:
--------------------------------------------------------------------------------
1 | import {get, post} from "./httpUtils";
2 |
3 |
4 | export const swapApi = {
5 | getAllBalance: (params: AllAddressBalanceReq): Promise => get('/v1/brc20-swap/all_balance', params),
6 |
7 | quoteSwap: async (req: QuoteSwapReq): Promise => {
8 | return get('/v1/brc20-swap/quote_swap', req);
9 | },
10 | preSwap: async (req: SwapReq): Promise => {
11 | return get('/v1/brc20-swap/pre_swap', req);
12 | },
13 | swap: async (req: SwapReq): Promise => {
14 | return post('/v1/brc20-swap/swap', req);
15 | },
16 |
17 | createDeposit: async (req: CreateDepositReq): Promise => {
18 | return get('/v1/brc20-swap/create_deposit', req);
19 | },
20 |
21 | confirmDeposit: async (req: ConfirmDepositReq): Promise => {
22 | return post('/v1/brc20-swap/confirm_deposit', req);
23 | },
24 | preDeployPool(params: DeployPoolReq): Promise {
25 | return get('/v1/brc20-swap/pre_deploy_pool', params)
26 | },
27 | deployPool(params: DeployPoolReq): Promise {
28 | return post('/v1/brc20-swap/deploy_pool', params)
29 | },
30 | // 获取添加流动性的待签名信息
31 | preAddLiquidity: (params: AddLiqReq): Promise => get('/v1/brc20-swap/pre_add_liq', params),
32 | // 添加流动性
33 | addLiquidity: (params: AddLiqReq): Promise => post('/v1/brc20-swap/add_liq', params),
34 | }
35 |
36 | export enum ExactType {
37 | exactIn = "exactIn",
38 | exactOut = "exactOut",
39 | }
40 |
41 | export type QuoteSwapReq = {
42 | address: string;
43 | tickIn: string;
44 | tickOut: string;
45 | amount: string;
46 | exactType: ExactType;
47 | };
48 |
49 | export type QuoteSwapRes = {
50 | expect: string;
51 | amountUSD: string;
52 | expectUSD: string;
53 | };
54 |
55 |
56 | export type SwapReq = {
57 | address: string;
58 | tickIn: string;
59 | tickOut: string;
60 | amountIn: string;
61 | amountOut: string;
62 | slippage: string;
63 | exactType: ExactType;
64 | ts: number;
65 | sig?: string;
66 | feeTick: string;
67 | payType: string;
68 | };
69 | export type SwapRes = {
70 | id: string;
71 | address: string;
72 | tickIn: string;
73 | tickOut: string;
74 | amountIn: string;
75 | amountOut: string;
76 | exactType: ExactType;
77 | ts: number;
78 | }
79 | export type PreRes = {
80 | // signedMsg: string;
81 | signMsgs: string[];
82 | } & FeeRes;
83 |
84 | export type FeeRes = {
85 | feeAmount: string;
86 | feeTick: string;
87 | feeTickPrice: string;
88 | feeBalance: string;
89 |
90 | originalFeeAmount: string;
91 | freeQuota: string;
92 | };
93 |
94 |
95 | interface CreateDepositReq {
96 | inscriptionId: string,
97 | address: string,
98 | pubkey: string
99 | }
100 |
101 | interface CreateDepositRes {
102 | psbt: string;
103 | type: "direct" | "matching";
104 | expiredTimestamp: number;
105 | recommendDeposit: string;
106 | }
107 |
108 |
109 | interface ConfirmDepositReq {
110 | psbt: string,
111 | inscriptionId: string
112 | }
113 |
114 | interface ConfirmDepositRes {
115 | txid: string;
116 | pendingNum: number;
117 | }
118 |
119 |
120 | export type DeployPoolReq = {
121 | address: string;
122 | tick0: string;
123 | tick1: string;
124 | ts: number;
125 | sig?: string;
126 | };
127 | export type DeployPoolRes = {
128 | //
129 | };
130 |
131 |
132 | export type AllAddressBalanceReq = {
133 | address: string;
134 | };
135 | export type AllAddressBalanceRes = {
136 | [key: string]: {
137 | balance: AddressBalance
138 | decimal: string
139 | withdrawLimit: string
140 | };
141 | };
142 |
143 | export type AddressBalance = {
144 | module: string; // 提现队列中的
145 | swap: string;
146 | pendingSwap: string; // 充值未确认的
147 | pendingAvailable: string; // 提现未确认的
148 | };
149 |
150 |
151 | export type AddLiqReq = {
152 | address: string;
153 | tick0: string;
154 | tick1: string;
155 | amount0: string;
156 | amount1: string;
157 | slippage: string;
158 | ts: number;
159 | lp: string;
160 | sig?: string;
161 | };
162 |
163 | export type AddLiqRes = {
164 | id: string;
165 | address: string;
166 | };
--------------------------------------------------------------------------------
/brc20-swap-demo/src/utils/unisatUtils.ts:
--------------------------------------------------------------------------------
1 | import {handleError} from "./utils";
2 |
3 | export const unisatUtils = {
4 | async getAccounts() {
5 | try {
6 | const accounts = await window.unisat.getAccounts();
7 | if (accounts && accounts.length > 0) {
8 | return accounts[0];
9 | }
10 | } catch (e: any) {
11 | handleError(e);
12 | }
13 | return '';
14 | },
15 | async requestAccounts() {
16 | try {
17 | const accounts = await window.unisat.requestAccounts();
18 | if (accounts && accounts.length > 0) {
19 | return accounts[0];
20 | }
21 | } catch (e: any) {
22 | handleError(e);
23 | }
24 | return '';
25 | },
26 | signMessage(message: string, type?: string) {
27 | return window.unisat.signMessage(message, type);
28 | },
29 | signPsbt(psbt: string) {
30 | return window.unisat.signPsbt(psbt, {autoFinalized: false});
31 | },
32 | getNetwork(): Promise {
33 | return window.unisat.getNetwork();
34 | },
35 | switchNetwork(network: string): Promise {
36 | return window.unisat.switchNetwork(network);
37 | },
38 |
39 | async checkNetwork(network: string): Promise {
40 | if (network !== await this.getNetwork()) {
41 | await this.switchNetwork(network);
42 | }
43 | },
44 | inscribeTransfer(tick: string, amount?: number | string) {
45 | return window.unisat.inscribeTransfer(tick, amount)
46 | },
47 | getPublicKey() {
48 | return window.unisat.getPublicKey();
49 | },
50 | switchChain(network: string) {
51 | return window.unisat.switchChain(network);
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/brc20-swap-demo/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import {Buffer} from "buffer";
2 | import {message} from "antd";
3 | import {NETWORK, NetworkType} from "../constants";
4 |
5 | export function sleep(ms: number) {
6 | return new Promise((resolve) => setTimeout(resolve, ms));
7 | }
8 |
9 | export function stringToHex(stringToEncode: string) {
10 | return Buffer.from(stringToEncode).toString('hex')
11 | }
12 |
13 | export function handleError(e: any) {
14 | return message.error((e && e.message) || e)
15 | }
16 |
17 | // @ts-ignore
18 | export const isTestnet = NETWORK === NetworkType.testnet;
--------------------------------------------------------------------------------
/brc20-swap-demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/README.md:
--------------------------------------------------------------------------------
1 | ## UniSat Inscribe Demo
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ### Run the demo
6 |
7 | #### Install dependencies
8 |
9 | ```bash
10 | yarn
11 | ```
12 |
13 | #### Run the app
14 |
15 | ```bash
16 | yarn start
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unisat-inscribe-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^5.3.0",
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^13.0.0",
9 | "@testing-library/user-event": "^13.2.1",
10 | "@types/jest": "^27.0.1",
11 | "@types/node": "^16.7.13",
12 | "@types/react": "^18.0.0",
13 | "@types/react-dom": "^18.0.0",
14 | "ahooks": "^3.7.10",
15 | "antd": "^5.14.0",
16 | "axios": "^1.6.7",
17 | "buffer": "^6.0.3",
18 | "js-base64": "^3.7.6",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-scripts": "5.0.1",
22 | "typescript": "^4.4.2",
23 | "web-vitals": "^2.1.0"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-inscribe-demo/public/favicon.ico
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-inscribe-demo/public/logo192.png
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-inscribe-demo/public/logo512.png
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import './style/App.css';
3 | import UnisatProvider from "./provider/UniSatProvider";
4 | import {ConfigProvider, Layout, Typography} from 'antd';
5 | import {useEventEmitter, useLocalStorageState} from "ahooks";
6 | import {Inscribe} from "./pages/Inscribe";
7 | import {ApiKeyInput} from "./components/ApiKeyInput";
8 | import {NetworkSwitch} from "./components/NetworkSwitch";
9 | import {OrderList} from "./pages/OrderList";
10 |
11 | function App() {
12 |
13 | const newOrder$ = useEventEmitter();
14 |
15 |
16 | const [apiKey, setApiKey] = useLocalStorageState('apiKey',
17 | {defaultValue: '', deserializer: (val) => val, serializer: (val) => val})
18 |
19 | return (
20 |
30 |
31 |
32 |
33 |
41 |
42 |
43 | {
44 | apiKey
45 | ? <>
46 |
47 |
48 | >
49 | : <>
50 |
51 | Please set API key to continue
52 |
53 | >
54 | }
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default App;
64 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/ApiKeyInput.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Input} from "antd";
2 | import React from "react";
3 | import {setApiKey} from "../utils/httpUtils";
4 |
5 | export function ApiKeyInput({apiKey,setKey}:{
6 | apiKey: string |undefined,
7 | setKey: (apiKey: string) => void
8 | }) {
9 |
10 | return
11 |
12 | Set API key:
13 |
14 |
15 | {
19 | const key = e.target.value;
20 | setKey(key);
21 | setApiKey(key);
22 | }}
23 | />
24 |
25 |
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/FeeDetail.tsx:
--------------------------------------------------------------------------------
1 | import {InscribeFileData} from "../pages/Inscribe";
2 | import {SimpleRow} from "./SimpleRow";
3 |
4 | type FeeDetailProps = {
5 | feeRate: number|undefined,
6 | outputValue: number
7 | devFee: number,
8 | address: string,
9 | fileList: InscribeFileData[]
10 | }
11 |
12 | export function FeeDetail({
13 | feeRate=0, outputValue, fileList, devFee,address
14 | }: FeeDetailProps) {
15 |
16 | const inscriptionBalance = outputValue; // the balance in each inscription
17 | const fileCount = fileList.length; // the fileCount
18 | const fileSize = fileList.reduce((pre,item)=>pre+item.size,0); // the total size of all files
19 | const contentTypeSize = fileList.reduce((pre, item) => pre + item.dataURL.split('data:')[1].split('base64')[0].length, 0); // the size of contentType
20 | const feeFileSize = fileList.slice(0,25).reduce((pre,item)=>pre+item.size,0); // the total size of first 25 files
21 | const feeFileCount = 25 // do not change this
22 |
23 | const balance = inscriptionBalance * fileCount;
24 |
25 | let addrSize = 25+1; // p2pkh
26 | if(address.indexOf('bc1q')==0 || address.indexOf('tb1q')==0){
27 | addrSize = 22+1;
28 | }else if(address.indexOf('bc1p')==0 || address.indexOf('tb1p')==0){
29 | addrSize = 34+1;
30 | }else if(address.indexOf('2')==0 || address.indexOf('3')==0){
31 | addrSize = 23+1;
32 | }
33 |
34 | const baseSize = 88;
35 | let networkFee = Math.ceil(((fileSize+contentTypeSize) / 4 + ( baseSize+8+addrSize+8+23)) * feeRate);
36 | if(fileCount>1){
37 | networkFee = Math.ceil(((fileSize+contentTypeSize) / 4 + (baseSize+8+addrSize+(35+8)*(fileCount-1)+ 8+23 +(baseSize+8+addrSize+0.5)*(fileCount-1) )) * feeRate);
38 | }
39 | let networkSatsByFeeCount = Math.ceil(((feeFileSize+contentTypeSize) / 4 + ( baseSize+8+addrSize+8+23)) * feeRate);
40 | if(fileCount>1){
41 | networkSatsByFeeCount = Math.ceil(((( feeFileSize)+contentTypeSize*(feeFileCount/fileCount)) / 4 + (baseSize+8+addrSize+(35+8)*(fileCount-1)+ 8+23 +(baseSize+8+addrSize+0.5)*Math.min(fileCount-1,feeFileCount-1) )) * feeRate);
42 | }
43 |
44 | const baseFee = 1999 * Math.min(fileCount, feeFileCount); // 1999 base fee for top 25 files
45 | const floatFee = Math.ceil(networkSatsByFeeCount * 0.0499); // 4.99% extra miner fee for top 25 transations
46 | const serviceFee = Math.floor(baseFee + floatFee);
47 |
48 | const total = balance + networkFee + serviceFee;
49 | const truncatedTotal = Math.floor((total) / 1000) * 1000; // truncate
50 | const amount = truncatedTotal + devFee; // add devFee at the end
51 |
52 | console.log("The final amount need to pay: ",amount)
53 |
54 | return <>
55 |
58 |
61 |
64 | {total} {amount}>}/>
67 |
68 | >
69 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/InscribeFileInput.tsx:
--------------------------------------------------------------------------------
1 | import {Button, Card, Flex, Upload} from "antd";
2 | import {RcFile} from "antd/es/upload";
3 |
4 | import {InscribeFileData} from "../pages/Inscribe";
5 | import {getSizeShow, handleError, isUtf8, showLongString} from "../utils/utils";
6 |
7 | type InscribeFileInputProps = {
8 | fileList: InscribeFileData[]
9 | setFileList: (list: InscribeFileData[]) => void
10 | isSubmitting: boolean
11 | }
12 |
13 |
14 | const fileSizeLimit = 1024 * 365; // 单个文件大小限制
15 | const multiFileSizeLimit = 1024 * 100; // 批量铸造时,中文件大小显示。
16 | const fileCountLimit = 1000;
17 |
18 |
19 | const getFileBase64 = (file: RcFile, callback: (url: string) => void) => {
20 | const reader = new FileReader();
21 | reader.addEventListener('load', () => callback(reader.result as string));
22 | reader.readAsDataURL(file);
23 | };
24 |
25 | export function InscribeFileInput({
26 | fileList,
27 | setFileList,
28 | isSubmitting
29 | }: InscribeFileInputProps) {
30 | const beforeUpload = async (file: RcFile) => {
31 | if (isSubmitting)
32 | return false;
33 |
34 | // console.log(file,file.name, file.type);
35 |
36 | if (fileList.length >= fileCountLimit) {
37 | handleError(`the file quantity limit is ${fileCountLimit}`);
38 | return false;
39 | }
40 |
41 | const isLtSize = file.size < fileSizeLimit;
42 | if (!isLtSize) {
43 | handleError(`File size must be smaller than ${getSizeShow(fileSizeLimit, 0)}!`);
44 | return false;
45 | }
46 |
47 | if (fileList.length > 0) {
48 | const total = fileList.reduce((pre, current) => pre + current.size, 0);
49 | if (total + file.size > multiFileSizeLimit) {
50 | handleError(`The total file size limit for inscribing inscriptions in bulk is ${getSizeShow(multiFileSizeLimit, 0)}!`);
51 | return false;
52 | }
53 | }
54 |
55 | return new Promise(async (resolve, reject) => {
56 |
57 | if (file.type.startsWith('text')) {
58 | try {
59 | await isUtf8(file)
60 | } catch (e: any) {
61 | handleError(e);
62 | resolve(false);
63 | return
64 | }
65 | }
66 |
67 | getFileBase64(file, (dataURL) => {
68 | console.log(file)
69 | // handle text file encoding
70 | if (dataURL.startsWith('data:text/plain;base64')) {
71 | dataURL = dataURL.replace('data:text/plain;base64', 'data:text/plain;charset=utf-8;base64')
72 | }
73 | if (dataURL.startsWith('data:text/html;base64')) {
74 | dataURL = dataURL.replace('data:text/html;base64', 'data:text/html;charset=utf-8;base64')
75 | }
76 |
77 |
78 | const temp: InscribeFileData[] = [...fileList, {
79 | filename: file.name,
80 | size: file.size,
81 | type: file.type,
82 | dataURL,
83 | }];
84 |
85 | setFileList(temp);
86 |
87 |
88 | resolve(true);
89 | });
90 | })
91 | };
92 |
93 | return <>
94 | {
99 | }}
100 | multiple={true}
101 | >
102 |
103 | Drag and drop your files here, or click to select files
104 |
105 |
106 | .jpg, .webp, .png, .gif, .txt, .mp3, .mp4(h264) + more!
107 |
108 |
109 | {
110 | fileList.length > 0 &&
111 | File list
112 | {
113 | setFileList([])
114 | }}>Remove All
115 | }>
116 | {
117 | fileList.map((item, index) => (
118 |
119 | {showLongString(item.filename)} - {item.type} - {getSizeShow(item.size)}
120 |
121 | ))
122 | }
123 |
124 | }
125 |
126 | >
127 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/InscribeTextInput.tsx:
--------------------------------------------------------------------------------
1 | import {SimpleRow} from "./SimpleRow";
2 | import {Input} from "antd";
3 | import {useEffect, useState} from "react";
4 |
5 | export type InscribeTextInputProps = {
6 | submit: (text: string) => void,
7 | }
8 |
9 | export function InscribeTextInput({submit}: InscribeTextInputProps) {
10 |
11 | const [inputText, setInputText] = useState('');
12 |
13 | useEffect(() => {
14 | submit(inputText);
15 | }, [inputText]);
16 |
17 | return <>
18 | setInputText(e.target.value)}
23 | placeholder={'Enter text to inscribe'}
24 | allowClear
25 | />}
26 | />
27 | >
28 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/NetworkSwitch.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Radio} from "antd";
2 | import {useUnisat} from "../provider/UniSatProvider";
3 | import {NetworkType} from "../types";
4 |
5 |
6 | export function NetworkSwitch() {
7 | const {network, switchNetwork} = useUnisat()
8 | return
9 | {
10 | switchNetwork(v.target.value)
11 | }} value={network}>
12 | Testnet
13 | Mainnet
14 |
15 |
16 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/OrderDetail.tsx:
--------------------------------------------------------------------------------
1 | import {Flex, Modal, Skeleton, Typography} from "antd";
2 | import {useCallback, useEffect, useState} from "react";
3 | import {InscribeOrderData} from "../utils/api-types";
4 | import {handleError} from "../utils/utils";
5 | import {api} from "../utils/api";
6 | import {isOrderProcessing} from "../utils/orderUtils";
7 | import {SimpleRow} from "./SimpleRow";
8 | import {OrderPay} from "./OrderPay";
9 | import {OrderStatus} from "./OrderStatus";
10 | import {OrderFiles} from "./OrderFiles";
11 |
12 | const {Text, Title} = Typography;
13 |
14 | export function OrderDetail({orderId, close}: { orderId: string, close: () => void }) {
15 |
16 | const [order, setOrder] = useState()
17 | const [isRequesting, setIsRequesting] = useState(false)
18 |
19 | const refresh = useCallback(async () => {
20 | if (!orderId) return undefined;
21 |
22 | let orderInfo;
23 | try {
24 | setIsRequesting(true);
25 | orderInfo = await api.orderInfo(orderId);
26 | setOrder(orderInfo);
27 | } catch (e) {
28 | handleError(e)
29 | } finally {
30 | setIsRequesting(false)
31 | }
32 | return orderInfo;
33 |
34 | }, [orderId]);
35 |
36 | useEffect(() => {
37 | let timer: any;
38 |
39 | refresh().then(data => {
40 | if (!data) {
41 | handleError('Order does not exist');
42 | return close();
43 | }
44 | if (isOrderProcessing(data)) {
45 | timer = setInterval(() => {
46 | refresh().then(data => {
47 | if (!data || !isOrderProcessing(data)) {
48 | clearInterval(timer);
49 | timer = null;
50 | }
51 | })
52 | }, 5000);
53 | }
54 | });
55 |
56 | return () => {
57 | if (timer)
58 | clearInterval(timer);
59 | }
60 | }, [refresh]);
61 |
62 | return }>
65 |
66 |
67 | {
68 | !order ? : <>
69 |
70 |
71 |
72 | >
73 | }
74 |
75 |
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/OrderFiles.tsx:
--------------------------------------------------------------------------------
1 | import {InscribeOrderData} from "../utils/api-types";
2 | import {Button, Divider, Flex} from "antd";
3 | import {useUnisat} from "../provider/UniSatProvider";
4 | import {NetworkType} from "../types";
5 |
6 | export function OrderFiles({order}: { order: InscribeOrderData }) {
7 | const {network} = useUnisat();
8 |
9 | function getLink(inscriptionId: string) {
10 | return `https://${network === NetworkType.testnet ? 'testnet.' : ''}unisat.io/inscription/${inscriptionId}`
11 |
12 | }
13 |
14 | return <>
15 |
16 | Inscribed Files
17 |
18 |
19 | {
20 | order.files.map((file, index) => {
21 | return
22 | {file.filename}
23 |
24 | {file.status}
25 | {
26 | file.inscriptionId
27 | &&
28 |
33 | View Inscription
34 |
35 | }
36 |
37 |
38 | })
39 | }
40 |
41 | >
42 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/OrderInscribing.tsx:
--------------------------------------------------------------------------------
1 | import {InscribeOrderData} from "../utils/api-types";
2 | import {SimpleRow} from "./SimpleRow";
3 | import {Typography} from "antd";
4 |
5 | const {Text, Title} = Typography;
6 |
7 | export function OrderInscribing({order}: { order: InscribeOrderData, }) {
8 | return <>
9 |
12 |
16 |
17 | {order.unconfirmedCount + order.confirmedCount}/{order.count}
18 |
19 | ({order.unconfirmedCount} Unconfirmed, {order.confirmedCount} Confirmed)
20 | >
21 | }/>
22 |
23 | >
24 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/OrderPay.tsx:
--------------------------------------------------------------------------------
1 | import {InscribeOrderData} from "../utils/api-types";
2 | import {SimpleRow} from "./SimpleRow";
3 | import {Button, Typography} from "antd";
4 | import {useUnisat} from "../provider/UniSatProvider";
5 | import {unisatUtils} from "../utils/unisatUtils";
6 | import {useState} from "react";
7 | import {handleError} from "../utils/utils";
8 |
9 | const {Text} = Typography;
10 |
11 | export function OrderPay({order}: { order: InscribeOrderData, }) {
12 | const [paying, setPaying] = useState(false)
13 |
14 | async function pay() {
15 | // send the amount BTC to payAddress, then order will continue to process
16 | try {
17 | setPaying(true)
18 | await unisatUtils.sendBitcoin(order.payAddress, order.amount, order.feeRate)
19 | }catch (e){
20 | handleError(e)
21 | setPaying(false)
22 | }
23 |
24 | }
25 |
26 | return <>
27 | {order.payAddress}} valueSpan={18}/>
30 | {order.amount}}/>
33 | Pay}/>
35 | >
36 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/OrderStatus.tsx:
--------------------------------------------------------------------------------
1 | import {InscribeOrderData} from "../utils/api-types";
2 | import {OrderPay} from "./OrderPay";
3 | import {Spin, Typography} from "antd";
4 | import {OrderInscribing} from "./OrderInscribing";
5 |
6 | const {Text, Title} = Typography;
7 |
8 | export function OrderStatus({order}: { order: InscribeOrderData, }) {
9 |
10 | if (order.status === 'closed')
11 | return
12 | The order has been closed as payment was not made within 1 hour.
13 |
14 |
15 |
16 | if (order.status === 'pending') {
17 | return
18 | }
19 |
20 | if(order.status === 'minted' || order.status === 'inscribing'){
21 | return
22 | }
23 |
24 | // unknown status, loading
25 | return
26 |
27 |
28 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/components/SimpleRow.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from "react";
2 | import {Col, Radio, Row} from "antd";
3 |
4 | export function SimpleRow({label,value,valueSpan=12,extra}:{
5 | label?:string,
6 | value:ReactNode,
7 | valueSpan?:number,
8 | extra?:ReactNode
9 | }) {
10 | return
11 |
12 | {label}
13 |
14 |
15 | {value}
16 |
17 |
18 | {extra}
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './style/index.css';
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(
7 | document.getElementById('root') as HTMLElement
8 | );
9 | root.render(
10 |
11 | );
12 |
13 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/pages/Inscribe.tsx:
--------------------------------------------------------------------------------
1 | import {Button, Card, Flex, Input, InputNumber, message, Radio} from "antd";
2 | import {useEffect, useState} from "react";
3 | import {InscribeTextInput} from "../components/InscribeTextInput";
4 | import {SimpleRow} from "../components/SimpleRow";
5 | import {InscribeFileInput} from "../components/InscribeFileInput";
6 | import {FeeDetail} from "../components/FeeDetail";
7 | import {getStringByteCount, handleError, stringToBase64} from "../utils/utils";
8 | import {useUnisat} from "../provider/UniSatProvider";
9 | import {api} from "../utils/api";
10 | import {OrderDetail} from "../components/OrderDetail";
11 | import {EventEmitter} from "ahooks/lib/useEventEmitter";
12 | import {NetworkType} from "../types";
13 |
14 | enum InscribeType {
15 | text,
16 | file,
17 | }
18 |
19 | export type InscribeFileData = {
20 | filename: string
21 | dataURL: string
22 | size: number
23 | type?: string
24 |
25 | }
26 |
27 | export function Inscribe({newOrder$}: {
28 | newOrder$: EventEmitter
29 | }) {
30 | const {address, isConnected, network} = useUnisat();
31 | const [inscribeType, setInscribeType] = useState(InscribeType.text)
32 | const [fileList, setFileList] = useState([])
33 | const [receiveAddress, setReceiveAddress] = useState('')
34 | const [outputValue, setOutputValue] = useState(546)
35 | const [feeRate, setFeeRate] = useState()
36 | const [devFee, setDevFee] = useState(0)
37 | const [devAddress, setDevAddress] = useState('')
38 |
39 | const [loading, setLoading] = useState(false)
40 |
41 | const [orderId, setOrderId] = useState('')
42 |
43 | useEffect(() => {
44 | setReceiveAddress('')
45 | }, [network]);
46 |
47 | function submitText(text: string) {
48 | setFileList([
49 | {
50 | filename: text.slice(0, 512),
51 | dataURL: `data:text/plain;charset=utf-8;base64,${stringToBase64(text)}`,
52 | size: getStringByteCount(text)
53 | }
54 | ])
55 | }
56 |
57 |
58 | async function createOrder() {
59 | try {
60 | if (!receiveAddress) {
61 | return message.error('Please enter receive address')
62 | }
63 | if (!outputValue) {
64 | return message.error('Please enter output value')
65 | }
66 | if (!feeRate) {
67 | return message.error('Please enter fee rate')
68 | }
69 | if (fileList.length === 0) {
70 | return message.error('Please enter content')
71 | }
72 |
73 | setLoading(true)
74 |
75 | const {orderId} = await api.createOrder({
76 | receiveAddress,
77 | feeRate,
78 | outputValue,
79 | files: fileList.map(item => ({dataURL: item.dataURL, filename: item.filename})),
80 | devAddress,
81 | devFee,
82 | })
83 |
84 | newOrder$.emit()
85 |
86 | setOrderId(orderId)
87 | } catch (e) {
88 | handleError(e)
89 | } finally {
90 | setLoading(false)
91 | }
92 |
93 | }
94 |
95 | return
96 |
97 | {
103 | setFileList([])
104 | setInscribeType(v.target.value)
105 | }} value={inscribeType}>
106 | Text
107 | File
108 | }
109 | />
110 | {
111 | inscribeType === InscribeType.text &&
112 | }
113 | {
114 | inscribeType === InscribeType.file &&
115 |
116 | }
117 |
118 | setReceiveAddress(e.target.value)}/>
125 | }
126 | extra={
127 | isConnected
128 | ? {
129 | setReceiveAddress(address)
130 | }}>Use Connected Address
131 | : null
132 | }
133 | />
134 | setOutputValue(v || 0)}/>}
141 | />
142 | {
147 | setFeeRate(value as number)
148 | }}
149 | min={1}
150 | max={1000}
151 | />}
152 | valueSpan={6}
153 | extra={Get recommend fee rate here}
157 | />
158 | setDevAddress(e.target.value)}/>
165 | }
166 | />
167 | setDevFee(v || 0)}/>}
174 | />
175 |
182 | Inscribe
184 | }/>
185 |
186 |
187 | {orderId && {
188 | setOrderId('')
189 | }}/>}
190 |
191 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/pages/OrderList.tsx:
--------------------------------------------------------------------------------
1 | import {Button, Card, Flex, Table} from "antd";
2 | import {useCallback, useEffect, useState} from "react";
3 | import {api} from "../utils/api";
4 | import {InscribeOrderData} from "../utils/api-types";
5 | import {OrderDetail} from "../components/OrderDetail";
6 | import {handleError} from "../utils/utils";
7 | import {ReloadOutlined} from "@ant-design/icons";
8 | import {useUnisat} from "../provider/UniSatProvider";
9 | import {EventEmitter} from "ahooks/lib/useEventEmitter";
10 | import {useRequest} from "ahooks";
11 |
12 | const pageSize = 10
13 |
14 | interface OrderListProps {
15 | newOrder$: EventEmitter
16 | }
17 |
18 | export function OrderList({newOrder$}: OrderListProps) {
19 | const {network} = useUnisat();
20 | const [list, setList] = useState()
21 | const [page, setPage] = useState(1)
22 | const [total, setTotal] = useState(0)
23 |
24 | const [orderId, setOrderId] = useState('')
25 |
26 | const { loading,run} = useRequest(() => api.listOrder({
27 | size: pageSize,
28 | cursor: (page - 1) * pageSize,
29 | sort: 'desc',
30 | }), {
31 | refreshDeps: [page, network],
32 | onSuccess: (res) => {
33 | setList(res.list)
34 | setTotal(res.total)
35 | },
36 | onError:handleError
37 | })
38 |
39 | newOrder$.useSubscription(run)
40 |
41 | return
42 | Order List
43 | } loading={!list}/>
44 | }>
45 | {
54 | setPage(current || 1)
55 | }}
56 | rowKey={record => record.orderId}
57 | columns={[
58 | {
59 | title: 'OrderId',
60 | dataIndex: 'orderId',
61 | },
62 | {
63 | title: 'Status',
64 | dataIndex: 'status',
65 | }, {
66 | title: 'Crate Time',
67 | dataIndex: 'createTime',
68 | render: (time: number) => new Date(time).toLocaleString()
69 | },
70 | ]}
71 | onRow={record => {
72 | return {
73 | onClick: () => {
74 | console.log(record.orderId)
75 | setOrderId(record.orderId)
76 | }
77 | }
78 | }}
79 |
80 | />
81 | {orderId && {
82 | setOrderId('')
83 | }}/>}
84 |
85 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/provider/UniSatProvider.tsx:
--------------------------------------------------------------------------------
1 | import {createContext, ReactNode, useCallback, useContext, useEffect, useState} from "react";
2 | import {handleError, sleep} from "../utils/utils";
3 | import {unisatUtils} from "../utils/unisatUtils";
4 | import {NetworkType} from "../types";
5 | import {setApiNetwork} from "../utils/httpUtils";
6 |
7 | interface UnisatContextType {
8 | isInstalled: boolean;
9 | isConnected: boolean;
10 | network: NetworkType;
11 | switchNetwork: (network: NetworkType) => void;
12 | address: string;
13 | pubkey: string;
14 | connect: () => void;
15 | signMessage: (msg: string) => Promise;
16 | signPsbt: (psbt: string) => Promise;
17 | }
18 |
19 | const UnisatContext = createContext({
20 | isInstalled: false,
21 | isConnected: false,
22 | address: '',
23 | pubkey: '',
24 | connect: () => {
25 | },
26 | signMessage: (msg: string) => Promise.resolve(''),
27 | signPsbt: (psbt: string) => Promise.resolve(''),
28 | network: NetworkType.livenet,
29 | switchNetwork: () => {
30 | }
31 | })
32 |
33 |
34 | export function useUnisat() {
35 | const context = useContext(UnisatContext);
36 | if (!context) {
37 | throw Error('Feature flag hooks can only be used by children of UnisatProvider.');
38 | } else {
39 | return context;
40 | }
41 | }
42 |
43 | export default function UnisatProvider({children}: {
44 | children: ReactNode
45 | }) {
46 | const [network, setNetwork] = useState(NetworkType.livenet)
47 | const [isInstalled, setIsInstalled] = useState(false)
48 | const [isConnected, setIsConnected] = useState(false)
49 | const [address, setAddress] = useState('')
50 | const [pubkey, setPubkey] = useState('')
51 |
52 |
53 | useEffect(() => {
54 |
55 | async function init() {
56 | let install = !!window.unisat;
57 | setIsInstalled(install);
58 |
59 | // 额外检查
60 | for (let i = 0; i < 10 && !install; i += 1) {
61 | await sleep(100 + i * 100);
62 | install = !!window.unisat;
63 | if (install) {
64 | setIsInstalled(install);
65 | break;
66 | }
67 | }
68 |
69 | if (install) {
70 | const address = await unisatUtils.getAccounts()
71 | if (address) {
72 | await switchNetwork(await unisatUtils.getNetwork())
73 |
74 | setPubkey(await unisatUtils.getPublicKey())
75 | // connected
76 | setIsConnected(true)
77 | setAddress(address)
78 | }
79 | }
80 | }
81 |
82 | init().then().catch(handleError);
83 |
84 | }, []);
85 |
86 | const connect = useCallback(async () => {
87 | try {
88 | await unisatUtils.checkNetwork(network);
89 | const address = await unisatUtils.requestAccounts();
90 | if (address) {
91 | setPubkey(await unisatUtils.getPublicKey())
92 | setIsConnected(true)
93 | setAddress(address)
94 | }
95 | } catch (e) {
96 | handleError(e)
97 | }
98 |
99 | }, [network])
100 |
101 | useEffect(() => {
102 | async function onAppNetworkChange() {
103 | try {
104 | await unisatUtils.checkNetwork(network);
105 | const address = await unisatUtils.getAccounts()
106 | if (address) {
107 | setAddress(address)
108 | }
109 | } catch (e) {
110 | handleError(e)
111 | }
112 | }
113 |
114 | if (isConnected) {
115 | onAppNetworkChange().then()
116 | }
117 | }, [isConnected, network]);
118 |
119 | const signMessage = useCallback((msg: string) => {
120 | return unisatUtils.signMessage(msg, 'bip322-simple')
121 | }, [])
122 |
123 | const signPsbt = useCallback((psbt: string) => {
124 | return unisatUtils.signPsbt(psbt)
125 | }, []);
126 |
127 | const switchNetwork = useCallback(async (_network: NetworkType) => {
128 | try {
129 | await unisatUtils.switchNetwork(_network)
130 | setApiNetwork(_network)
131 | setNetwork(_network)
132 | } catch (e) {
133 | handleError(e)
134 | }
135 |
136 |
137 | }, [])
138 |
139 | const value = {
140 | isInstalled,
141 | isConnected,
142 | address,
143 | pubkey,
144 | connect,
145 | signMessage,
146 | signPsbt,
147 | network,
148 | switchNetwork,
149 | }
150 |
151 | return
152 | {children}
153 |
154 |
155 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/style/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/style/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/types.ts:
--------------------------------------------------------------------------------
1 | export enum NetworkType {
2 | livenet = 'livenet',
3 | testnet = 'testnet',
4 | }
5 |
6 |
7 | export type UnisatWalletInterface = {
8 | getAccounts(): Promise;
9 | requestAccounts(): Promise;
10 | getNetwork(): Promise;
11 | switchNetwork(network: NetworkType): Promise;
12 | sendBitcoin(address: string, amount: number, options: any): Promise;
13 | on(event: string, listener: Function): void;
14 | removeListener(event: string, listener: Function): void;
15 | signMessage(message: string, type?: string): Promise;
16 | signPsbt(psbt: string, opt: { autoFinalized: boolean }): Promise;
17 | getPublicKey(): Promise;
18 | getBalance(): Promise<{
19 | confirmed: number,
20 | unconfirmed: number,
21 | total: number
22 | }>;
23 | inscribeTransfer(tick: string, amount?: number | string): Promise<{
24 | amount: string,
25 | inscriptionId: string
26 | inscriptionNumber: number
27 | ticker: string
28 | }>;
29 | getInscriptions(num: number): Promise;
30 | }
31 |
32 |
33 | declare global {
34 | interface Window {
35 | unisat: UnisatWalletInterface;
36 | }
37 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/api-types.ts:
--------------------------------------------------------------------------------
1 | export type CreateOrderFile = {
2 | filename: string,
3 | dataURL: string,
4 | }
5 |
6 | export type CreateOrderReq = {
7 | receiveAddress: string,
8 | feeRate: number,
9 | outputValue: number,
10 | files: CreateOrderFile[]
11 | devAddress: string
12 | devFee: number
13 | }
14 |
15 | export type ListOrderReq = {
16 | cursor: number,
17 | size: number,
18 | sort?: 'asc' | 'desc',
19 | status?: InscribeOrderStatus,
20 | receiveAddress?: string,
21 | clientId?: string,
22 | }
23 |
24 | export type ListOrderRes = {
25 | list: InscribeOrderData[],
26 | total: number,
27 | }
28 |
29 | export enum InscribeOrderStatus {
30 | // when create order
31 | pending = 'pending',
32 | // pay not enough, need pay more
33 | payment_notenough = 'payment_notenough',
34 | // pay over, need choose continue or refund
35 | payment_overpay = 'payment_overpay',
36 | // there is an inscription in payment transaction, need refund
37 | payment_withinscription = 'payment_withinscription',
38 | // in some case, payment transaction need be confirmed
39 | payment_waitconfirmed = 'payment_waitconfirmed',
40 | // payment success
41 | payment_success = 'payment_success',
42 | // ready to inscribe
43 | ready = 'ready',
44 | inscribing = 'inscribing',
45 | minted = 'minted',
46 | closed = 'closed',
47 | refunded = 'refunded',
48 | cancel = 'cancel',
49 | }
50 |
51 | export enum FileStatus {
52 | pending = 'pending',
53 | unconfirmed = 'unconfirmed',
54 | confirmed = 'confirmed',
55 | }
56 |
57 | export type InscribeOrderData = {
58 | orderId: string,
59 | status: InscribeOrderStatus,
60 | payAddress: string,
61 | receiveAddress: string,
62 | amount: number, // need to pay amount
63 | paidAmount: number, // paid amount
64 | outputValue: number,
65 | feeRate: number,
66 | minerFee: number,
67 | serviceFee: number,
68 | files: {
69 | filename: string,
70 | status: FileStatus,
71 | inscriptionId: string,
72 | }[],
73 | count: number,
74 | pendingCount: number,
75 | unconfirmedCount: number,
76 | confirmedCount: number,
77 | createTime: number,
78 | devFee: number,
79 | refundAmount?: number,
80 | refundTxid?: string,
81 | refundFeeRate?: number;
82 | }
83 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/api.ts:
--------------------------------------------------------------------------------
1 | import {CreateOrderReq, InscribeOrderData, ListOrderReq, ListOrderRes} from "./api-types";
2 | import {get, post} from "./httpUtils";
3 |
4 | export const api = {
5 | createOrder(req: CreateOrderReq): Promise {
6 | return post('/v2/inscribe/order/create', req)
7 | },
8 | listOrder(req:ListOrderReq):Promise{
9 | return get('/v2/inscribe/order/list', req)
10 | },
11 | orderInfo(orderId: string): Promise {
12 | return get(`/v2/inscribe/order/${orderId}`)
13 | },
14 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/httpUtils.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {NetworkType} from "../types";
3 |
4 | let apiKey = localStorage.getItem('apiKey') || '';
5 | let network = NetworkType.livenet;
6 |
7 | export function setApiNetwork(type: NetworkType) {
8 | network = type;
9 | }
10 |
11 |
12 | export function setApiKey(key: string) {
13 | apiKey = key;
14 | }
15 |
16 | function createApi(baseURL: string) {
17 | const api = axios.create({
18 | baseURL,
19 | timeout: 10000,
20 | headers: {
21 | "Content-Type": "application/json",
22 | },
23 | })
24 |
25 | api.interceptors.request.use((config) => {
26 | if (!apiKey) {
27 | throw new Error("input apiKey and reload page");
28 | }
29 | config.headers.Authorization = `Bearer ${apiKey}`;
30 | return config;
31 | });
32 | return api;
33 | }
34 |
35 | const mainnetApi = createApi("https://open-api.unisat.io");
36 | const testnetApi = createApi("https://open-api-testnet.unisat.io");
37 |
38 | function getApi() {
39 | return network===NetworkType.testnet ? testnetApi : mainnetApi;
40 | }
41 |
42 |
43 | export const get = async (url: string, params?: any) => {
44 | const res = await getApi().get(url, {params});
45 | if (res.status !== 200) {
46 | throw new Error(res.statusText);
47 | }
48 |
49 | const responseData = res.data;
50 |
51 | if (responseData.code !== 0) {
52 | throw new Error(responseData.msg);
53 | }
54 | return responseData.data;
55 | };
56 |
57 | export const post = async (url: string, data?: any) => {
58 | const res = await getApi().post(url, data,);
59 | if (res.status !== 200) {
60 | throw new Error(res.statusText);
61 | }
62 |
63 | const responseData = res.data;
64 |
65 | if (responseData.code !== 0) {
66 | throw new Error(responseData.msg);
67 | }
68 |
69 | return responseData.data;
70 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/orderUtils.ts:
--------------------------------------------------------------------------------
1 | import {InscribeOrderData} from "./api-types";
2 |
3 | export function isOrderProcessing(data?: InscribeOrderData | null) {
4 | if (!data)
5 | return false;
6 | const {status} = data;
7 | return status !== 'minted' && status !== 'closed' && status !== 'refunded' && status !== 'cancel';
8 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/unisatUtils.ts:
--------------------------------------------------------------------------------
1 | import {handleError} from "./utils";
2 | import {NetworkType} from "../types";
3 |
4 | export const unisatUtils = {
5 | async getAccounts() {
6 | try {
7 | const accounts = await window.unisat.getAccounts();
8 | if (accounts && accounts.length > 0) {
9 | return accounts[0];
10 | }
11 | } catch (e: any) {
12 | handleError(e);
13 | }
14 | return '';
15 | },
16 | async requestAccounts() {
17 | try {
18 | const accounts = await window.unisat.requestAccounts();
19 | if (accounts && accounts.length > 0) {
20 | return accounts[0];
21 | }
22 | } catch (e: any) {
23 | handleError(e);
24 | }
25 | return '';
26 | },
27 | signMessage(message: string, type?: string) {
28 | return window.unisat.signMessage(message, type);
29 | },
30 | signPsbt(psbt: string) {
31 | return window.unisat.signPsbt(psbt, {autoFinalized: false});
32 | },
33 | getNetwork(): Promise {
34 | return window.unisat.getNetwork();
35 | },
36 | switchNetwork(network: NetworkType): Promise {
37 | return window.unisat.switchNetwork(network);
38 | },
39 |
40 | async checkNetwork(network: NetworkType): Promise {
41 | if (network !== await this.getNetwork()) {
42 | await this.switchNetwork(network);
43 | }
44 | },
45 | inscribeTransfer(tick: string, amount?: number | string) {
46 | return window.unisat.inscribeTransfer(tick, amount)
47 | },
48 | getPublicKey() {
49 | return window.unisat.getPublicKey();
50 | },
51 | sendBitcoin(toAddress: string, amount: number, options: any) {
52 | return window.unisat.sendBitcoin(toAddress, amount, options);
53 | },
54 |
55 | }
--------------------------------------------------------------------------------
/unisat-inscribe-demo/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import {Buffer} from "buffer";
2 | import {message} from "antd";
3 | import {Base64} from "js-base64";
4 |
5 | export function sleep(ms: number) {
6 | return new Promise((resolve) => setTimeout(resolve, ms));
7 | }
8 |
9 | export function stringToHex(stringToEncode: string) {
10 | return Buffer.from(stringToEncode).toString('hex')
11 | }
12 |
13 | export function stringToBase64(stringToEncode: string) {
14 | // btoa only support ascii, use js-base64 instead
15 | return Base64.encode(stringToEncode)
16 | }
17 |
18 | export function isTicketValid(ticket: string) {
19 | return Buffer.from(ticket).length === 4
20 | }
21 |
22 | export function handleError(e: any) {
23 | return message.error((e && e.message) || e)
24 | }
25 |
26 | export function showLongString(str: string, length = 10) {
27 | if (!str) return '';
28 | if (str.length <= length) return str;
29 | return `${str.substring(0, length / 2)}...${str.substring(
30 | str.length - length / 2,
31 | str.length
32 | )}`;
33 | }
34 |
35 | export function getSizeShow(size: number, fixed = 3): string {
36 | if (size < 1024) return size + " B";
37 |
38 | return (size / 1024).toFixed(fixed) + " KB";
39 | }
40 |
41 |
42 | export const isUtf8 = async (file: File) => {
43 | return await new Promise((resolve, reject) => {
44 | const reader = new FileReader();
45 | reader.readAsText(file);
46 |
47 | reader.onloadend = (e: any): void => {
48 | const content = e.target.result;
49 | const encodingRight = content.indexOf("�") === -1;
50 |
51 | if (encodingRight) {
52 | resolve(encodingRight);
53 | } else {
54 | reject("Only UTF-8 format file supported. ");
55 | }
56 | };
57 |
58 | reader.onerror = () => {
59 | reject(
60 | "Failed to read the file content. Please check whether the file is damaged"
61 | );
62 | };
63 | });
64 | };
65 |
66 |
67 |
68 |
69 | export function getStringByteCount(str: string) {
70 | let totalLength = 0;
71 | let charCode;
72 | for (let i = 0; i < str.length; i++) {
73 | charCode = str.charCodeAt(i);
74 | if (charCode < 0x007f) {
75 | totalLength++;
76 | } else if (0x0080 <= charCode && charCode <= 0x07ff) {
77 | totalLength += 2;
78 | } else if (0x0800 <= charCode && charCode <= 0xffff) {
79 | totalLength += 3;
80 | } else {
81 | totalLength += 4;
82 | }
83 | }
84 | return totalLength;
85 | }
86 |
--------------------------------------------------------------------------------
/unisat-inscribe-demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/unisat-web3-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/unisat-web3-demo/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/unisat-web3-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unisat-web3-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.5.2",
10 | "@types/node": "^16.18.14",
11 | "@types/react": "^18.0.28",
12 | "@types/react-dom": "^18.0.11",
13 | "antd": "^5.2.3",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-scripts": "5.0.1",
17 | "typescript": "^4.9.5",
18 | "web-vitals": "^2.1.4"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/unisat-web3-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-web3-demo/public/favicon.ico
--------------------------------------------------------------------------------
/unisat-web3-demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Unisat Wallet Demo
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/unisat-web3-demo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-web3-demo/public/logo192.png
--------------------------------------------------------------------------------
/unisat-web3-demo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unisat-wallet/dev-support/e50df50b8c7d1b04690261dca0cd511ed0d4f657/unisat-web3-demo/public/logo512.png
--------------------------------------------------------------------------------
/unisat-web3-demo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/unisat-web3-demo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import "./App.css";
3 | import { Button, Card, Input, Radio } from "antd";
4 |
5 | function App() {
6 | const [unisatInstalled, setUnisatInstalled] = useState(false);
7 | const [connected, setConnected] = useState(false);
8 | const [accounts, setAccounts] = useState([]);
9 | const [publicKey, setPublicKey] = useState("");
10 | const [address, setAddress] = useState("");
11 | const [balance, setBalance] = useState({
12 | confirmed: 0,
13 | unconfirmed: 0,
14 | total: 0,
15 | });
16 | const [network, setNetwork] = useState("livenet");
17 |
18 | const getBasicInfo = async () => {
19 | const unisat = (window as any).unisat;
20 | const [address] = await unisat.getAccounts();
21 | setAddress(address);
22 |
23 | const publicKey = await unisat.getPublicKey();
24 | setPublicKey(publicKey);
25 |
26 | const balance = await unisat.getBalance();
27 | setBalance(balance);
28 |
29 | const network = await unisat.getNetwork();
30 | setNetwork(network);
31 | };
32 |
33 | const selfRef = useRef<{ accounts: string[] }>({
34 | accounts: [],
35 | });
36 | const self = selfRef.current;
37 | const handleAccountsChanged = (_accounts: string[]) => {
38 | if (self.accounts[0] === _accounts[0]) {
39 | // prevent from triggering twice
40 | return;
41 | }
42 | self.accounts = _accounts;
43 | if (_accounts.length > 0) {
44 | setAccounts(_accounts);
45 | setConnected(true);
46 |
47 | setAddress(_accounts[0]);
48 |
49 | getBasicInfo();
50 | } else {
51 | setConnected(false);
52 | }
53 | };
54 |
55 | const handleNetworkChanged = (network: string) => {
56 | setNetwork(network);
57 | getBasicInfo();
58 | };
59 |
60 | useEffect(() => {
61 |
62 | async function checkUnisat() {
63 | let unisat = (window as any).unisat;
64 |
65 | for (let i = 1; i < 10 && !unisat; i += 1) {
66 | await new Promise((resolve) => setTimeout(resolve, 100*i));
67 | unisat = (window as any).unisat;
68 | }
69 |
70 | if(unisat){
71 | setUnisatInstalled(true);
72 | }else if (!unisat)
73 | return;
74 |
75 | unisat.getAccounts().then((accounts: string[]) => {
76 | handleAccountsChanged(accounts);
77 | });
78 |
79 | unisat.on("accountsChanged", handleAccountsChanged);
80 | unisat.on("networkChanged", handleNetworkChanged);
81 |
82 | return () => {
83 | unisat.removeListener("accountsChanged", handleAccountsChanged);
84 | unisat.removeListener("networkChanged", handleNetworkChanged);
85 | };
86 | }
87 |
88 | checkUnisat().then();
89 | }, []);
90 |
91 | if (!unisatInstalled) {
92 | return (
93 |
106 | );
107 | }
108 | const unisat = (window as any).unisat;
109 | return (
110 |
183 | );
184 | }
185 |
186 | function SignPsbtCard() {
187 | const [psbtHex, setPsbtHex] = useState("");
188 | const [psbtResult, setPsbtResult] = useState("");
189 | return (
190 |
191 |
192 |
PsbtHex:
193 |
{
196 | setPsbtHex(e.target.value);
197 | }}
198 | >
199 |
200 |
201 |
Result:
202 |
{psbtResult}
203 |
204 | {
207 | try {
208 | const psbtResult = await (window as any).unisat.signPsbt(psbtHex);
209 | setPsbtResult(psbtResult);
210 | } catch (e) {
211 | setPsbtResult((e as any).message);
212 | }
213 | }}
214 | >
215 | Sign Psbt
216 |
217 |
218 | );
219 | }
220 |
221 | function SignMessageCard() {
222 | const [message, setMessage] = useState("hello world~");
223 | const [signature, setSignature] = useState("");
224 | return (
225 |
226 |
227 |
Message:
228 |
{
231 | setMessage(e.target.value);
232 | }}
233 | >
234 |
235 |
236 |
Signature:
237 |
{signature}
238 |
239 | {
242 | const signature = await (window as any).unisat.signMessage(message);
243 | setSignature(signature);
244 | }}
245 | >
246 | Sign Message
247 |
248 |
249 | );
250 | }
251 |
252 | function PushTxCard() {
253 | const [rawtx, setRawtx] = useState("");
254 | const [txid, setTxid] = useState("");
255 | return (
256 |
261 |
262 |
rawtx:
263 |
{
266 | setRawtx(e.target.value);
267 | }}
268 | >
269 |
270 |
271 |
txid:
272 |
{txid}
273 |
274 | {
277 | try {
278 | const txid = await (window as any).unisat.pushTx(rawtx);
279 | setTxid(txid);
280 | } catch (e) {
281 | setTxid((e as any).message);
282 | }
283 | }}
284 | >
285 | PushTx
286 |
287 |
288 | );
289 | }
290 |
291 | function PushPsbtCard() {
292 | const [psbtHex, setPsbtHex] = useState("");
293 | const [txid, setTxid] = useState("");
294 | return (
295 |
296 |
297 |
psbt hex:
298 |
{
301 | setPsbtHex(e.target.value);
302 | }}
303 | >
304 |
305 |
306 |
txid:
307 |
{txid}
308 |
309 | {
312 | try {
313 | const txid = await (window as any).unisat.pushPsbt(psbtHex);
314 | setTxid(txid);
315 | } catch (e) {
316 | setTxid((e as any).message);
317 | }
318 | }}
319 | >
320 | pushPsbt
321 |
322 |
323 | );
324 | }
325 |
326 | function SendBitcoin() {
327 | const [toAddress, setToAddress] = useState(
328 | "tb1qmfla5j7cpdvmswtruldgvjvk87yrflrfsf6hh0"
329 | );
330 | const [satoshis, setSatoshis] = useState(1000);
331 | const [txid, setTxid] = useState("");
332 | return (
333 |
334 |
335 |
Receiver Address:
336 |
{
339 | setToAddress(e.target.value);
340 | }}
341 | >
342 |
343 |
344 |
345 |
Amount: (satoshis)
346 |
{
349 | setSatoshis(parseInt(e.target.value));
350 | }}
351 | >
352 |
353 |
354 |
txid:
355 |
{txid}
356 |
357 | {
360 | try {
361 | const txid = await (window as any).unisat.sendBitcoin(
362 | toAddress,
363 | satoshis
364 | );
365 | setTxid(txid);
366 | } catch (e) {
367 | setTxid((e as any).message);
368 | }
369 | }}
370 | >
371 | SendBitcoin
372 |
373 |
374 | );
375 | }
376 |
377 | export default App;
378 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(
8 | document.getElementById('root') as HTMLElement
9 | );
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/unisat-web3-demo/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/unisat-web3-demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 |
4 | const ENV = process.env.BABEL_ENV || "umd";
5 | // esm cjs
6 | console.log("++++++++++++++++++++++++++++++");
7 | console.log("BABEL_ENV: ", ENV);
8 | console.log("++++++++++++++++++++++++++++++");
9 | const presets = [
10 | [
11 | "@babel/preset-env",
12 | {
13 | modules: false,
14 | browserslistEnv: ENV,
15 | loose: true,
16 | bugfixes: true,
17 | },
18 | ],
19 | "@babel/preset-typescript",
20 | ];
21 | const plugins = [
22 | [
23 | "@babel/plugin-transform-runtime",
24 | {
25 | useESModules: true,
26 | },
27 | ],
28 | ];
29 |
30 | return { presets, plugins, ignore: [/@babel[\\|/]runtime/] };
31 | };
32 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
4 | parserOptions: {
5 | project: ["./tsconfig.json"],
6 | },
7 | parser: "@typescript-eslint/parser",
8 | plugins: ["@typescript-eslint"],
9 | root: true,
10 | rules: {
11 | "@typescript-eslint/no-var-requires": "off",
12 | "no-undef": "off",
13 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off",
14 | "@typescript-eslint/no-empty-function": "off",
15 | "@typescript-eslint/no-explicit-any": "off",
16 | "prefer-const": "off",
17 | "@typescript-eslint/ban-types": "off",
18 | "no-empty": "off",
19 | "@typescript-eslint/ban-ts-comment": "off",
20 | "@typescript-eslint/no-floating-promises": ["error"],
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vscode/*
3 | dist/
4 | .DS_Store
5 | lib
6 | es
7 | umd
8 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2022 Unisat
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/README.md:
--------------------------------------------------------------------------------
1 | # Wallet Sdk Examples
2 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/brc20-5byte-mint.ts:
--------------------------------------------------------------------------------
1 | import { AddressType } from "@unisat/wallet-sdk";
2 | import { bitcoin } from "@unisat/wallet-sdk/lib/bitcoin-core";
3 | import { NetworkType } from "@unisat/wallet-sdk/lib/network";
4 | import { LocalWallet } from "@unisat/wallet-sdk/lib/wallet";
5 | import { OpenApi } from "./open-api";
6 |
7 | const mintBrc205Byte = async () => {
8 | const openapi = new OpenApi({
9 | baseUrl: "https://open-api-fractal.unisat.io",
10 | apiKey: "YOUR-API-KEY",
11 | });
12 |
13 | const wallet = new LocalWallet(
14 | "YOUR-PRIVATE-KEY",
15 | AddressType.P2WPKH,
16 | NetworkType.MAINNET
17 | );
18 |
19 | const toAddress = wallet.address;
20 | const { orderId } = await openapi.createBrc205ByteMint({
21 | deployerAddress: wallet.address,
22 | deployerPubkey: wallet.pubkey,
23 | receiveAddress: toAddress,
24 | feeRate: 1,
25 | outputValue: 546,
26 | brc20Ticker: "WORLD",
27 | brc20Amount: "1000",
28 | devAddress: wallet.address,
29 | devFee: 0,
30 | });
31 |
32 | console.log("orderId:", orderId);
33 |
34 | const toSignData1 = await openapi.requestCommitBrc205ByteMint({
35 | orderId: orderId,
36 | });
37 |
38 | const psbt1 = bitcoin.Psbt.fromHex(toSignData1.psbtHex);
39 |
40 | await wallet.signPsbt(psbt1, {
41 | autoFinalized: false,
42 | toSignInputs: toSignData1.inputsToSign.map((v) => ({
43 | address: v.address,
44 | index: v.signingIndexes[0],
45 | })),
46 | });
47 | const signedPsbtHex1 = psbt1.toHex();
48 |
49 | const toSignData2 = await openapi.signCommitBrc205ByteMint({
50 | orderId,
51 | psbt: signedPsbtHex1,
52 | });
53 |
54 | const psbt2 = bitcoin.Psbt.fromHex(toSignData2.psbtHex);
55 | await wallet.signPsbt(psbt2, {
56 | autoFinalized: false,
57 | toSignInputs: toSignData2.inputsToSign.map((v) => ({
58 | address: v.address,
59 | index: v.signingIndexes[0],
60 | })),
61 | });
62 |
63 | const signedPsbtHex2 = psbt2.toHex();
64 |
65 | const { inscriptionId } = await openapi.signRevealBrc205ByteMint({
66 | orderId,
67 | psbt: signedPsbtHex2,
68 | });
69 |
70 | console.log("minted:", inscriptionId);
71 | };
72 | mintBrc205Byte();
73 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/create-bid.ts:
--------------------------------------------------------------------------------
1 | import { AddressType } from "@unisat/wallet-sdk";
2 | import { bitcoin } from "@unisat/wallet-sdk/lib/bitcoin-core";
3 | import { NetworkType } from "@unisat/wallet-sdk/lib/network";
4 | import { LocalWallet } from "@unisat/wallet-sdk/lib/wallet";
5 | import { OpenApi } from "./open-api";
6 |
7 | const runCreateBidExample = async () => {
8 | const wallet = new LocalWallet(
9 | "xxxxxx",
10 | AddressType.P2TR,
11 | NetworkType.MAINNET
12 | );
13 |
14 | const openapi = new OpenApi({
15 | baseUrl: "https://open-api.unisat.io",
16 | apiKey: "xxxxx",
17 | });
18 | const type = "domain";
19 |
20 | const auctionId = "42hw91h3lc273c8ebdcw7wi4s4jrivuy";
21 | const data = await openapi.createBid({
22 | type: "domain",
23 | auctionId,
24 | bidPrice: 10000,
25 | address: wallet.address,
26 | pubkey: wallet.pubkey,
27 | feeRate: 66,
28 | });
29 |
30 | const psbt = bitcoin.Psbt.fromHex(data.psbtBid, {
31 | network: bitcoin.networks.bitcoin,
32 | });
33 |
34 | await wallet.signPsbt(psbt, {
35 | autoFinalized: false,
36 | });
37 |
38 | const res = await openapi.confirmBid({
39 | type,
40 | auctionId: auctionId,
41 | fromBase64: false,
42 | bidId: data.bidId,
43 | psbtBid: psbt.toHex(),
44 | psbtBid2: "",
45 | psbtSettle: "",
46 | });
47 | console.log("success", res);
48 | };
49 | runCreateBidExample();
50 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/create-put-on.ts:
--------------------------------------------------------------------------------
1 | import { AddressType } from "@unisat/wallet-sdk";
2 | import { bitcoin } from "@unisat/wallet-sdk/lib/bitcoin-core";
3 | import { NetworkType } from "@unisat/wallet-sdk/lib/network";
4 | import { LocalWallet } from "@unisat/wallet-sdk/lib/wallet";
5 | import { OpenApi } from "./open-api";
6 |
7 | const runCreatePutOnExample = async () => {
8 | const wallet = new LocalWallet(
9 | "xxxx",
10 | AddressType.P2WPKH,
11 | NetworkType.MAINNET
12 | );
13 |
14 | const openapi = new OpenApi({
15 | baseUrl: "https://open-api.unisat.io",
16 | apiKey: "xxxxx",
17 | });
18 |
19 | const type = "domain";
20 |
21 | const data = await openapi.createPutOnPrepare({
22 | type,
23 | inscriptionId:
24 | "789d2c6ec282d852b34b655fd8fe6a383747a7aee9fe8cd1f24208cb9bcecce9i0",
25 | initPrice: "100000",
26 | pubkey: wallet.pubkey,
27 | marketType: "fixedPrice",
28 | unitPrice: "0.001",
29 | });
30 |
31 | const psbt = bitcoin.Psbt.fromHex(data.psbt, {
32 | network: bitcoin.networks.bitcoin,
33 | });
34 |
35 | await wallet.signPsbt(psbt, {
36 | autoFinalized: false,
37 | });
38 | const res = await openapi.confirmPutOn({
39 | type,
40 | auctionId: data.auctionId,
41 | psbt: psbt.toHex(),
42 | fromBase64: false,
43 | });
44 | console.log("put on success", res);
45 | };
46 | runCreatePutOnExample();
47 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/mempool-api/index.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosResponse } from "axios";
2 |
3 | interface ApiResponse {
4 | code: number;
5 | msg: string;
6 | data: T;
7 | }
8 |
9 | class RequestError extends Error {
10 | constructor(
11 | public message: string,
12 | public status?: number,
13 | public response?: AxiosResponse
14 | ) {
15 | super((response && response.config ? response.config.url : "") + message);
16 | }
17 |
18 | isApiException = true;
19 | }
20 |
21 | interface RecommendedFees {
22 | fastestFee: number;
23 | halfHourFee: number;
24 | hourFee: number;
25 | economyFee: number;
26 | minimumFee: number;
27 | }
28 |
29 | export class MempoolApi {
30 | private axios: AxiosInstance;
31 |
32 | constructor(params: { baseUrl: string }) {
33 | this.axios = axios.create({
34 | baseURL: params.baseUrl,
35 | headers: {
36 | Accept: "application/json",
37 | "Content-Type": "application/json",
38 | },
39 | });
40 |
41 | this.axios.interceptors.response.use(
42 | (async (
43 | response: AxiosResponse<{
44 | code: number;
45 | msg: string;
46 | data: any;
47 | }>
48 | ) => {
49 | const res = response.data;
50 | return res;
51 | }) as any,
52 | (error) => {
53 | if (error.status == 400) {
54 | return Promise.reject(error.response.data);
55 | }
56 | if (error.response) {
57 | return Promise.reject(
58 | new RequestError(
59 | error.response.data,
60 | error.response.status,
61 | error.response
62 | )
63 | );
64 | }
65 |
66 | if (error.isAxiosError) {
67 | return Promise.reject(new RequestError("noInternetConnection"));
68 | }
69 | return Promise.reject(error);
70 | }
71 | );
72 | }
73 |
74 | async getRecommendFee() {
75 | const response = await this.axios.get(
76 | "/v1/fees/recommended",
77 | {}
78 | );
79 | return response;
80 | }
81 |
82 | async getRawTx(txid: string) {
83 | const response = await this.axios.get(`/tx/${txid}/hex`);
84 | return response;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/open-api/index.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosResponse } from "axios";
2 |
3 | interface CreatePutOnPrepareResponse {
4 | auctionId: string;
5 | psbt: string;
6 | signIndexes: number[];
7 | }
8 |
9 | export interface CreateBidPrepareResponse {
10 | serverFee: number;
11 | serverReal: number;
12 | serverFeeRate: number;
13 | txSize: number;
14 | feeRate: number;
15 | nftValue: number;
16 | discounts: any[];
17 | inscriptionCount: number;
18 | availableBalance: number;
19 | allBalance: number;
20 | }
21 |
22 | export interface UTXO {
23 | address: string;
24 | codeType: number;
25 | height: number;
26 | idx: number;
27 | inscriptions: {
28 | inscriptionId: string;
29 | inscriptionNumber: number;
30 | isBRC20: boolean;
31 | moved: boolean;
32 | offset: number;
33 | }[];
34 | isOpInRBF: boolean;
35 | satoshi: number;
36 | scriptPk: string;
37 | scriptType: string;
38 | txid: string;
39 | vout: number;
40 | rawtx?: string;
41 | }
42 |
43 | interface InscriptionInfo {
44 | address: string;
45 | inscriptionNumber: number;
46 | contentType: string;
47 | utxo: UTXO;
48 | }
49 |
50 | class RequestError extends Error {
51 | constructor(
52 | public message: string,
53 | public status?: number,
54 | public response?: AxiosResponse
55 | ) {
56 | super((response && response.config ? response.config.url : "") + message);
57 | }
58 |
59 | isApiException = true;
60 | }
61 |
62 | export class OpenApi {
63 | private axios: AxiosInstance;
64 |
65 | constructor(params: { baseUrl: string; apiKey: string }) {
66 | this.axios = axios.create({
67 | baseURL: params.baseUrl,
68 | headers: {
69 | Accept: "application/json",
70 | "Content-Type": "application/json",
71 | Authorization: `Bearer ${params.apiKey}`,
72 | },
73 | });
74 |
75 | this.axios.interceptors.response.use(
76 | (async (
77 | response: AxiosResponse<{
78 | code: number;
79 | msg: string;
80 | data: any;
81 | }>
82 | ) => {
83 | const res = response.data;
84 | if (res.code != 0) {
85 | throw new RequestError(res.msg);
86 | }
87 | return res.data;
88 | }) as any,
89 | (error) => {
90 | if (error.response) {
91 | return Promise.reject(
92 | new RequestError(
93 | error.response.data,
94 | error.response.status,
95 | error.response
96 | )
97 | );
98 | }
99 |
100 | if (error.isAxiosError) {
101 | return Promise.reject(new RequestError("noInternetConnection"));
102 | }
103 | return Promise.reject(error);
104 | }
105 | );
106 | }
107 |
108 | async createPutOnPrepare({
109 | type,
110 | inscriptionId,
111 | initPrice,
112 | unitPrice,
113 | pubkey,
114 | marketType,
115 | }: {
116 | type: "brc20" | "collection" | "domain";
117 | inscriptionId: string;
118 | initPrice: string;
119 | unitPrice: string;
120 | pubkey: string;
121 | marketType: "fixedPrice";
122 | }) {
123 | const response = await this.axios.post(
124 | `/v3/market/${type}/auction/create_put_on`,
125 | {
126 | inscriptionId,
127 | initPrice,
128 | unitPrice,
129 | pubkey,
130 | marketType,
131 | }
132 | );
133 | return response;
134 | }
135 |
136 | async confirmPutOn({
137 | type,
138 | auctionId,
139 | psbt,
140 | fromBase64,
141 | }: {
142 | type: "brc20" | "collection" | "domain";
143 | auctionId: string;
144 | psbt: string;
145 | fromBase64: boolean;
146 | }) {
147 | const response = await this.axios.post(
148 | `/v3/market/${type}/auction/confirm_put_on`,
149 | {
150 | auctionId,
151 | psbt,
152 | fromBase64,
153 | }
154 | );
155 | return response;
156 | }
157 |
158 | async createBidPrepare({
159 | type,
160 | auctionId,
161 | bidPrice,
162 | address,
163 | pubkey,
164 | }: {
165 | type: "brc20" | "collection" | "domain";
166 | auctionId: string;
167 | bidPrice: number;
168 | address: string;
169 | pubkey: string;
170 | }) {
171 | const response = await this.axios.post(
172 | `/v3/market/${type}/auction/create_bid_prepare`,
173 | {
174 | auctionId,
175 | bidPrice,
176 | address,
177 | pubkey,
178 | }
179 | );
180 | return response;
181 | }
182 |
183 | async createBid({
184 | type,
185 | address,
186 | auctionId,
187 | bidPrice,
188 | feeRate,
189 | pubkey,
190 | }: {
191 | type: "brc20" | "collection" | "domain";
192 | address: string;
193 | auctionId: string;
194 | bidPrice: number;
195 | feeRate: number;
196 | pubkey: string;
197 | }) {
198 | const response = await this.axios.post<
199 | null,
200 | {
201 | bidId: string;
202 | psbtBid: string;
203 | serverFee: number;
204 | networkFee: number;
205 | feeRate: number;
206 | nftValue: number;
207 | bidSignIndexes: number[];
208 | }
209 | >(`/v3/market/${type}/auction/create_bid`, {
210 | auctionId,
211 | feeRate,
212 | address,
213 | pubkey,
214 | bidPrice,
215 | });
216 | return response;
217 | }
218 |
219 | async confirmBid({
220 | type,
221 | bidId,
222 | psbtBid,
223 | psbtBid2,
224 | auctionId,
225 | psbtSettle,
226 | fromBase64,
227 | }: {
228 | type: "brc20" | "collection" | "domain";
229 | auctionId: string;
230 | fromBase64: boolean;
231 | bidId: string;
232 | psbtBid: string;
233 | psbtBid2: string;
234 | psbtSettle: string;
235 | }) {
236 | const response = await this.axios.post(
237 | `/v3/market/${type}/auction/confirm_bid`,
238 | {
239 | auctionId,
240 | bidId,
241 | psbtBid,
242 | psbtBid2,
243 | psbtSettle,
244 | fromBase64,
245 | }
246 | );
247 | return response;
248 | }
249 |
250 | async getInscriptionInfo(inscriptionId: string) {
251 | const response = await this.axios.get(
252 | `/v1/indexer/inscription/info/${inscriptionId}`
253 | );
254 | return response;
255 | }
256 |
257 | async getAddressUtxoData(address: string, cursor = 0, size = 16) {
258 | const response = await this.axios.get<
259 | null,
260 | {
261 | cursor: number;
262 | total: number;
263 | totalConfirmed: number;
264 | totalUnconfirmed: number;
265 | totalUnconfirmedSpend: number;
266 | utxo: UTXO[];
267 | }
268 | >(`/v1/indexer/address/${address}/utxo-data?cursor=${cursor}&size=${size}`);
269 | return response;
270 | }
271 |
272 | async pushtx(txHex: string) {
273 | const response = await this.axios.post(
274 | `/v1/indexer/local_pushtx`,
275 | { txHex }
276 | );
277 | return response;
278 | }
279 |
280 | async createBrc205ByteMint({
281 | deployerAddress,
282 | deployerPubkey,
283 | receiveAddress,
284 | feeRate,
285 | outputValue,
286 | brc20Ticker,
287 | brc20Amount,
288 | devAddress,
289 | devFee,
290 | }: {
291 | deployerAddress: string;
292 | deployerPubkey: string;
293 | receiveAddress: string;
294 | feeRate: number;
295 | outputValue: number;
296 | brc20Ticker: string;
297 | brc20Amount: string;
298 | devAddress: string;
299 | devFee: number;
300 | }) {
301 | const response = await this.axios.post<
302 | null,
303 | {
304 | orderId: string;
305 | }
306 | >(`/v2/inscribe/order/create/brc20-5byte-mint`, {
307 | deployerAddress,
308 | deployerPubkey,
309 | receiveAddress,
310 | feeRate,
311 | outputValue,
312 | brc20Ticker,
313 | brc20Amount,
314 | devAddress,
315 | devFee,
316 | });
317 | return response;
318 | }
319 |
320 | async requestCommitBrc205ByteMint({ orderId }: { orderId: string }) {
321 | const response = await this.axios.post<
322 | null,
323 | {
324 | psbtHex: string;
325 | inputsToSign: {
326 | address: string;
327 | signingIndexes: number[];
328 | }[];
329 | }
330 | >(`/v2/inscribe/order/request-commit/brc20-5byte-mint`, {
331 | orderId,
332 | });
333 | return response;
334 | }
335 |
336 | async signCommitBrc205ByteMint({
337 | orderId,
338 | psbt,
339 | }: {
340 | orderId: string;
341 | psbt: string;
342 | }) {
343 | const response = await this.axios.post<
344 | null,
345 | {
346 | psbtHex: string;
347 | inputsToSign: {
348 | address: string;
349 | signingIndexes: number[];
350 | }[];
351 | }
352 | >(`/v2/inscribe/order/sign-commit/brc20-5byte-mint`, {
353 | orderId,
354 | psbt,
355 | });
356 | return response;
357 | }
358 |
359 | async signRevealBrc205ByteMint({
360 | orderId,
361 | psbt,
362 | }: {
363 | orderId: string;
364 | psbt: string;
365 | }) {
366 | const response = await this.axios.post<
367 | null,
368 | {
369 | inscriptionId: string;
370 | }
371 | >(`/v2/inscribe/order/sign-reveal/brc20-5byte-mint`, {
372 | orderId,
373 | psbt,
374 | });
375 | return response;
376 | }
377 | }
378 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/open-api/types.ts:
--------------------------------------------------------------------------------
1 | export interface ApiResponse {
2 | code: number;
3 | msg: string;
4 | data: T;
5 | }
6 |
7 | export interface UTXO {
8 | address: string;
9 | codeType: number;
10 | height: number;
11 | idx: number;
12 | inscriptions: [
13 | {
14 | inscriptionId: string;
15 | inscriptionNumber: number;
16 | isBRC20: boolean;
17 | moved: boolean;
18 | offset: number;
19 | }
20 | ];
21 | atomicals: {
22 | atomicalId: string;
23 | atomicalNumber: number;
24 | isARC20: boolean;
25 | ticker: string;
26 | }[];
27 | isOpInRBF: boolean;
28 | satoshi: number;
29 | scriptPk: string;
30 | scriptType: string;
31 | txid: string;
32 | vout: number;
33 | }
34 |
35 | export interface TickerDetail {
36 | completeBlocktime: number;
37 | completeHeight: number;
38 | confirmedMinted: string;
39 | confirmedMinted1h: string;
40 | confirmedMinted24h: string;
41 | creator: string;
42 | decimal: number;
43 | deployBlocktime: number;
44 | deployHeight: number;
45 | historyCount: number;
46 | holdersCount: number;
47 | inscriptionId: string;
48 | inscriptionNumber: number;
49 | inscriptionNumberEnd: number;
50 | inscriptionNumberStart: number;
51 | limit: string;
52 | max: string;
53 | mintTimes: number;
54 | minted: string;
55 | ticker: string;
56 | totalMinted: string;
57 | txid: string;
58 | }
59 |
60 | export interface InscribeSummary {
61 | inscribeCount: number;
62 | ogPassConfirmations: number;
63 | ogPassCount: number;
64 | satsCount: number;
65 | unisatCount: number;
66 | }
67 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/send-btc.ts:
--------------------------------------------------------------------------------
1 | import { AddressType } from "@unisat/wallet-sdk";
2 | import { NetworkType } from "@unisat/wallet-sdk/lib/network";
3 | import { sendBTC } from "@unisat/wallet-sdk/lib/tx-helpers";
4 | import { LocalWallet } from "@unisat/wallet-sdk/lib/wallet";
5 | import { MempoolApi } from "./mempool-api";
6 | import { OpenApi } from "./open-api";
7 |
8 | const runSendBitcoin = async () => {
9 | const mempoolApi = new MempoolApi({
10 | baseUrl: "https://mempool.space/testnet/api",
11 | });
12 |
13 | const openapi = new OpenApi({
14 | baseUrl: "https://open-api-testnet.unisat.io",
15 | apiKey: "",
16 | });
17 |
18 | const wallet = new LocalWallet(
19 | "xxxxxxx",
20 | AddressType.P2PKH,
21 | NetworkType.TESTNET
22 | );
23 |
24 | const toAddress = "xxxxxx";
25 | const btcUtxos = await openapi.getAddressUtxoData(wallet.address);
26 | if (wallet.addressType === AddressType.P2PKH) {
27 | for (let i = 0; i < btcUtxos.utxo.length; i++) {
28 | btcUtxos.utxo[i].rawtx = await mempoolApi.getRawTx(btcUtxos.utxo[i].txid);
29 | }
30 | }
31 |
32 | const { psbt, toSignInputs } = await sendBTC({
33 | btcUtxos: btcUtxos.utxo.map((v) => ({
34 | txid: v.txid,
35 | vout: v.vout,
36 | satoshis: v.satoshi,
37 | scriptPk: v.scriptPk,
38 | pubkey: wallet.pubkey,
39 | addressType: wallet.addressType,
40 | inscriptions: v.inscriptions,
41 | atomicals: [],
42 | rawtx: v.rawtx, // only for p2pkh
43 | })),
44 | tos: [
45 | {
46 | address: toAddress,
47 | satoshis: 1000,
48 | },
49 | ],
50 | networkType: wallet.networkType,
51 | changeAddress: wallet.address,
52 | feeRate: 1,
53 | });
54 |
55 | let signWithLocalWallet = true;
56 | if (signWithLocalWallet) {
57 | // sign with local wallet
58 | await wallet.signPsbt(psbt, {
59 | autoFinalized: true,
60 | toSignInputs,
61 | });
62 | const rawtx = psbt.extractTransaction().toHex();
63 |
64 | const txid = await openapi.pushtx(rawtx);
65 | console.log("txid:", txid);
66 | } else {
67 | const psbtHex = psbt.toHex();
68 | console.log("psbtHex:", psbtHex);
69 | // copy this hex to a wallet that can sign it
70 | // unisat.signPsbt(psbtHex).then(result=>{
71 | // unisat.pushPsbt(result).then(txid=>{ console.log('push success',txid) })
72 | //});
73 | }
74 | };
75 | runSendBitcoin();
76 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/example/send-inscription.ts:
--------------------------------------------------------------------------------
1 | import { AddressType } from "@unisat/wallet-sdk";
2 | import { NetworkType } from "@unisat/wallet-sdk/lib/network";
3 | import { getAddressUtxoDust } from "@unisat/wallet-sdk/lib/transaction";
4 | import { sendInscription } from "@unisat/wallet-sdk/lib/tx-helpers";
5 | import { LocalWallet } from "@unisat/wallet-sdk/lib/wallet";
6 | import { MempoolApi } from "./mempool-api";
7 | import { OpenApi } from "./open-api";
8 |
9 | const runSendInscription = async () => {
10 | const openapi = new OpenApi({
11 | baseUrl: "https://open-api-testnet.unisat.io",
12 | apiKey: "xxxx",
13 | });
14 |
15 | const wallet = new LocalWallet(
16 | "xxxxx",
17 | AddressType.P2WPKH,
18 | NetworkType.TESTNET
19 | );
20 |
21 | const inscriptionId = "xxxx";
22 | const toAddress = "xxxxx";
23 | const inscriptionInfo = await openapi.getInscriptionInfo(inscriptionId);
24 | const btcUtxos = await openapi.getAddressUtxoData(wallet.address);
25 |
26 | if (wallet.addressType === AddressType.P2PKH) {
27 | const mempoolApi = new MempoolApi({
28 | baseUrl: "https://mempool.space/testnet/api",
29 | });
30 | for (let i = 0; i < btcUtxos.utxo.length; i++) {
31 | btcUtxos.utxo[i].rawtx = await mempoolApi.getRawTx(btcUtxos.utxo[i].txid);
32 | }
33 |
34 | inscriptionInfo.utxo.rawtx = await mempoolApi.getRawTx(
35 | inscriptionInfo.utxo.txid
36 | );
37 | }
38 |
39 | const { psbt, toSignInputs } = await sendInscription({
40 | assetUtxo: {
41 | txid: inscriptionInfo.utxo.txid,
42 | vout: inscriptionInfo.utxo.vout,
43 | satoshis: inscriptionInfo.utxo.satoshi,
44 | scriptPk: inscriptionInfo.utxo.scriptPk,
45 | pubkey: wallet.pubkey,
46 | addressType: wallet.addressType,
47 | inscriptions: inscriptionInfo.utxo.inscriptions,
48 | atomicals: [],
49 | rawtx: inscriptionInfo.utxo.rawtx, // only for p2pkh
50 | },
51 | btcUtxos: btcUtxos.utxo.map((v) => ({
52 | txid: v.txid,
53 | vout: v.vout,
54 | satoshis: v.satoshi,
55 | scriptPk: v.scriptPk,
56 | pubkey: wallet.pubkey,
57 | addressType: wallet.addressType,
58 | inscriptions: v.inscriptions,
59 | atomicals: [],
60 | rawtx: v.rawtx, // only for p2pkh
61 | })),
62 | toAddress,
63 | networkType: wallet.networkType,
64 | changeAddress: wallet.address,
65 | feeRate: 1,
66 | outputValue: getAddressUtxoDust(toAddress),
67 | });
68 |
69 | let signWithLocalWallet = true;
70 | if (signWithLocalWallet) {
71 | // sign with local wallet
72 | await wallet.signPsbt(psbt, {
73 | autoFinalized: true,
74 | toSignInputs,
75 | });
76 | const rawtx = psbt.extractTransaction().toHex();
77 |
78 | const txid = await openapi.pushtx(rawtx);
79 | console.log("txid:", txid);
80 | } else {
81 | const psbtHex = psbt.toHex();
82 | console.log("psbtHex:", psbtHex);
83 | // copy this hex to a wallet that can sign it
84 | // unisat.signPsbt(psbtHex).then(result=>{
85 | // unisat.pushPsbt(result).then(txid=>{ console.log('push success',txid) })
86 | //});
87 | }
88 | };
89 | runSendInscription();
90 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require("gulp");
2 | const babel = require("gulp-babel");
3 | const ts = require("gulp-typescript");
4 | const tsProject = ts.createProject("tsconfig.json");
5 | const SRC_PATH = "src";
6 |
7 | const BUILD_MODE = {
8 | ESM: {
9 | PATH: "es",
10 | },
11 | CJS: {
12 | PATH: "lib",
13 | },
14 | };
15 | const CURRENT_MODE = BUILD_MODE[process.env.BABEL_ENV.toUpperCase()];
16 | gulp.task("build-esm", () => {
17 | return gulp
18 | .src([`${SRC_PATH}/**/*.ts`])
19 | .pipe(babel())
20 | .pipe(gulp.dest(BUILD_MODE.ESM.PATH));
21 | });
22 |
23 | gulp.task("build-cjs", () => {
24 | return gulp
25 | .src([`${SRC_PATH}/**/*.ts`])
26 | .pipe(tsProject())
27 | .pipe(gulp.dest(BUILD_MODE.CJS.PATH));
28 | });
29 |
30 | if (CURRENT_MODE == BUILD_MODE.ESM) {
31 | gulp.task("build", gulp.series("build-esm"));
32 | } else if (CURRENT_MODE == BUILD_MODE.CJS) {
33 | gulp.task("build", gulp.series("build-cjs"));
34 | }
35 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@unisat/dev-example",
3 | "version": "1.1.1",
4 | "description": "UniSat Developer Example",
5 | "keywords": [
6 | "bitcoin",
7 | "keyring",
8 | "unisat"
9 | ],
10 | "homepage": "https://github.com/unisat-wallet/dev-example#readme",
11 | "bugs": {
12 | "url": "https://github.com/unisat-wallet/dev-example/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/unisat-wallet/dev-example.git"
17 | },
18 | "author": "cybersinsloth",
19 | "license": "ISC",
20 | "main": "lib/index.js",
21 | "module": "es/index.js",
22 | "types": "lib/index.d.ts",
23 | "files": [
24 | "/es",
25 | "/lib"
26 | ],
27 | "scripts": {
28 | "build": "npm run build:cjs && npm run build:esm",
29 | "build:cjs": "rimraf lib && cross-env BABEL_ENV=cjs gulp build",
30 | "build:esm": "rimraf es && cross-env BABEL_ENV=esm gulp build",
31 | "build:typed": "tsc --declaration --emitDeclarationOnly --noEmit false",
32 | "test": "mocha -r ts-node/register test/**/*.ts --timeout 300000",
33 | "prepublishOnly": "npm run build"
34 | },
35 | "dependencies": {
36 | "@unisat/wallet-sdk": "^1.4.0",
37 | "axios": "^1.6.4"
38 | },
39 | "devDependencies": {
40 | "@babel/core": "^7.16.7",
41 | "@babel/plugin-transform-runtime": "^7.16.5",
42 | "@babel/preset-env": "^7.16.5",
43 | "@babel/preset-typescript": "^7.16.5",
44 | "@types/chai": "^4.2.20",
45 | "@types/mocha": "^8.2.2",
46 | "@types/node": "^16.11.10",
47 | "@typescript-eslint/eslint-plugin": "^5.59.1",
48 | "@typescript-eslint/parser": "^5.59.1",
49 | "babel-loader": "^8.2.3",
50 | "chai": "^4.3.4",
51 | "clean-webpack-plugin": "^4.0.0",
52 | "cross-env": "^7.0.3",
53 | "glob": "^7.1.7",
54 | "gulp": "^4.0.2",
55 | "gulp-babel": "^8.0.0",
56 | "gulp-typescript": "*",
57 | "mocha": "^9.0.2",
58 | "ts-node": "^10.4.0",
59 | "typescript": "^4.5.3",
60 | "watch": "^1.0.2",
61 | "watchify": "^4.0.0",
62 | "webpack": "^5.65.0",
63 | "webpack-cli": "^4.9.1"
64 | },
65 | "engines": {
66 | "node": ">=14.0.0"
67 | },
68 | "publishConfig": {
69 | "access": "public",
70 | "registry": "https://registry.npmjs.org/"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/wallet-sdk-examples/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2015",
5 | "noImplicitAny": false,
6 | "outDir": "./lib",
7 | "esModuleInterop": true,
8 | "sourceMap": true,
9 | "declaration": true
10 | },
11 | "include": ["example/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------