Skip to content

Commit 529b4b7

Browse files
authored
Merge pull request OneKeyHQ#35 from huazhouwang/btc
Support BTC and BTC-like coins
2 parents 756c393 + eff7e69 commit 529b4b7

File tree

15 files changed

+1330
-6
lines changed

15 files changed

+1330
-6
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@
5555
"@starcoin/starcoin": "^1.6.2",
5656
"algo-msgpack-with-bigint": "^2.1.1",
5757
"asmcrypto.js": "^2.3.2",
58+
"bchaddrjs": "^0.5.2",
5859
"bech32": "^1.1.4",
5960
"bignumber.js": "^9.0.1",
6061
"bip39": "^3.0.4",
62+
"bitcoinjs-lib": "^6.0.1",
6163
"bs58": "^4.0.1",
6264
"cosmjs-types": "^0.4.0",
6365
"cross-fetch": "^3.1.4",
@@ -72,6 +74,7 @@
7274
"devDependencies": {
7375
"@commitlint/cli": "^13.1.0",
7476
"@commitlint/config-conventional": "^13.1.0",
77+
"@types/bchaddrjs": "^0.4.0",
7578
"@types/bs58": "^4.0.1",
7679
"@types/elliptic": "^6.4.13",
7780
"@types/jest": "^27.0.2",

src/provider/chains/bch/index.ts

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

src/provider/chains/bch/provider.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import bchaddr from 'bchaddrjs';
2+
3+
import {
4+
AddressValidation,
5+
SignedTx,
6+
UnsignedTx,
7+
} from '../../../types/provider';
8+
import { Signer, Verifier } from '../../../types/secret';
9+
import { Provider as BTCProvider } from '../btc';
10+
import { SupportedEncodings } from '../btc/provider';
11+
12+
class Provider extends BTCProvider {
13+
async pubkeyToAddress(
14+
verifier: Verifier,
15+
encoding?: string,
16+
): Promise<string> {
17+
const legacyAddress = await super.pubkeyToAddress(
18+
verifier,
19+
SupportedEncodings.p2pkh,
20+
);
21+
return bchaddr.toCashAddress(legacyAddress);
22+
}
23+
24+
async verifyAddress(address: string): Promise<AddressValidation> {
25+
const isValid = bchaddr.isCashAddress(address);
26+
return isValid
27+
? {
28+
displayAddress: address,
29+
normalizedAddress: address,
30+
encoding: SupportedEncodings.p2pkh,
31+
isValid: true,
32+
}
33+
: { isValid: false };
34+
}
35+
36+
processUnsignedTxBeforeSign(unsignedTx: UnsignedTx): UnsignedTx {
37+
return Object.assign({}, unsignedTx, {
38+
inputs: unsignedTx.inputs.map((i) => ({
39+
...i,
40+
address: bchaddr.toLegacyAddress(i.address),
41+
})),
42+
outputs: unsignedTx.outputs.map((o) => ({
43+
...o,
44+
address: bchaddr.toLegacyAddress(o.address),
45+
})),
46+
});
47+
}
48+
49+
async signTransaction(
50+
unsignedTx: UnsignedTx,
51+
signers: { [p: string]: Signer },
52+
): Promise<SignedTx> {
53+
unsignedTx = this.processUnsignedTxBeforeSign(unsignedTx);
54+
return super.signTransaction(unsignedTx, signers);
55+
}
56+
}
57+
58+
export { Provider };

src/provider/chains/btc/blockbook.ts

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)