signet.js 0.0.2 → 0.0.3

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.
Files changed (71) hide show
  1. package/.eslintrc.json +55 -0
  2. package/.prettierrc +1 -0
  3. package/babel.config.js +6 -0
  4. package/docs/pages/index.mdx +36 -0
  5. package/docs/pages/signetjs/advanced/chain-signatures-contract.mdx +52 -0
  6. package/docs/pages/signetjs/advanced/chain.mdx +45 -0
  7. package/docs/pages/signetjs/chains/bitcoin/bitcoin.mdx +171 -0
  8. package/docs/pages/signetjs/chains/bitcoin/btc-rpc-adapter.mdx +26 -0
  9. package/docs/pages/signetjs/chains/cosmos.mdx +171 -0
  10. package/docs/pages/signetjs/chains/evm.mdx +319 -0
  11. package/docs/pages/signetjs/contract-addresses.mdx +27 -0
  12. package/docs/pages/signetjs/index.mdx +88 -0
  13. package/docs/snippets/code/contract.ts +21 -0
  14. package/docs/snippets/code/evm/env.ts +16 -0
  15. package/docs/snippets/code/near/env.ts +13 -0
  16. package/hardhat.config.mts +19 -0
  17. package/package.json +1 -1
  18. package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.ts +11 -0
  19. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.ts +96 -0
  20. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.ts +1 -0
  21. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.ts +72 -0
  22. package/src/chains/Bitcoin/BTCRpcAdapter/index.ts +6 -0
  23. package/src/chains/Bitcoin/Bitcoin.ts +287 -0
  24. package/src/chains/Bitcoin/types.ts +48 -0
  25. package/src/chains/Bitcoin/utils.ts +14 -0
  26. package/src/chains/Chain.ts +92 -0
  27. package/src/chains/ChainSignatureContract.ts +65 -0
  28. package/src/chains/Cosmos/Cosmos.ts +258 -0
  29. package/src/chains/Cosmos/types.ts +35 -0
  30. package/src/chains/Cosmos/utils.ts +45 -0
  31. package/src/chains/EVM/EVM.test.ts +238 -0
  32. package/src/chains/EVM/EVM.ts +334 -0
  33. package/src/chains/EVM/types.ts +53 -0
  34. package/src/chains/EVM/utils.ts +27 -0
  35. package/src/chains/index.ts +38 -0
  36. package/src/chains/types.ts +46 -0
  37. package/src/index.ts +2 -0
  38. package/src/utils/chains/evm/ChainSignaturesContract.ts +286 -0
  39. package/src/utils/chains/evm/ChainSignaturesContractABI.ts +359 -0
  40. package/src/utils/chains/evm/errors.ts +52 -0
  41. package/src/utils/chains/evm/index.ts +3 -0
  42. package/src/utils/chains/evm/types.ts +28 -0
  43. package/src/utils/chains/evm/utils.ts +11 -0
  44. package/src/utils/chains/index.ts +2 -0
  45. package/src/utils/chains/near/ChainSignatureContract.ts +155 -0
  46. package/src/utils/chains/near/account.ts +42 -0
  47. package/src/utils/chains/near/constants.ts +4 -0
  48. package/src/utils/chains/near/index.ts +3 -0
  49. package/src/utils/chains/near/signAndSend/index.ts +1 -0
  50. package/src/utils/chains/near/signAndSend/keypair.ts +178 -0
  51. package/src/utils/chains/near/transactionBuilder.ts +73 -0
  52. package/src/utils/chains/near/types.ts +77 -0
  53. package/src/utils/constants.ts +62 -0
  54. package/src/utils/cryptography.ts +131 -0
  55. package/src/utils/index.ts +3 -0
  56. package/src/utils/publicKey.ts +23 -0
  57. package/tsconfig.eslint.json +8 -0
  58. package/tsconfig.json +122 -0
  59. package/tsup.config.ts +55 -0
  60. package/vitest.config.ts +16 -0
  61. package/vocs.config.ts +73 -0
  62. package/browser/index.browser.cjs +0 -3
  63. package/browser/index.browser.cjs.map +0 -1
  64. package/browser/index.browser.js +0 -3
  65. package/browser/index.browser.js.map +0 -1
  66. package/node/index.node.cjs +0 -3
  67. package/node/index.node.cjs.map +0 -1
  68. package/node/index.node.js +0 -3
  69. package/node/index.node.js.map +0 -1
  70. package/types/index.d.cts +0 -919
  71. package/types/index.d.ts +0 -919
@@ -0,0 +1,287 @@
1
+ import * as bitcoin from 'bitcoinjs-lib'
2
+
3
+ import { type BTCRpcAdapter } from '@chains/Bitcoin/BTCRpcAdapter'
4
+ import type {
5
+ BTCInput,
6
+ BTCNetworkIds,
7
+ BTCOutput,
8
+ BTCTransactionRequest,
9
+ BTCUnsignedTransaction,
10
+ } from '@chains/Bitcoin/types'
11
+ import { parseBTCNetwork } from '@chains/Bitcoin/utils'
12
+ import { Chain } from '@chains/Chain'
13
+ import type { BaseChainSignatureContract } from '@chains/ChainSignatureContract'
14
+ import type { HashToSign, RSVSignature, KeyDerivationPath } from '@chains/types'
15
+ import { cryptography } from '@utils'
16
+
17
+ /**
18
+ * Implementation of the Chain interface for Bitcoin network.
19
+ * Handles interactions with both Bitcoin mainnet and testnet, supporting P2WPKH transactions.
20
+ */
21
+ export class Bitcoin extends Chain<
22
+ BTCTransactionRequest,
23
+ BTCUnsignedTransaction
24
+ > {
25
+ private static readonly SATOSHIS_PER_BTC = 100_000_000
26
+
27
+ private readonly network: BTCNetworkIds
28
+ private readonly btcRpcAdapter: BTCRpcAdapter
29
+ private readonly contract: BaseChainSignatureContract
30
+
31
+ /**
32
+ * Creates a new Bitcoin chain instance
33
+ * @param params - Configuration parameters
34
+ * @param params.network - Network identifier (mainnet/testnet)
35
+ * @param params.contract - Instance of the chain signature contract for MPC operations
36
+ * @param params.btcRpcAdapter - Bitcoin RPC adapter for network interactions
37
+ */
38
+ constructor({
39
+ network,
40
+ contract,
41
+ btcRpcAdapter,
42
+ }: {
43
+ network: BTCNetworkIds
44
+ contract: BaseChainSignatureContract
45
+ btcRpcAdapter: BTCRpcAdapter
46
+ }) {
47
+ super()
48
+
49
+ this.network = network
50
+ this.btcRpcAdapter = btcRpcAdapter
51
+ this.contract = contract
52
+ }
53
+
54
+ /**
55
+ * Converts satoshis to BTC
56
+ * @param satoshis - Amount in satoshis
57
+ * @returns Amount in BTC
58
+ */
59
+ static toBTC(satoshis: number): number {
60
+ return satoshis / Bitcoin.SATOSHIS_PER_BTC
61
+ }
62
+
63
+ /**
64
+ * Converts BTC to satoshis
65
+ * @param btc - Amount in BTC
66
+ * @returns Amount in satoshis (rounded)
67
+ */
68
+ static toSatoshi(btc: number): number {
69
+ return Math.round(btc * Bitcoin.SATOSHIS_PER_BTC)
70
+ }
71
+
72
+ private async fetchTransaction(
73
+ transactionId: string
74
+ ): Promise<bitcoin.Transaction> {
75
+ const data = await this.btcRpcAdapter.getTransaction(transactionId)
76
+ const tx = new bitcoin.Transaction()
77
+
78
+ data.vout.forEach((vout) => {
79
+ const scriptPubKey = Buffer.from(vout.scriptpubkey, 'hex')
80
+ tx.addOutput(scriptPubKey, Number(vout.value))
81
+ })
82
+
83
+ return tx
84
+ }
85
+
86
+ private static transformRSVSignature(signature: RSVSignature): Buffer {
87
+ const r = signature.r.padStart(64, '0')
88
+ const s = signature.s.padStart(64, '0')
89
+
90
+ const rawSignature = Buffer.from(r + s, 'hex')
91
+
92
+ if (rawSignature.length !== 64) {
93
+ throw new Error('Invalid signature length.')
94
+ }
95
+
96
+ return rawSignature
97
+ }
98
+
99
+ /**
100
+ * Creates a Partially Signed Bitcoin Transaction (PSBT)
101
+ * @param params - Parameters for creating the PSBT
102
+ * @param params.transactionRequest - Transaction request containing inputs and outputs
103
+ * @returns Created PSBT instance
104
+ */
105
+ async createPSBT({
106
+ transactionRequest,
107
+ }: {
108
+ transactionRequest: BTCTransactionRequest
109
+ }): Promise<bitcoin.Psbt> {
110
+ const { inputs, outputs } =
111
+ transactionRequest.inputs && transactionRequest.outputs
112
+ ? transactionRequest
113
+ : await this.btcRpcAdapter.selectUTXOs(transactionRequest.from, [
114
+ {
115
+ address: transactionRequest.to,
116
+ value: parseFloat(transactionRequest.value),
117
+ },
118
+ ])
119
+
120
+ const psbt = new bitcoin.Psbt({ network: parseBTCNetwork(this.network) })
121
+
122
+ await Promise.all(
123
+ inputs.map(async (input: BTCInput) => {
124
+ if (!input.scriptPubKey) {
125
+ const transaction = await this.fetchTransaction(input.txid)
126
+ const prevOut = transaction.outs[input.vout]
127
+ input.scriptPubKey = prevOut.script
128
+ }
129
+
130
+ // Prepare the input as P2WPKH
131
+ psbt.addInput({
132
+ hash: input.txid,
133
+ index: input.vout,
134
+ witnessUtxo: {
135
+ script: input.scriptPubKey,
136
+ value: input.value,
137
+ },
138
+ })
139
+ })
140
+ )
141
+
142
+ outputs.forEach((out: BTCOutput) => {
143
+ if ('address' in out) {
144
+ psbt.addOutput({
145
+ address: out.address,
146
+ value: out.value,
147
+ })
148
+ } else if ('script' in out) {
149
+ psbt.addOutput({
150
+ script: out.script,
151
+ value: out.value,
152
+ })
153
+ } else if (transactionRequest.from !== undefined) {
154
+ // Include change address from coinselect
155
+ psbt.addOutput({
156
+ value: Number(out.value),
157
+ address: transactionRequest.from,
158
+ })
159
+ }
160
+ })
161
+
162
+ return psbt
163
+ }
164
+
165
+ async getBalance(
166
+ address: string
167
+ ): Promise<{ balance: bigint; decimals: number }> {
168
+ const balance = BigInt(await this.btcRpcAdapter.getBalance(address))
169
+ return {
170
+ balance,
171
+ decimals: 8,
172
+ }
173
+ }
174
+
175
+ async deriveAddressAndPublicKey(
176
+ predecessor: string,
177
+ path: KeyDerivationPath
178
+ ): Promise<{ address: string; publicKey: string }> {
179
+ const uncompressedPubKey = await this.contract.getDerivedPublicKey({
180
+ path,
181
+ predecessor,
182
+ })
183
+
184
+ if (!uncompressedPubKey) {
185
+ throw new Error('Failed to get derived public key')
186
+ }
187
+
188
+ const derivedKey = cryptography.compressPubKey(uncompressedPubKey)
189
+ const publicKeyBuffer = Buffer.from(derivedKey, 'hex')
190
+ const network = parseBTCNetwork(this.network)
191
+
192
+ const payment = bitcoin.payments.p2wpkh({
193
+ pubkey: publicKeyBuffer,
194
+ network,
195
+ })
196
+
197
+ const { address } = payment
198
+
199
+ if (!address) {
200
+ throw new Error('Failed to generate Bitcoin address')
201
+ }
202
+
203
+ return { address, publicKey: derivedKey }
204
+ }
205
+
206
+ serializeTransaction(transaction: BTCUnsignedTransaction): string {
207
+ return JSON.stringify({
208
+ psbt: transaction.psbt.toHex(),
209
+ publicKey: transaction.publicKey,
210
+ })
211
+ }
212
+
213
+ deserializeTransaction(serialized: string): BTCUnsignedTransaction {
214
+ const transactionJSON = JSON.parse(serialized)
215
+ return {
216
+ psbt: bitcoin.Psbt.fromHex(transactionJSON.psbt as string),
217
+ publicKey: transactionJSON.publicKey,
218
+ }
219
+ }
220
+
221
+ async prepareTransactionForSigning(
222
+ transactionRequest: BTCTransactionRequest
223
+ ): Promise<{
224
+ transaction: BTCUnsignedTransaction
225
+ hashesToSign: HashToSign[]
226
+ }> {
227
+ const publicKeyBuffer = Buffer.from(transactionRequest.publicKey, 'hex')
228
+ const psbt = await this.createPSBT({
229
+ transactionRequest,
230
+ })
231
+
232
+ // We can't double sign a PSBT, therefore we serialize the payload before to return it
233
+ const psbtHex = psbt.toHex()
234
+
235
+ const hashesToSign: HashToSign[] = []
236
+
237
+ const mockKeyPair = (index: number): bitcoin.Signer => ({
238
+ publicKey: publicKeyBuffer,
239
+ sign: (hash: Buffer): Buffer => {
240
+ hashesToSign[index] = Array.from(hash)
241
+ // Return dummy signature to satisfy the interface
242
+ return Buffer.alloc(64)
243
+ },
244
+ })
245
+
246
+ for (let index = 0; index < psbt.inputCount; index++) {
247
+ psbt.signInput(index, mockKeyPair(index))
248
+ }
249
+
250
+ return {
251
+ transaction: {
252
+ psbt: bitcoin.Psbt.fromHex(psbtHex),
253
+ publicKey: transactionRequest.publicKey,
254
+ },
255
+ hashesToSign,
256
+ }
257
+ }
258
+
259
+ attachTransactionSignature({
260
+ transaction: { psbt, publicKey },
261
+ rsvSignatures,
262
+ }: {
263
+ transaction: BTCUnsignedTransaction
264
+ rsvSignatures: RSVSignature[]
265
+ }): string {
266
+ const publicKeyBuffer = Buffer.from(publicKey, 'hex')
267
+
268
+ const keyPair = (index: number): bitcoin.Signer => ({
269
+ publicKey: publicKeyBuffer,
270
+ sign: () => {
271
+ const mpcSignature = rsvSignatures[index]
272
+ return Bitcoin.transformRSVSignature(mpcSignature)
273
+ },
274
+ })
275
+
276
+ for (let index = 0; index < psbt.inputCount; index++) {
277
+ psbt.signInput(index, keyPair(index))
278
+ }
279
+
280
+ psbt.finalizeAllInputs()
281
+ return psbt.extractTransaction().toHex()
282
+ }
283
+
284
+ async broadcastTx(txSerialized: string): Promise<string> {
285
+ return await this.btcRpcAdapter.broadcastTransaction(txSerialized)
286
+ }
287
+ }
@@ -0,0 +1,48 @@
1
+ import type * as bitcoin from 'bitcoinjs-lib'
2
+
3
+ export interface BTCTransaction {
4
+ vout: Array<{
5
+ scriptpubkey: string
6
+ value: number
7
+ }>
8
+ }
9
+
10
+ export interface BTCInput {
11
+ txid: string
12
+ vout: number
13
+ value: number
14
+ scriptPubKey: Buffer
15
+ }
16
+
17
+ export type BTCOutput =
18
+ | {
19
+ value: number
20
+ }
21
+ | { address: string; value: number }
22
+ | { script: Buffer; value: number }
23
+
24
+ export type BTCTransactionRequest = {
25
+ publicKey: string
26
+ } & (
27
+ | {
28
+ inputs: BTCInput[]
29
+ outputs: BTCOutput[]
30
+ from?: never
31
+ to?: never
32
+ value?: never
33
+ }
34
+ | {
35
+ inputs?: never
36
+ outputs?: never
37
+ from: string
38
+ to: string
39
+ value: string
40
+ }
41
+ )
42
+
43
+ export interface BTCUnsignedTransaction {
44
+ psbt: bitcoin.Psbt
45
+ publicKey: string
46
+ }
47
+
48
+ export type BTCNetworkIds = 'mainnet' | 'testnet' | 'regtest'
@@ -0,0 +1,14 @@
1
+ import * as bitcoin from 'bitcoinjs-lib'
2
+
3
+ export function parseBTCNetwork(network: string): bitcoin.networks.Network {
4
+ switch (network.toLowerCase()) {
5
+ case 'mainnet':
6
+ return bitcoin.networks.bitcoin
7
+ case 'testnet':
8
+ return bitcoin.networks.testnet
9
+ case 'regtest':
10
+ return bitcoin.networks.regtest
11
+ default:
12
+ throw new Error(`Unknown Bitcoin network: ${network}`)
13
+ }
14
+ }
@@ -0,0 +1,92 @@
1
+ import type { KeyDerivationPath, HashToSign, RSVSignature } from '@chains/types'
2
+
3
+ export abstract class Chain<TransactionRequest, UnsignedTransaction> {
4
+ /**
5
+ * Gets the native token balance and decimals for a given address
6
+ *
7
+ * @param address - The address to check
8
+ * @returns Promise resolving to an object containing:
9
+ * - balance: The balance as a bigint, in the chain's base units
10
+ * - decimals: The number of decimals used to format the balance
11
+ */
12
+ abstract getBalance(address: string): Promise<{
13
+ balance: bigint
14
+ decimals: number
15
+ }>
16
+
17
+ /**
18
+ * Uses Sig Network Key Derivation Function to derive the address and public key. from a signer ID and string path.
19
+ *
20
+ * @param predecessor - The id/address of the account requesting signature
21
+ * @param path - The string path used to derive the key
22
+ * @returns Promise resolving to the derived address and public key
23
+ */
24
+ abstract deriveAddressAndPublicKey(
25
+ predecessor: string,
26
+ path: KeyDerivationPath
27
+ ): Promise<{
28
+ address: string
29
+ publicKey: string
30
+ }>
31
+
32
+ /**
33
+ * Serializes an unsigned transaction to a string format.
34
+ * This is useful for storing or transmitting the transaction.
35
+ *
36
+ * @param transaction - The unsigned transaction to serialize
37
+ * @returns The serialized transaction string
38
+ */
39
+ abstract serializeTransaction(transaction: UnsignedTransaction): string
40
+
41
+ /**
42
+ * Deserializes a transaction string back into an unsigned transaction object.
43
+ * This reverses the serialization done by serializeTransaction().
44
+ *
45
+ * @param serialized - The serialized transaction string
46
+ * @returns The deserialized unsigned transaction
47
+ */
48
+ abstract deserializeTransaction(serialized: string): UnsignedTransaction
49
+
50
+ /**
51
+ * Prepares a transaction for Sig Network MPC signing by creating the necessary payloads.
52
+ * This method handles chain-specific transaction preparation including:
53
+ * - Fee calculation
54
+ * - Nonce/sequence management
55
+ * - UTXO selection (for UTXO-based chains)
56
+ * - Transaction encoding
57
+ *
58
+ * @param transactionRequest - The transaction request containing parameters like recipient, amount, etc.
59
+ * @returns Promise resolving to an object containing:
60
+ * - transaction: The unsigned transaction
61
+ * - hashesToSign: Array of payloads to be signed by MPC. The order of these payloads must match
62
+ * the order of signatures provided to attachTransactionSignature()
63
+ */
64
+ abstract prepareTransactionForSigning(
65
+ transactionRequest: TransactionRequest
66
+ ): Promise<{
67
+ transaction: UnsignedTransaction
68
+ hashesToSign: HashToSign[]
69
+ }>
70
+
71
+ /**
72
+ * Adds Sig Network MPC-generated signatures to an unsigned transaction.
73
+ *
74
+ * @param params - Parameters for adding signatures
75
+ * @param params.transaction - The unsigned transaction to add signatures to
76
+ * @param params.rsvSignatures - Array of RSV signatures generated through MPC. Must be in the same order
77
+ * as the payloads returned by prepareTransactionForSigning()
78
+ * @returns The serialized signed transaction ready for broadcast
79
+ */
80
+ abstract attachTransactionSignature(params: {
81
+ transaction: UnsignedTransaction
82
+ rsvSignatures: RSVSignature[]
83
+ }): string
84
+
85
+ /**
86
+ * Broadcasts a signed transaction to the network.
87
+ *
88
+ * @param txSerialized - The serialized signed transaction
89
+ * @returns Promise resolving to the transaction hash/ID
90
+ */
91
+ abstract broadcastTx(txSerialized: string): Promise<string>
92
+ }
@@ -0,0 +1,65 @@
1
+ import type BN from 'bn.js'
2
+
3
+ import type { RSVSignature, UncompressedPubKeySEC1 } from '@chains/types'
4
+
5
+ export interface SignArgs {
6
+ /** The payload to sign as an array of 32 bytes */
7
+ payload: number[]
8
+ /** The derivation path for key generation */
9
+ path: string
10
+ /** Version of the key to use */
11
+ key_version: number
12
+ }
13
+
14
+ /**
15
+ * Base contract interface required for compatibility with Chain instances like EVM and Bitcoin.
16
+ *
17
+ * See {@link EVM} and {@link Bitcoin} for example implementations.
18
+ */
19
+ export abstract class BaseChainSignatureContract {
20
+ /**
21
+ * Gets the current signature deposit required by the contract.
22
+ * This deposit amount helps manage network congestion.
23
+ *
24
+ * @returns Promise resolving to the required deposit amount as a BigNumber
25
+ */
26
+ abstract getCurrentSignatureDeposit(): Promise<BN>
27
+
28
+ /**
29
+ * Derives a child public key using a\ derivation path and predecessor.
30
+ *
31
+ * @param args - Arguments for key derivation
32
+ * @param args.path - The string path to use derive the key
33
+ * @param args.predecessor - The id/address of the account requesting signature
34
+ * @returns Promise resolving to the derived SEC1 uncompressed public key
35
+ */
36
+ abstract getDerivedPublicKey(
37
+ args: {
38
+ path: string
39
+ predecessor: string
40
+ } & Record<string, unknown>
41
+ ): Promise<UncompressedPubKeySEC1>
42
+ }
43
+
44
+ /**
45
+ * Full contract interface that extends BaseChainSignatureContract to provide all Sig Network Smart Contract capabilities.
46
+ */
47
+ export abstract class ChainSignatureContract extends BaseChainSignatureContract {
48
+ /**
49
+ * Signs a payload using Sig Network MPC.
50
+ *
51
+ * @param args - Arguments for the signing operation
52
+ * @param args.payload - The data to sign as an array of 32 bytes
53
+ * @param args.path - The string path to use derive the key
54
+ * @param args.key_version - Version of the key to use
55
+ * @returns Promise resolving to the RSV signature
56
+ */
57
+ abstract sign(args: SignArgs & Record<string, unknown>): Promise<RSVSignature>
58
+
59
+ /**
60
+ * Gets the public key associated with this contract instance.
61
+ *
62
+ * @returns Promise resolving to the SEC1 uncompressed public key
63
+ */
64
+ abstract getPublicKey(): Promise<UncompressedPubKeySEC1>
65
+ }