signet.js 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/package.json +64 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.d.ts +10 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.js +2 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.d.ts +15 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.js +69 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.d.ts +1 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.js +1 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.d.ts +69 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.js +1 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/index.d.ts +5 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/index.js +5 -0
- package/src/chains/Bitcoin/Bitcoin.d.ts +66 -0
- package/src/chains/Bitcoin/Bitcoin.js +197 -0
- package/src/chains/Bitcoin/types.d.ts +42 -0
- package/src/chains/Bitcoin/types.js +1 -0
- package/src/chains/Bitcoin/utils.d.ts +2 -0
- package/src/chains/Bitcoin/utils.js +13 -0
- package/src/chains/Chain.d.ts +89 -0
- package/src/chains/Chain.js +9 -0
- package/src/chains/ChainSignatureContract.d.ts +62 -0
- package/src/chains/ChainSignatureContract.js +7 -0
- package/src/chains/Cosmos/Cosmos.d.ts +49 -0
- package/src/chains/Cosmos/Cosmos.js +156 -0
- package/src/chains/Cosmos/types.d.ts +30 -0
- package/src/chains/Cosmos/types.js +1 -0
- package/src/chains/Cosmos/utils.d.ts +2 -0
- package/src/chains/Cosmos/utils.js +27 -0
- package/src/chains/EVM/EVM.d.ts +40 -0
- package/src/chains/EVM/EVM.js +108 -0
- package/src/chains/EVM/types.d.ts +5 -0
- package/src/chains/EVM/types.js +1 -0
- package/src/chains/EVM/utils.d.ts +7 -0
- package/src/chains/EVM/utils.js +14 -0
- package/src/chains/index.d.ts +12 -0
- package/src/chains/index.js +12 -0
- package/src/chains/types.d.ts +31 -0
- package/src/chains/types.js +1 -0
- package/src/chains/utils.d.ts +12 -0
- package/src/chains/utils.js +27 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/utils/chains/index.d.ts +1 -0
- package/src/utils/chains/index.js +1 -0
- package/src/utils/chains/near/account.d.ts +13 -0
- package/src/utils/chains/near/account.js +22 -0
- package/src/utils/chains/near/constants.d.ts +3 -0
- package/src/utils/chains/near/constants.js +3 -0
- package/src/utils/chains/near/contract.d.ts +39 -0
- package/src/utils/chains/near/contract.js +101 -0
- package/src/utils/chains/near/index.d.ts +3 -0
- package/src/utils/chains/near/index.js +3 -0
- package/src/utils/chains/near/relayer/index.d.ts +1 -0
- package/src/utils/chains/near/relayer/index.js +1 -0
- package/src/utils/chains/near/relayer/relayer.d.ts +8 -0
- package/src/utils/chains/near/relayer/relayer.js +33 -0
- package/src/utils/chains/near/relayer/types.d.ts +22 -0
- package/src/utils/chains/near/relayer/types.js +1 -0
- package/src/utils/chains/near/signAndSend/index.d.ts +1 -0
- package/src/utils/chains/near/signAndSend/index.js +1 -0
- package/src/utils/chains/near/signAndSend/keypair.d.ts +6 -0
- package/src/utils/chains/near/signAndSend/keypair.js +126 -0
- package/src/utils/chains/near/transactionBuilder.d.ts +26 -0
- package/src/utils/chains/near/transactionBuilder.js +72 -0
- package/src/utils/chains/near/types.d.ts +47 -0
- package/src/utils/chains/near/types.js +1 -0
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
- package/vocs.config.d.ts +3 -0
- package/vocs.config.js +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Signet.js
|
|
2
|
+
|
|
3
|
+
A TypeScript library for handling multi-chain transactions and signatures using MPC (Multi-Party Computation).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This library provides a unified interface for interacting with different blockchain networks through a common set of methods. It uses MPC for secure key management and transaction signing.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multi-Chain Support**: Built-in support for EVM chains, Bitcoin, and Cosmos networks
|
|
12
|
+
- **Unified Interface**: Common API across all supported chains
|
|
13
|
+
- **MPC Integration**: Secure key management and transaction signing
|
|
14
|
+
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
|
15
|
+
- **Modular Design**: Easy to extend with new chain implementations
|
|
16
|
+
- **Secure**: No private keys stored or transmitted
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install signet.js
|
|
22
|
+
# or
|
|
23
|
+
yarn add signet.js
|
|
24
|
+
# or
|
|
25
|
+
pnpm add signet.js
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Example
|
|
29
|
+
|
|
30
|
+
```ts twoslash
|
|
31
|
+
import { EVM, near } from 'signet.js'
|
|
32
|
+
|
|
33
|
+
// Initialize MPC contract
|
|
34
|
+
const contract = new near.contract.ChainSignaturesContract({
|
|
35
|
+
networkId: 'testnet',
|
|
36
|
+
contractId: 'mpc.testnet',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Initialize chain
|
|
40
|
+
const chain = new EVM({
|
|
41
|
+
rpcUrl: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
|
|
42
|
+
contract,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Create and sign transaction
|
|
46
|
+
const { transaction, mpcPayloads } = await chain.getMPCPayloadAndTransaction({
|
|
47
|
+
to: '0x1234...',
|
|
48
|
+
value: '1000000000000000000', // 1 ETH
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const signature = await contract.sign({
|
|
52
|
+
payload: mpcPayloads[0].payload,
|
|
53
|
+
path: 'any_string',
|
|
54
|
+
key_version: 0,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const signedTx = chain.addSignature({
|
|
58
|
+
transaction,
|
|
59
|
+
mpcSignatures: [signature],
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const txHash = await chain.broadcastTx(signedTx)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
For detailed documentation, including:
|
|
68
|
+
|
|
69
|
+
- Getting started guide
|
|
70
|
+
- Chain-specific implementations
|
|
71
|
+
- MPC system overview
|
|
72
|
+
- Implementation guides
|
|
73
|
+
- API reference
|
|
74
|
+
|
|
75
|
+
Visit our [documentation site](https://near.github.io/signet.js).
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signet.js",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"typings": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc --project tsconfig.json",
|
|
10
|
+
"pre:deploy": "npm run build && cp package.json README.md dist/",
|
|
11
|
+
"publish-npm": "npm run pre:deploy && cd dist && npm publish",
|
|
12
|
+
"publish-npm:beta": "npm run pre:deploy && cd dist && npm publish --tag beta",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"docs:dev": "vocs dev",
|
|
15
|
+
"docs:build": "vocs build",
|
|
16
|
+
"docs:preview": "vocs preview"
|
|
17
|
+
},
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@babel/core": "^7.25.2",
|
|
22
|
+
"@babel/preset-env": "^7.25.3",
|
|
23
|
+
"@babel/preset-typescript": "^7.24.7",
|
|
24
|
+
"@types/bn.js": "^5.1.5",
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
26
|
+
"babel-jest": "^29.7.0",
|
|
27
|
+
"dotenv": "^16.4.5",
|
|
28
|
+
"eslint": "^8.57.0",
|
|
29
|
+
"eslint-config-prettier": "^9.1.0",
|
|
30
|
+
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
31
|
+
"eslint-plugin-import": "^2.29.1",
|
|
32
|
+
"eslint-plugin-n": "^16.6.2",
|
|
33
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
34
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"prettier": "^3.2.5",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"typescript": "^5.4.3",
|
|
39
|
+
"vocs": "1.0.0-alpha.62"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@cosmjs/amino": "^0.32.4",
|
|
43
|
+
"@cosmjs/crypto": "^0.32.4",
|
|
44
|
+
"@cosmjs/encoding": "^0.32.4",
|
|
45
|
+
"@cosmjs/math": "^0.32.4",
|
|
46
|
+
"@cosmjs/proto-signing": "^0.32.4",
|
|
47
|
+
"@cosmjs/stargate": "^0.32.4",
|
|
48
|
+
"@near-js/accounts": "^1.3.0",
|
|
49
|
+
"@near-js/crypto": "^1.4.0",
|
|
50
|
+
"@near-js/keystores": "^0.2.0",
|
|
51
|
+
"@near-js/transactions": "^1.3.1",
|
|
52
|
+
"@near-wallet-selector/core": "^8.9.5",
|
|
53
|
+
"bech32": "^2.0.0",
|
|
54
|
+
"bitcoinjs-lib": "^6.1.5",
|
|
55
|
+
"bn.js": "^5.2.1",
|
|
56
|
+
"bs58": "^6.0.0",
|
|
57
|
+
"chain-registry": "^1.69.72",
|
|
58
|
+
"coinselect": "^3.1.13",
|
|
59
|
+
"cosmjs-types": "^0.9.0",
|
|
60
|
+
"ethers": "^6.11.1",
|
|
61
|
+
"near-api-js": "^3.0.4"
|
|
62
|
+
},
|
|
63
|
+
"packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf"
|
|
64
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BTCTransaction, BTCInput, BTCOutput } from '@chains';
|
|
2
|
+
export declare abstract class BTCRpcAdapter {
|
|
3
|
+
abstract selectUTXOs(from: string, targets: BTCOutput[]): Promise<{
|
|
4
|
+
inputs: BTCInput[];
|
|
5
|
+
outputs: BTCOutput[];
|
|
6
|
+
}>;
|
|
7
|
+
abstract broadcastTransaction(transactionHex: string): Promise<string>;
|
|
8
|
+
abstract getBalance(address: string): Promise<number>;
|
|
9
|
+
abstract getTransaction(txid: string): Promise<BTCTransaction>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BTCRpcAdapter } from '@chains';
|
|
2
|
+
import type { BTCTransaction, BTCInput, BTCOutput } from '@chains';
|
|
3
|
+
export declare class Mempool extends BTCRpcAdapter {
|
|
4
|
+
private readonly providerUrl;
|
|
5
|
+
constructor(providerUrl: string);
|
|
6
|
+
private fetchFeeRate;
|
|
7
|
+
private fetchUTXOs;
|
|
8
|
+
selectUTXOs(from: string, targets: BTCOutput[], confirmationTarget?: number): Promise<{
|
|
9
|
+
inputs: BTCInput[];
|
|
10
|
+
outputs: BTCOutput[];
|
|
11
|
+
}>;
|
|
12
|
+
broadcastTransaction(transactionHex: string): Promise<string>;
|
|
13
|
+
getBalance(address: string): Promise<number>;
|
|
14
|
+
getTransaction(txid: string): Promise<BTCTransaction>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// There is no types for coinselect
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-expect-error
|
|
4
|
+
import coinselect from 'coinselect';
|
|
5
|
+
import { BTCRpcAdapter } from '@chains';
|
|
6
|
+
export class Mempool extends BTCRpcAdapter {
|
|
7
|
+
constructor(providerUrl) {
|
|
8
|
+
super();
|
|
9
|
+
this.providerUrl = providerUrl;
|
|
10
|
+
}
|
|
11
|
+
async fetchFeeRate(confirmationTarget = 6) {
|
|
12
|
+
const response = await fetch(`${this.providerUrl}/v1/fees/recommended`);
|
|
13
|
+
const data = (await response.json());
|
|
14
|
+
if (confirmationTarget <= 1) {
|
|
15
|
+
return data.fastestFee;
|
|
16
|
+
}
|
|
17
|
+
else if (confirmationTarget <= 3) {
|
|
18
|
+
return data.halfHourFee;
|
|
19
|
+
}
|
|
20
|
+
else if (confirmationTarget <= 6) {
|
|
21
|
+
return data.hourFee;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return data.economyFee;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async fetchUTXOs(address) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${this.providerUrl}/address/${address}/utxo`);
|
|
30
|
+
return (await response.json());
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error('Failed to fetch UTXOs:', error);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async selectUTXOs(from, targets, confirmationTarget = 6) {
|
|
38
|
+
const utxos = await this.fetchUTXOs(from);
|
|
39
|
+
const feeRate = await this.fetchFeeRate(confirmationTarget);
|
|
40
|
+
// Add a small amount to the fee rate to ensure the transaction is confirmed
|
|
41
|
+
const ret = coinselect(utxos, targets, Math.ceil(feeRate + 1));
|
|
42
|
+
if (!ret.inputs || !ret.outputs) {
|
|
43
|
+
throw new Error('Invalid transaction: coinselect failed to find a suitable set of inputs and outputs. This could be due to insufficient funds, or no inputs being available that meet the criteria.');
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
inputs: ret.inputs,
|
|
47
|
+
outputs: ret.outputs,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async broadcastTransaction(transactionHex) {
|
|
51
|
+
const response = await fetch(`${this.providerUrl}/tx`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: transactionHex,
|
|
54
|
+
});
|
|
55
|
+
if (response.ok) {
|
|
56
|
+
return await response.text();
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Failed to broadcast transaction: ${await response.text()}`);
|
|
59
|
+
}
|
|
60
|
+
async getBalance(address) {
|
|
61
|
+
const response = await fetch(`${this.providerUrl}/address/${address}`);
|
|
62
|
+
const data = (await response.json());
|
|
63
|
+
return data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum;
|
|
64
|
+
}
|
|
65
|
+
async getTransaction(txid) {
|
|
66
|
+
const response = await fetch(`${this.providerUrl}/tx/${txid}`);
|
|
67
|
+
return (await response.json());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Mempool } from './Mempool';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Mempool } from './Mempool';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface BTCFeeRecommendation {
|
|
2
|
+
fastestFee: number;
|
|
3
|
+
halfHourFee: number;
|
|
4
|
+
hourFee: number;
|
|
5
|
+
economyFee: number;
|
|
6
|
+
minimumFee: number;
|
|
7
|
+
}
|
|
8
|
+
interface BTCAddressStats {
|
|
9
|
+
funded_txo_count: number;
|
|
10
|
+
funded_txo_sum: number;
|
|
11
|
+
spent_txo_count: number;
|
|
12
|
+
spent_txo_sum: number;
|
|
13
|
+
tx_count: number;
|
|
14
|
+
}
|
|
15
|
+
export interface BTCAddressInfo {
|
|
16
|
+
address: string;
|
|
17
|
+
chain_stats: BTCAddressStats;
|
|
18
|
+
mempool_stats: BTCAddressStats;
|
|
19
|
+
}
|
|
20
|
+
export interface Transaction {
|
|
21
|
+
txid: string;
|
|
22
|
+
version: number;
|
|
23
|
+
locktime: number;
|
|
24
|
+
vin: Array<{
|
|
25
|
+
txid: string;
|
|
26
|
+
vout: number;
|
|
27
|
+
prevout: {
|
|
28
|
+
scriptpubkey: string;
|
|
29
|
+
scriptpubkey_asm: string;
|
|
30
|
+
scriptpubkey_type: string;
|
|
31
|
+
scriptpubkey_address: string;
|
|
32
|
+
value: number;
|
|
33
|
+
};
|
|
34
|
+
scriptsig: string;
|
|
35
|
+
scriptsig_asm: string;
|
|
36
|
+
witness: string[];
|
|
37
|
+
is_coinbase: boolean;
|
|
38
|
+
sequence: number;
|
|
39
|
+
}>;
|
|
40
|
+
vout: Array<{
|
|
41
|
+
scriptpubkey: string;
|
|
42
|
+
scriptpubkey_asm: string;
|
|
43
|
+
scriptpubkey_type: string;
|
|
44
|
+
scriptpubkey_address: string;
|
|
45
|
+
value: number;
|
|
46
|
+
}>;
|
|
47
|
+
size: number;
|
|
48
|
+
weight: number;
|
|
49
|
+
sigops?: number;
|
|
50
|
+
fee: number;
|
|
51
|
+
status: {
|
|
52
|
+
confirmed: boolean;
|
|
53
|
+
block_height: number;
|
|
54
|
+
block_hash: string;
|
|
55
|
+
block_time: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export interface UTXO {
|
|
59
|
+
txid: string;
|
|
60
|
+
vout: number;
|
|
61
|
+
status: {
|
|
62
|
+
confirmed: boolean;
|
|
63
|
+
block_height: number;
|
|
64
|
+
block_hash: string;
|
|
65
|
+
block_time: number;
|
|
66
|
+
};
|
|
67
|
+
value: number;
|
|
68
|
+
}
|
|
69
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
2
|
+
import type { MPCPayloads, RSVSignature, KeyDerivationPath, ChainSignatureContract, BTCRpcAdapter, BTCNetworkIds, BTCTransactionRequest, BTCUnsignedTransaction } from '@chains';
|
|
3
|
+
import { Chain } from '@chains';
|
|
4
|
+
/**
|
|
5
|
+
* Implementation of the Chain interface for Bitcoin network.
|
|
6
|
+
* Handles interactions with both Bitcoin mainnet and testnet, supporting P2WPKH transactions.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Bitcoin extends Chain<BTCTransactionRequest, BTCUnsignedTransaction> {
|
|
9
|
+
private static readonly SATOSHIS_PER_BTC;
|
|
10
|
+
private readonly network;
|
|
11
|
+
private readonly btcRpcAdapter;
|
|
12
|
+
private readonly contract;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Bitcoin chain instance
|
|
15
|
+
* @param params - Configuration parameters
|
|
16
|
+
* @param params.network - Network identifier (mainnet/testnet)
|
|
17
|
+
* @param params.contract - Instance of the chain signature contract for MPC operations
|
|
18
|
+
* @param params.btcRpcAdapter - Bitcoin RPC adapter for network interactions
|
|
19
|
+
*/
|
|
20
|
+
constructor({ network, contract, btcRpcAdapter, }: {
|
|
21
|
+
network: BTCNetworkIds;
|
|
22
|
+
contract: ChainSignatureContract;
|
|
23
|
+
btcRpcAdapter: BTCRpcAdapter;
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Converts satoshis to BTC
|
|
27
|
+
* @param satoshis - Amount in satoshis
|
|
28
|
+
* @returns Amount in BTC
|
|
29
|
+
*/
|
|
30
|
+
static toBTC(satoshis: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* Converts BTC to satoshis
|
|
33
|
+
* @param btc - Amount in BTC
|
|
34
|
+
* @returns Amount in satoshis (rounded)
|
|
35
|
+
*/
|
|
36
|
+
static toSatoshi(btc: number): number;
|
|
37
|
+
private fetchTransaction;
|
|
38
|
+
private static parseRSVSignature;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Partially Signed Bitcoin Transaction (PSBT)
|
|
41
|
+
* @param params - Parameters for creating the PSBT
|
|
42
|
+
* @param params.transactionRequest - Transaction request containing inputs and outputs
|
|
43
|
+
* @returns Created PSBT instance
|
|
44
|
+
*/
|
|
45
|
+
createPSBT({ transactionRequest, }: {
|
|
46
|
+
transactionRequest: BTCTransactionRequest;
|
|
47
|
+
}): Promise<bitcoin.Psbt>;
|
|
48
|
+
getBalance(address: string): Promise<string>;
|
|
49
|
+
deriveAddressAndPublicKey(predecessor: string, path: KeyDerivationPath): Promise<{
|
|
50
|
+
address: string;
|
|
51
|
+
publicKey: string;
|
|
52
|
+
}>;
|
|
53
|
+
setTransaction(transaction: BTCUnsignedTransaction, storageKey: string): void;
|
|
54
|
+
getTransaction(storageKey: string, options?: {
|
|
55
|
+
remove?: boolean;
|
|
56
|
+
}): BTCUnsignedTransaction | undefined;
|
|
57
|
+
getMPCPayloadAndTransaction(transactionRequest: BTCTransactionRequest): Promise<{
|
|
58
|
+
transaction: BTCUnsignedTransaction;
|
|
59
|
+
mpcPayloads: MPCPayloads;
|
|
60
|
+
}>;
|
|
61
|
+
addSignature({ transaction: { psbt, publicKey }, mpcSignatures, }: {
|
|
62
|
+
transaction: BTCUnsignedTransaction;
|
|
63
|
+
mpcSignatures: RSVSignature[];
|
|
64
|
+
}): string;
|
|
65
|
+
broadcastTx(txSerialized: string): Promise<string>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
2
|
+
import { Chain, utils } from '@chains';
|
|
3
|
+
import { parseBTCNetwork } from '@chains/Bitcoin/utils';
|
|
4
|
+
/**
|
|
5
|
+
* Implementation of the Chain interface for Bitcoin network.
|
|
6
|
+
* Handles interactions with both Bitcoin mainnet and testnet, supporting P2WPKH transactions.
|
|
7
|
+
*/
|
|
8
|
+
export class Bitcoin extends Chain {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new Bitcoin chain instance
|
|
11
|
+
* @param params - Configuration parameters
|
|
12
|
+
* @param params.network - Network identifier (mainnet/testnet)
|
|
13
|
+
* @param params.contract - Instance of the chain signature contract for MPC operations
|
|
14
|
+
* @param params.btcRpcAdapter - Bitcoin RPC adapter for network interactions
|
|
15
|
+
*/
|
|
16
|
+
constructor({ network, contract, btcRpcAdapter, }) {
|
|
17
|
+
super();
|
|
18
|
+
this.network = network;
|
|
19
|
+
this.btcRpcAdapter = btcRpcAdapter;
|
|
20
|
+
this.contract = contract;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts satoshis to BTC
|
|
24
|
+
* @param satoshis - Amount in satoshis
|
|
25
|
+
* @returns Amount in BTC
|
|
26
|
+
*/
|
|
27
|
+
static toBTC(satoshis) {
|
|
28
|
+
return satoshis / Bitcoin.SATOSHIS_PER_BTC;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Converts BTC to satoshis
|
|
32
|
+
* @param btc - Amount in BTC
|
|
33
|
+
* @returns Amount in satoshis (rounded)
|
|
34
|
+
*/
|
|
35
|
+
static toSatoshi(btc) {
|
|
36
|
+
return Math.round(btc * Bitcoin.SATOSHIS_PER_BTC);
|
|
37
|
+
}
|
|
38
|
+
async fetchTransaction(transactionId) {
|
|
39
|
+
const data = await this.btcRpcAdapter.getTransaction(transactionId);
|
|
40
|
+
const tx = new bitcoin.Transaction();
|
|
41
|
+
data.vout.forEach((vout) => {
|
|
42
|
+
const scriptPubKey = Buffer.from(vout.scriptpubkey, 'hex');
|
|
43
|
+
tx.addOutput(scriptPubKey, Number(vout.value));
|
|
44
|
+
});
|
|
45
|
+
return tx;
|
|
46
|
+
}
|
|
47
|
+
static parseRSVSignature(signature) {
|
|
48
|
+
const r = signature.r.padStart(64, '0');
|
|
49
|
+
const s = signature.s.padStart(64, '0');
|
|
50
|
+
const rawSignature = Buffer.from(r + s, 'hex');
|
|
51
|
+
if (rawSignature.length !== 64) {
|
|
52
|
+
throw new Error('Invalid signature length.');
|
|
53
|
+
}
|
|
54
|
+
return rawSignature;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a Partially Signed Bitcoin Transaction (PSBT)
|
|
58
|
+
* @param params - Parameters for creating the PSBT
|
|
59
|
+
* @param params.transactionRequest - Transaction request containing inputs and outputs
|
|
60
|
+
* @returns Created PSBT instance
|
|
61
|
+
*/
|
|
62
|
+
async createPSBT({ transactionRequest, }) {
|
|
63
|
+
const { inputs, outputs } = transactionRequest.inputs && transactionRequest.outputs
|
|
64
|
+
? transactionRequest
|
|
65
|
+
: await this.btcRpcAdapter.selectUTXOs(transactionRequest.from, [
|
|
66
|
+
{
|
|
67
|
+
address: transactionRequest.to,
|
|
68
|
+
value: parseFloat(transactionRequest.value),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
const psbt = new bitcoin.Psbt({ network: parseBTCNetwork(this.network) });
|
|
72
|
+
await Promise.all(inputs.map(async (input) => {
|
|
73
|
+
if (!input.scriptPubKey) {
|
|
74
|
+
const transaction = await this.fetchTransaction(input.txid);
|
|
75
|
+
const prevOut = transaction.outs[input.vout];
|
|
76
|
+
input.scriptPubKey = prevOut.script;
|
|
77
|
+
}
|
|
78
|
+
// Prepare the input as P2WPKH
|
|
79
|
+
psbt.addInput({
|
|
80
|
+
hash: input.txid,
|
|
81
|
+
index: input.vout,
|
|
82
|
+
witnessUtxo: {
|
|
83
|
+
script: input.scriptPubKey,
|
|
84
|
+
value: input.value,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}));
|
|
88
|
+
outputs.forEach((out) => {
|
|
89
|
+
if (out.address) {
|
|
90
|
+
psbt.addOutput({
|
|
91
|
+
address: out.address,
|
|
92
|
+
value: out.value,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (out.script) {
|
|
96
|
+
psbt.addOutput({
|
|
97
|
+
script: out.script,
|
|
98
|
+
value: out.value,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return psbt;
|
|
103
|
+
}
|
|
104
|
+
async getBalance(address) {
|
|
105
|
+
const balance = await this.btcRpcAdapter.getBalance(address);
|
|
106
|
+
return Bitcoin.toBTC(balance).toString();
|
|
107
|
+
}
|
|
108
|
+
async deriveAddressAndPublicKey(predecessor, path) {
|
|
109
|
+
const uncompressedPubKey = await this.contract.getDerivedPublicKey({
|
|
110
|
+
path,
|
|
111
|
+
predecessor,
|
|
112
|
+
});
|
|
113
|
+
if (!uncompressedPubKey) {
|
|
114
|
+
throw new Error('Failed to get derived public key');
|
|
115
|
+
}
|
|
116
|
+
const derivedKey = utils.compressPubKey(uncompressedPubKey);
|
|
117
|
+
const publicKeyBuffer = Buffer.from(derivedKey, 'hex');
|
|
118
|
+
const network = parseBTCNetwork(this.network);
|
|
119
|
+
const payment = bitcoin.payments.p2wpkh({
|
|
120
|
+
pubkey: publicKeyBuffer,
|
|
121
|
+
network,
|
|
122
|
+
});
|
|
123
|
+
const { address } = payment;
|
|
124
|
+
if (!address) {
|
|
125
|
+
throw new Error('Failed to generate Bitcoin address');
|
|
126
|
+
}
|
|
127
|
+
return { address, publicKey: derivedKey };
|
|
128
|
+
}
|
|
129
|
+
setTransaction(transaction, storageKey) {
|
|
130
|
+
window.localStorage.setItem(storageKey, JSON.stringify({
|
|
131
|
+
psbt: transaction.psbt.toHex(),
|
|
132
|
+
publicKey: transaction.publicKey,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
getTransaction(storageKey, options) {
|
|
136
|
+
const txSerialized = window.localStorage.getItem(storageKey);
|
|
137
|
+
if (!txSerialized)
|
|
138
|
+
return undefined;
|
|
139
|
+
if (options?.remove) {
|
|
140
|
+
window.localStorage.removeItem(storageKey);
|
|
141
|
+
}
|
|
142
|
+
const transactionJSON = JSON.parse(txSerialized);
|
|
143
|
+
return {
|
|
144
|
+
psbt: bitcoin.Psbt.fromHex(transactionJSON.psbt),
|
|
145
|
+
publicKey: transactionJSON.publicKey,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async getMPCPayloadAndTransaction(transactionRequest) {
|
|
149
|
+
const publicKeyBuffer = Buffer.from(transactionRequest.publicKey, 'hex');
|
|
150
|
+
const psbt = await this.createPSBT({
|
|
151
|
+
transactionRequest,
|
|
152
|
+
});
|
|
153
|
+
// We can't double sign a PSBT, therefore we serialize the payload before to return it
|
|
154
|
+
const psbtHex = psbt.toHex();
|
|
155
|
+
const mpcPayloads = [];
|
|
156
|
+
const mockKeyPair = (index) => ({
|
|
157
|
+
publicKey: publicKeyBuffer,
|
|
158
|
+
sign: (hash) => {
|
|
159
|
+
mpcPayloads.push({
|
|
160
|
+
index,
|
|
161
|
+
payload: Array.from(hash),
|
|
162
|
+
});
|
|
163
|
+
// Return dummy signature to satisfy the interface
|
|
164
|
+
return Buffer.alloc(64);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
for (let index = 0; index < psbt.inputCount; index++) {
|
|
168
|
+
psbt.signInput(index, mockKeyPair(index));
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
transaction: {
|
|
172
|
+
psbt: bitcoin.Psbt.fromHex(psbtHex),
|
|
173
|
+
publicKey: transactionRequest.publicKey,
|
|
174
|
+
},
|
|
175
|
+
mpcPayloads: mpcPayloads.sort((a, b) => a.index - b.index),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
addSignature({ transaction: { psbt, publicKey }, mpcSignatures, }) {
|
|
179
|
+
const publicKeyBuffer = Buffer.from(publicKey, 'hex');
|
|
180
|
+
const keyPair = (index) => ({
|
|
181
|
+
publicKey: publicKeyBuffer,
|
|
182
|
+
sign: () => {
|
|
183
|
+
const mpcSignature = mpcSignatures[index];
|
|
184
|
+
return Bitcoin.parseRSVSignature(mpcSignature);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
for (let index = 0; index < psbt.inputCount; index++) {
|
|
188
|
+
psbt.signInput(index, keyPair(index));
|
|
189
|
+
}
|
|
190
|
+
psbt.finalizeAllInputs();
|
|
191
|
+
return psbt.extractTransaction().toHex();
|
|
192
|
+
}
|
|
193
|
+
async broadcastTx(txSerialized) {
|
|
194
|
+
return await this.btcRpcAdapter.broadcastTransaction(txSerialized);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
Bitcoin.SATOSHIS_PER_BTC = 100000000;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type * as bitcoin from 'bitcoinjs-lib';
|
|
2
|
+
export interface BTCTransaction {
|
|
3
|
+
vout: Array<{
|
|
4
|
+
scriptpubkey: string;
|
|
5
|
+
value: number;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export interface BTCInput {
|
|
9
|
+
txid: string;
|
|
10
|
+
vout: number;
|
|
11
|
+
value: number;
|
|
12
|
+
scriptPubKey: Buffer;
|
|
13
|
+
}
|
|
14
|
+
export type BTCOutput = {
|
|
15
|
+
value: number;
|
|
16
|
+
} & ({
|
|
17
|
+
address: string;
|
|
18
|
+
script?: never;
|
|
19
|
+
} | {
|
|
20
|
+
address?: never;
|
|
21
|
+
script: Buffer;
|
|
22
|
+
});
|
|
23
|
+
export type BTCTransactionRequest = {
|
|
24
|
+
publicKey: string;
|
|
25
|
+
} & ({
|
|
26
|
+
inputs: BTCInput[];
|
|
27
|
+
outputs: BTCOutput[];
|
|
28
|
+
from?: never;
|
|
29
|
+
to?: never;
|
|
30
|
+
value?: never;
|
|
31
|
+
} | {
|
|
32
|
+
inputs?: never;
|
|
33
|
+
outputs?: never;
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
value: string;
|
|
37
|
+
});
|
|
38
|
+
export interface BTCUnsignedTransaction {
|
|
39
|
+
psbt: bitcoin.Psbt;
|
|
40
|
+
publicKey: string;
|
|
41
|
+
}
|
|
42
|
+
export type BTCNetworkIds = 'mainnet' | 'testnet' | 'regtest';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
2
|
+
export function parseBTCNetwork(network) {
|
|
3
|
+
switch (network.toLowerCase()) {
|
|
4
|
+
case 'mainnet':
|
|
5
|
+
return bitcoin.networks.bitcoin;
|
|
6
|
+
case 'testnet':
|
|
7
|
+
return bitcoin.networks.testnet;
|
|
8
|
+
case 'regtest':
|
|
9
|
+
return bitcoin.networks.regtest;
|
|
10
|
+
default:
|
|
11
|
+
throw new Error(`Unknown Bitcoin network: ${network}`);
|
|
12
|
+
}
|
|
13
|
+
}
|