Skip to content

Commit 5724275

Browse files
committed
feat: support btc
1 parent 7e722a8 commit 5724275

File tree

7 files changed

+595
-1
lines changed

7 files changed

+595
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"bech32": "^1.1.4",
5959
"bignumber.js": "^9.0.1",
6060
"bip39": "^3.0.4",
61+
"bitcoinjs-lib": "^6.0.1",
6162
"bs58": "^4.0.1",
6263
"cosmjs-types": "^0.4.0",
6364
"cross-fetch": "^3.1.4",
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import BigNumber from 'bignumber.js';
2+
3+
import { ResponseError } from '../../../basic/request/exceptions';
4+
import { RestfulRequest } from '../../../basic/request/restful';
5+
import {
6+
AddressInfo,
7+
ClientInfo,
8+
FeePricePerUnit,
9+
TransactionStatus,
10+
} from '../../../types/provider';
11+
import { SimpleClient } from '../../abc';
12+
13+
const MIN_SAT_PER_BYTE = 1;
14+
const BTC_PER_KBYTES__TO__SAT_PER_BYTE = Math.pow(10, 5);
15+
16+
class BlockBook extends SimpleClient {
17+
readonly restful: RestfulRequest;
18+
19+
constructor(url: string) {
20+
super();
21+
this.restful = new RestfulRequest(url);
22+
}
23+
24+
async getInfo(): Promise<ClientInfo> {
25+
const resp: any = await this.restful.get('/api/v2').then((i) => i.json());
26+
const bestBlockNumber = Number(resp.backend.blocks);
27+
28+
return {
29+
bestBlockNumber,
30+
isReady: Number.isFinite(bestBlockNumber) && bestBlockNumber > 0,
31+
};
32+
}
33+
34+
async getAddress(address: string): Promise<AddressInfo> {
35+
const resp: any = await this.restful
36+
.get(`/api/v2/address/${address}`, {
37+
details: 'basic',
38+
})
39+
.then((i) => i.json());
40+
const unconfirmedBalance = Number(resp.unconfirmedBalance || 0);
41+
42+
return {
43+
balance: new BigNumber(Number(resp.balance || 0) + unconfirmedBalance),
44+
existing: Number(resp.txs || 0) > 0,
45+
};
46+
}
47+
48+
async estimateFee(waitingBlock: number): Promise<number> {
49+
const resp = await this.restful
50+
.get(`/api/v2/estimatefee/${waitingBlock}`)
51+
.then((i) => i.json());
52+
53+
return Math.max(
54+
MIN_SAT_PER_BYTE,
55+
(Number(resp.result || 0) * BTC_PER_KBYTES__TO__SAT_PER_BYTE) as never,
56+
);
57+
}
58+
59+
async getFeePricePerUnit(): Promise<FeePricePerUnit> {
60+
const [normalResp, fastResp, slowResp] = await Promise.allSettled([
61+
this.estimateFee(5),
62+
this.estimateFee(1),
63+
this.estimateFee(20),
64+
]);
65+
66+
const isFulfilledFee = (resp?: PromiseSettledResult<number>) =>
67+
resp &&
68+
resp.status === 'fulfilled' &&
69+
Number.isFinite(resp.value) &&
70+
resp.value > 0;
71+
72+
const normal: number = isFulfilledFee(normalResp)
73+
? (normalResp as PromiseFulfilledResult<number>).value
74+
: MIN_SAT_PER_BYTE;
75+
76+
const fast = isFulfilledFee(fastResp)
77+
? (fastResp as PromiseFulfilledResult<number>).value
78+
: Math.max(MIN_SAT_PER_BYTE, normal * 1.6);
79+
80+
const slow = isFulfilledFee(slowResp)
81+
? (slowResp as PromiseFulfilledResult<number>).value
82+
: Math.max(MIN_SAT_PER_BYTE, normal * 0.6);
83+
84+
return {
85+
normal: { price: new BigNumber(normal), waitingBlock: 5 },
86+
others: [
87+
{ price: new BigNumber(slow), waitingBlock: 20 },
88+
{ price: new BigNumber(fast), waitingBlock: 1 },
89+
],
90+
};
91+
}
92+
93+
async getTransactionStatus(txid: string): Promise<TransactionStatus> {
94+
try {
95+
const resp = await this.restful
96+
.get(`/api/v2/tx/${txid}`)
97+
.then((i) => i.json());
98+
const confirmations = Number(resp.confirmations);
99+
return Number.isFinite(confirmations) && confirmations > 0
100+
? TransactionStatus.CONFIRM_AND_SUCCESS
101+
: TransactionStatus.PENDING;
102+
} catch (e) {
103+
if (e instanceof ResponseError && e.response) {
104+
const error = await e.response.json();
105+
if (error.error?.includes('not found')) {
106+
return TransactionStatus.NOT_FOUND;
107+
}
108+
}
109+
throw e;
110+
}
111+
}
112+
113+
async getRawTransaction(txid: string): Promise<string> {
114+
const resp = await this.restful
115+
.get(`/api/v2/tx/${txid}`)
116+
.then((i) => i.json());
117+
118+
return resp.hex;
119+
}
120+
121+
async broadcastTransaction(rawTx: string): Promise<boolean> {
122+
try {
123+
const resp = await this.restful
124+
.get(`/api/v2/sendtx/${rawTx}`)
125+
.then((i) => i.json());
126+
127+
const txid = resp.result;
128+
return typeof txid === 'string' && txid.length === 64;
129+
} catch (e) {
130+
if (e instanceof ResponseError && e.response) {
131+
const error = await e.response.json();
132+
133+
if (
134+
typeof error.error === 'string' &&
135+
error.error.includes('Transaction already in block chain')
136+
) {
137+
throw new Error('Transaction already in block');
138+
}
139+
}
140+
141+
throw e;
142+
}
143+
}
144+
}
145+
146+
export { BlockBook };

src/provider/chains/btc/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Provider } from './provider';
2+
export { BlockBook } from './blockbook';

0 commit comments

Comments
 (0)