signet.js 0.0.1-beta.5 → 0.0.1-beta.7
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/.eslintrc.json +55 -0
- package/.prettierrc +1 -0
- package/LICENSE +19 -0
- package/README.md +42 -14
- package/babel.config.js +6 -0
- package/docs/pages/chain-signatures-contract.mdx +56 -0
- package/docs/pages/chain.mdx +45 -0
- package/docs/pages/chains/bitcoin/bitcoin.mdx +191 -0
- package/docs/pages/chains/bitcoin/btc-rpc-adapter.mdx +307 -0
- package/docs/pages/chains/cosmos.mdx +181 -0
- package/docs/pages/chains/evm.mdx +189 -0
- package/docs/pages/index.mdx +99 -0
- package/docs/snippets/contract.ts +21 -0
- package/docs/snippets/env.ts +13 -0
- package/jest.config.ts +199 -0
- package/package.json +21 -11
- package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.ts +11 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.ts +96 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.ts +1 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.ts +72 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/index.ts +6 -0
- package/src/chains/Bitcoin/Bitcoin.ts +301 -0
- package/src/chains/Bitcoin/types.ts +45 -0
- package/src/chains/Bitcoin/utils.ts +14 -0
- package/src/chains/Chain.ts +96 -0
- package/src/chains/ChainSignatureContract.ts +48 -0
- package/src/chains/Cosmos/Cosmos.ts +279 -0
- package/src/chains/Cosmos/types.ts +35 -0
- package/src/chains/Cosmos/utils.ts +45 -0
- package/src/chains/EVM/EVM.ts +180 -0
- package/src/chains/EVM/types.ts +7 -0
- package/src/chains/EVM/utils.ts +26 -0
- package/src/chains/index.ts +34 -0
- package/src/chains/types.ts +35 -0
- package/src/chains/utils.ts +40 -0
- package/src/index.ts +2 -0
- package/src/utils/chains/index.ts +1 -0
- package/src/utils/chains/near/ChainSignatureContract.ts +195 -0
- package/src/utils/chains/near/account.ts +42 -0
- package/src/utils/chains/near/constants.ts +4 -0
- package/src/utils/chains/near/index.ts +3 -0
- package/src/utils/chains/near/relayer/index.ts +1 -0
- package/src/utils/chains/near/relayer/relayer.ts +39 -0
- package/src/utils/chains/near/relayer/types.ts +24 -0
- package/src/utils/chains/near/signAndSend/index.ts +1 -0
- package/src/utils/chains/near/signAndSend/keypair.ts +180 -0
- package/src/utils/chains/near/transactionBuilder.ts +138 -0
- package/src/utils/chains/near/types.ts +67 -0
- package/src/utils/index.ts +1 -0
- package/tsconfig.eslint.json +8 -0
- package/tsconfig.json +116 -0
- package/vite.config.ts +47 -0
- package/vocs.config.ts +60 -0
- package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.d.ts +0 -10
- package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.js +0 -2
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.d.ts +0 -15
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.js +0 -69
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.d.ts +0 -1
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.js +0 -1
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.d.ts +0 -69
- package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.js +0 -1
- package/src/chains/Bitcoin/BTCRpcAdapter/index.d.ts +0 -5
- package/src/chains/Bitcoin/BTCRpcAdapter/index.js +0 -5
- package/src/chains/Bitcoin/Bitcoin.d.ts +0 -69
- package/src/chains/Bitcoin/Bitcoin.js +0 -198
- package/src/chains/Bitcoin/types.d.ts +0 -42
- package/src/chains/Bitcoin/types.js +0 -1
- package/src/chains/Bitcoin/utils.d.ts +0 -2
- package/src/chains/Bitcoin/utils.js +0 -13
- package/src/chains/Chain.d.ts +0 -89
- package/src/chains/Chain.js +0 -9
- package/src/chains/ChainSignatureContract.d.ts +0 -62
- package/src/chains/ChainSignatureContract.js +0 -7
- package/src/chains/Cosmos/Cosmos.d.ts +0 -51
- package/src/chains/Cosmos/Cosmos.js +0 -157
- package/src/chains/Cosmos/types.d.ts +0 -30
- package/src/chains/Cosmos/types.js +0 -1
- package/src/chains/Cosmos/utils.d.ts +0 -2
- package/src/chains/Cosmos/utils.js +0 -27
- package/src/chains/EVM/EVM.d.ts +0 -42
- package/src/chains/EVM/EVM.js +0 -109
- package/src/chains/EVM/types.d.ts +0 -5
- package/src/chains/EVM/types.js +0 -1
- package/src/chains/EVM/utils.d.ts +0 -7
- package/src/chains/EVM/utils.js +0 -14
- package/src/chains/index.d.ts +0 -12
- package/src/chains/index.js +0 -12
- package/src/chains/types.d.ts +0 -31
- package/src/chains/types.js +0 -1
- package/src/chains/utils.d.ts +0 -12
- package/src/chains/utils.js +0 -27
- package/src/index.d.ts +0 -2
- package/src/index.js +0 -2
- package/src/utils/chains/index.d.ts +0 -1
- package/src/utils/chains/index.js +0 -1
- package/src/utils/chains/near/account.d.ts +0 -13
- package/src/utils/chains/near/account.js +0 -22
- package/src/utils/chains/near/constants.d.ts +0 -3
- package/src/utils/chains/near/constants.js +0 -3
- package/src/utils/chains/near/contract.d.ts +0 -40
- package/src/utils/chains/near/contract.js +0 -102
- package/src/utils/chains/near/index.d.ts +0 -3
- package/src/utils/chains/near/index.js +0 -3
- package/src/utils/chains/near/relayer/index.d.ts +0 -1
- package/src/utils/chains/near/relayer/index.js +0 -1
- package/src/utils/chains/near/relayer/relayer.d.ts +0 -8
- package/src/utils/chains/near/relayer/relayer.js +0 -33
- package/src/utils/chains/near/relayer/types.d.ts +0 -22
- package/src/utils/chains/near/relayer/types.js +0 -1
- package/src/utils/chains/near/signAndSend/index.d.ts +0 -1
- package/src/utils/chains/near/signAndSend/index.js +0 -1
- package/src/utils/chains/near/signAndSend/keypair.d.ts +0 -6
- package/src/utils/chains/near/signAndSend/keypair.js +0 -127
- package/src/utils/chains/near/transactionBuilder.d.ts +0 -26
- package/src/utils/chains/near/transactionBuilder.js +0 -72
- package/src/utils/chains/near/types.d.ts +0 -50
- package/src/utils/chains/near/types.js +0 -1
- package/src/utils/index.d.ts +0 -1
- package/src/utils/index.js +0 -1
- package/vocs.config.d.ts +0 -3
- package/vocs.config.js +0 -71
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { encodeSecp256k1Pubkey } from '@cosmjs/amino'
|
|
2
|
+
import { ripemd160, sha256 } from '@cosmjs/crypto'
|
|
3
|
+
import { toBase64, fromBase64, fromHex } from '@cosmjs/encoding'
|
|
4
|
+
import {
|
|
5
|
+
Registry,
|
|
6
|
+
makeSignBytes,
|
|
7
|
+
encodePubkey,
|
|
8
|
+
makeAuthInfoBytes,
|
|
9
|
+
makeSignDoc,
|
|
10
|
+
type TxBodyEncodeObject,
|
|
11
|
+
} from '@cosmjs/proto-signing'
|
|
12
|
+
import { GasPrice, StargateClient, calculateFee } from '@cosmjs/stargate'
|
|
13
|
+
import { bech32 } from 'bech32'
|
|
14
|
+
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing'
|
|
15
|
+
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
CosmosNetworkIds,
|
|
19
|
+
CosmosTransactionRequest,
|
|
20
|
+
CosmosUnsignedTransaction,
|
|
21
|
+
} from '@chains/Cosmos/types'
|
|
22
|
+
import type { ChainSignatureContract } from '@chains/ChainSignatureContract'
|
|
23
|
+
import type {
|
|
24
|
+
MPCPayloads,
|
|
25
|
+
RSVSignature,
|
|
26
|
+
KeyDerivationPath,
|
|
27
|
+
} from '@chains/types'
|
|
28
|
+
import { Chain } from '@chains/Chain'
|
|
29
|
+
import { utils } from '@chains'
|
|
30
|
+
import type { ChainInfo, BalanceResponse } from '@chains/Cosmos/types'
|
|
31
|
+
import { fetchChainInfo } from '@chains/Cosmos/utils'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Implementation of the Chain interface for Cosmos-based networks.
|
|
35
|
+
* Handles interactions with Cosmos SDK chains like Cosmos Hub, Osmosis, etc.
|
|
36
|
+
*/
|
|
37
|
+
export class Cosmos extends Chain<
|
|
38
|
+
CosmosTransactionRequest,
|
|
39
|
+
CosmosUnsignedTransaction
|
|
40
|
+
> {
|
|
41
|
+
private readonly registry: Registry
|
|
42
|
+
private readonly chainId: CosmosNetworkIds
|
|
43
|
+
private readonly contract: ChainSignatureContract
|
|
44
|
+
private readonly endpoints?: {
|
|
45
|
+
rpcUrl?: string
|
|
46
|
+
restUrl?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new Cosmos chain instance
|
|
51
|
+
* @param params - Configuration parameters
|
|
52
|
+
* @param params.chainId - Chain id for the Cosmos network
|
|
53
|
+
* @param params.contract - Instance of the chain signature contract for MPC operations
|
|
54
|
+
* @param params.endpoints - Optional RPC and REST endpoints
|
|
55
|
+
* @param params.endpoints.rpcUrl - Optional RPC endpoint URL
|
|
56
|
+
* @param params.endpoints.restUrl - Optional REST endpoint URL
|
|
57
|
+
*/
|
|
58
|
+
constructor({
|
|
59
|
+
chainId,
|
|
60
|
+
contract,
|
|
61
|
+
endpoints,
|
|
62
|
+
}: {
|
|
63
|
+
contract: ChainSignatureContract
|
|
64
|
+
chainId: CosmosNetworkIds
|
|
65
|
+
endpoints?: {
|
|
66
|
+
rpcUrl?: string
|
|
67
|
+
restUrl?: string
|
|
68
|
+
}
|
|
69
|
+
}) {
|
|
70
|
+
super()
|
|
71
|
+
|
|
72
|
+
this.contract = contract
|
|
73
|
+
this.registry = new Registry()
|
|
74
|
+
this.chainId = chainId
|
|
75
|
+
this.endpoints = endpoints
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private parseRSVSignature(rsvSignature: RSVSignature): Uint8Array {
|
|
79
|
+
return new Uint8Array([
|
|
80
|
+
...fromHex(rsvSignature.r),
|
|
81
|
+
...fromHex(rsvSignature.s),
|
|
82
|
+
])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async getChainInfo(): Promise<ChainInfo> {
|
|
86
|
+
return {
|
|
87
|
+
...(await fetchChainInfo(this.chainId)),
|
|
88
|
+
...this.endpoints,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getBalance(address: string): Promise<string> {
|
|
93
|
+
try {
|
|
94
|
+
const { restUrl, denom, decimals } = await this.getChainInfo()
|
|
95
|
+
|
|
96
|
+
const response = await fetch(
|
|
97
|
+
`${restUrl}/cosmos/bank/v1beta1/balances/${address}`
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = (await response.json()) as BalanceResponse
|
|
105
|
+
const balance = data.balances.find((b) => b.denom === denom)
|
|
106
|
+
const amount = balance?.amount ?? '0'
|
|
107
|
+
|
|
108
|
+
const formattedBalance = (
|
|
109
|
+
parseInt(amount) / Math.pow(10, decimals)
|
|
110
|
+
).toString()
|
|
111
|
+
return formattedBalance
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Failed to fetch Cosmos balance:', error)
|
|
114
|
+
throw new Error('Failed to fetch Cosmos balance')
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async deriveAddressAndPublicKey(
|
|
119
|
+
predecessor: string,
|
|
120
|
+
path: KeyDerivationPath
|
|
121
|
+
): Promise<{
|
|
122
|
+
address: string
|
|
123
|
+
publicKey: string
|
|
124
|
+
}> {
|
|
125
|
+
const { prefix } = await this.getChainInfo()
|
|
126
|
+
const uncompressedPubKey = await this.contract.getDerivedPublicKey({
|
|
127
|
+
path,
|
|
128
|
+
predecessor,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
if (!uncompressedPubKey) {
|
|
132
|
+
throw new Error('Failed to get derived public key')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const derivedKey = utils.compressPubKey(uncompressedPubKey)
|
|
136
|
+
const pubKeySha256 = sha256(fromHex(derivedKey))
|
|
137
|
+
const ripemd160Hash = ripemd160(pubKeySha256)
|
|
138
|
+
const address = bech32.encode(prefix, bech32.toWords(ripemd160Hash))
|
|
139
|
+
|
|
140
|
+
return { address, publicKey: derivedKey }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setTransaction(
|
|
144
|
+
transaction: CosmosUnsignedTransaction,
|
|
145
|
+
storageKey: string
|
|
146
|
+
): void {
|
|
147
|
+
const serialized = TxRaw.encode(transaction).finish()
|
|
148
|
+
window.localStorage.setItem(storageKey, toBase64(serialized))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getTransaction(
|
|
152
|
+
storageKey: string,
|
|
153
|
+
options?: {
|
|
154
|
+
remove?: boolean
|
|
155
|
+
}
|
|
156
|
+
): CosmosUnsignedTransaction | undefined {
|
|
157
|
+
const serialized = window.localStorage.getItem(storageKey)
|
|
158
|
+
if (!serialized) return undefined
|
|
159
|
+
|
|
160
|
+
if (options?.remove) {
|
|
161
|
+
window.localStorage.removeItem(storageKey)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return TxRaw.decode(fromBase64(serialized))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getMPCPayloadAndTransaction(
|
|
168
|
+
transactionRequest: CosmosTransactionRequest
|
|
169
|
+
): Promise<{
|
|
170
|
+
transaction: CosmosUnsignedTransaction
|
|
171
|
+
mpcPayloads: MPCPayloads
|
|
172
|
+
}> {
|
|
173
|
+
const { denom, rpcUrl, gasPrice } = await this.getChainInfo()
|
|
174
|
+
const publicKeyBytes = fromHex(transactionRequest.publicKey)
|
|
175
|
+
|
|
176
|
+
const gasLimit = transactionRequest.gas || 200_000
|
|
177
|
+
|
|
178
|
+
const fee = calculateFee(
|
|
179
|
+
gasLimit,
|
|
180
|
+
GasPrice.fromString(`${gasPrice}${denom}`)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const client = await StargateClient.connect(rpcUrl)
|
|
184
|
+
const accountOnChain = await client.getAccount(transactionRequest.address)
|
|
185
|
+
if (!accountOnChain) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Account ${transactionRequest.address} does not exist on chain`
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { accountNumber, sequence } = accountOnChain
|
|
192
|
+
|
|
193
|
+
const txBodyEncodeObject: TxBodyEncodeObject = {
|
|
194
|
+
typeUrl: '/cosmos.tx.v1beta1.TxBody',
|
|
195
|
+
value: {
|
|
196
|
+
messages: transactionRequest.messages,
|
|
197
|
+
memo: transactionRequest.memo || '',
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const txBodyBytes = this.registry.encode(txBodyEncodeObject)
|
|
202
|
+
|
|
203
|
+
const pubkey = encodePubkey(encodeSecp256k1Pubkey(publicKeyBytes))
|
|
204
|
+
|
|
205
|
+
// TODO: Allow caller to provide: multiple signers, fee payer, fee granter
|
|
206
|
+
const authInfoBytes = makeAuthInfoBytes(
|
|
207
|
+
[
|
|
208
|
+
{
|
|
209
|
+
pubkey,
|
|
210
|
+
sequence,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
fee.amount,
|
|
214
|
+
Number(fee.gas),
|
|
215
|
+
undefined,
|
|
216
|
+
undefined,
|
|
217
|
+
SignMode.SIGN_MODE_DIRECT
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const signDoc = makeSignDoc(
|
|
221
|
+
txBodyBytes,
|
|
222
|
+
authInfoBytes,
|
|
223
|
+
this.chainId,
|
|
224
|
+
accountNumber
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
const signBytes = makeSignBytes(signDoc)
|
|
228
|
+
const payload = Array.from(sha256(signBytes))
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
transaction: TxRaw.fromPartial({
|
|
232
|
+
bodyBytes: txBodyBytes,
|
|
233
|
+
authInfoBytes,
|
|
234
|
+
signatures: [],
|
|
235
|
+
}),
|
|
236
|
+
mpcPayloads: [
|
|
237
|
+
{
|
|
238
|
+
index: 0,
|
|
239
|
+
payload,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
addSignature({
|
|
246
|
+
transaction,
|
|
247
|
+
mpcSignatures,
|
|
248
|
+
}: {
|
|
249
|
+
transaction: CosmosUnsignedTransaction
|
|
250
|
+
mpcSignatures: RSVSignature[]
|
|
251
|
+
}): string {
|
|
252
|
+
// Allow support for multi-sig but the package only supports single-sig
|
|
253
|
+
transaction.signatures = mpcSignatures.map((sig) =>
|
|
254
|
+
this.parseRSVSignature(sig)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const txBytes = TxRaw.encode(transaction).finish()
|
|
258
|
+
return Buffer.from(txBytes).toString('hex')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async broadcastTx(txSerialized: string): Promise<string> {
|
|
262
|
+
try {
|
|
263
|
+
const { rpcUrl } = await this.getChainInfo()
|
|
264
|
+
const client = await StargateClient.connect(rpcUrl)
|
|
265
|
+
|
|
266
|
+
const txBytes = fromHex(txSerialized)
|
|
267
|
+
const broadcastResponse = await client.broadcastTx(txBytes)
|
|
268
|
+
|
|
269
|
+
if (broadcastResponse.code !== 0) {
|
|
270
|
+
throw new Error(`Broadcast error: ${broadcastResponse.rawLog}`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return broadcastResponse.transactionHash
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Transaction broadcast failed:', error)
|
|
276
|
+
throw new Error('Failed to broadcast transaction.')
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type EncodeObject } from '@cosmjs/proto-signing'
|
|
2
|
+
import { type TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
|
|
3
|
+
|
|
4
|
+
export type CosmosNetworkIds = string
|
|
5
|
+
|
|
6
|
+
export type CosmosUnsignedTransaction = TxRaw
|
|
7
|
+
|
|
8
|
+
export interface CosmosTransactionRequest {
|
|
9
|
+
address: string
|
|
10
|
+
publicKey: string
|
|
11
|
+
messages: EncodeObject[]
|
|
12
|
+
memo?: string
|
|
13
|
+
gas?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BalanceResponse {
|
|
17
|
+
balances: Array<{
|
|
18
|
+
denom: string
|
|
19
|
+
amount: string
|
|
20
|
+
}>
|
|
21
|
+
pagination: {
|
|
22
|
+
next_key: string | null
|
|
23
|
+
total: string
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ChainInfo {
|
|
28
|
+
prefix: string
|
|
29
|
+
denom: string
|
|
30
|
+
rpcUrl: string
|
|
31
|
+
restUrl: string
|
|
32
|
+
expectedChainId: string
|
|
33
|
+
gasPrice: number
|
|
34
|
+
decimals: number
|
|
35
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { chains, assets } from 'chain-registry'
|
|
2
|
+
|
|
3
|
+
import { type ChainInfo } from '@chains/Cosmos/types'
|
|
4
|
+
|
|
5
|
+
export const fetchChainInfo = async (chainId: string): Promise<ChainInfo> => {
|
|
6
|
+
const chainInfo = chains.find((chain) => chain.chain_id === chainId)
|
|
7
|
+
if (!chainInfo) {
|
|
8
|
+
throw new Error(`Chain info not found for chainId: ${chainId}`)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { bech32_prefix: prefix, chain_id: expectedChainId } = chainInfo
|
|
12
|
+
const denom = chainInfo.staking?.staking_tokens?.[0]?.denom
|
|
13
|
+
const rpcUrl = chainInfo.apis?.rpc?.[0]?.address
|
|
14
|
+
const restUrl = chainInfo.apis?.rest?.[0]?.address
|
|
15
|
+
const gasPrice = chainInfo.fees?.fee_tokens?.[0]?.average_gas_price
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
!prefix ||
|
|
19
|
+
!denom ||
|
|
20
|
+
!rpcUrl ||
|
|
21
|
+
!restUrl ||
|
|
22
|
+
!expectedChainId ||
|
|
23
|
+
gasPrice === undefined
|
|
24
|
+
) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Missing required chain information for ${chainInfo.chain_name}`
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const assetList = assets.find(
|
|
31
|
+
(asset) => asset.chain_name === chainInfo.chain_name
|
|
32
|
+
)
|
|
33
|
+
const asset = assetList?.assets.find((asset) => asset.base === denom)
|
|
34
|
+
const decimals = asset?.denom_units.find(
|
|
35
|
+
(unit) => unit.denom === asset.display
|
|
36
|
+
)?.exponent
|
|
37
|
+
|
|
38
|
+
if (decimals === undefined) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Could not find decimals for ${denom} on chain ${chainInfo.chain_name}`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { prefix, denom, rpcUrl, restUrl, expectedChainId, gasPrice, decimals }
|
|
45
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { fromHex } from '@cosmjs/encoding'
|
|
2
|
+
import { ethers, keccak256 } from 'ethers'
|
|
3
|
+
|
|
4
|
+
import { Chain } from '@chains/Chain'
|
|
5
|
+
import type { ChainSignatureContract } from '@chains/ChainSignatureContract'
|
|
6
|
+
import type {
|
|
7
|
+
EVMTransactionRequest,
|
|
8
|
+
EVMUnsignedTransaction,
|
|
9
|
+
} from '@chains/EVM/types'
|
|
10
|
+
import { fetchEVMFeeProperties } from '@chains/EVM/utils'
|
|
11
|
+
import type {
|
|
12
|
+
MPCPayloads,
|
|
13
|
+
RSVSignature,
|
|
14
|
+
KeyDerivationPath,
|
|
15
|
+
} from '@chains/types'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Implementation of the Chain interface for EVM-compatible networks.
|
|
19
|
+
* Handles interactions with Ethereum Virtual Machine based blockchains like Ethereum, BSC, Polygon, etc.
|
|
20
|
+
*/
|
|
21
|
+
export class EVM extends Chain<EVMTransactionRequest, EVMUnsignedTransaction> {
|
|
22
|
+
private readonly provider: ethers.JsonRpcProvider
|
|
23
|
+
private readonly contract: ChainSignatureContract
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new EVM chain instance
|
|
27
|
+
* @param params - Configuration parameters
|
|
28
|
+
* @param params.rpcUrl - URL of the EVM JSON-RPC provider (e.g., Infura endpoint)
|
|
29
|
+
* @param params.contract - Instance of the chain signature contract for MPC operations
|
|
30
|
+
*/
|
|
31
|
+
constructor({
|
|
32
|
+
rpcUrl,
|
|
33
|
+
contract,
|
|
34
|
+
}: {
|
|
35
|
+
rpcUrl: string
|
|
36
|
+
contract: ChainSignatureContract
|
|
37
|
+
}) {
|
|
38
|
+
super()
|
|
39
|
+
|
|
40
|
+
this.contract = contract
|
|
41
|
+
this.provider = new ethers.JsonRpcProvider(rpcUrl)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async attachGasAndNonce(
|
|
45
|
+
transaction: EVMTransactionRequest
|
|
46
|
+
): Promise<EVMUnsignedTransaction> {
|
|
47
|
+
const fees = await fetchEVMFeeProperties(
|
|
48
|
+
this.provider._getConnection().url,
|
|
49
|
+
transaction
|
|
50
|
+
)
|
|
51
|
+
const nonce = await this.provider.getTransactionCount(
|
|
52
|
+
transaction.from,
|
|
53
|
+
'latest'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const { from, ...rest } = transaction
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...fees,
|
|
60
|
+
chainId: this.provider._network.chainId,
|
|
61
|
+
nonce,
|
|
62
|
+
type: 2,
|
|
63
|
+
...rest,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private parseSignature(signature: RSVSignature): ethers.SignatureLike {
|
|
68
|
+
return ethers.Signature.from({
|
|
69
|
+
r: `0x${signature.r}`,
|
|
70
|
+
s: `0x${signature.s}`,
|
|
71
|
+
v: signature.v,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async deriveAddressAndPublicKey(
|
|
76
|
+
predecessor: string,
|
|
77
|
+
path: KeyDerivationPath
|
|
78
|
+
): Promise<{
|
|
79
|
+
address: string
|
|
80
|
+
publicKey: string
|
|
81
|
+
}> {
|
|
82
|
+
const uncompressedPubKey = await this.contract.getDerivedPublicKey({
|
|
83
|
+
path,
|
|
84
|
+
predecessor,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!uncompressedPubKey) {
|
|
88
|
+
throw new Error('Failed to get derived public key')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const publicKeyNoPrefix = uncompressedPubKey.startsWith('04')
|
|
92
|
+
? uncompressedPubKey.substring(2)
|
|
93
|
+
: uncompressedPubKey
|
|
94
|
+
|
|
95
|
+
const hash = ethers.keccak256(fromHex(publicKeyNoPrefix))
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
address: `0x${hash.substring(hash.length - 40)}`,
|
|
99
|
+
publicKey: uncompressedPubKey,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getBalance(address: string): Promise<string> {
|
|
104
|
+
try {
|
|
105
|
+
const balance = await this.provider.getBalance(address)
|
|
106
|
+
return ethers.formatEther(balance)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`Failed to fetch balance for address ${address}:`, error)
|
|
109
|
+
throw new Error('Failed to fetch balance.')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setTransaction(
|
|
114
|
+
transaction: EVMUnsignedTransaction,
|
|
115
|
+
storageKey: string
|
|
116
|
+
): void {
|
|
117
|
+
const serializedTransaction = JSON.stringify(transaction, (_, value) =>
|
|
118
|
+
typeof value === 'bigint' ? value.toString() : value
|
|
119
|
+
)
|
|
120
|
+
window.localStorage.setItem(storageKey, serializedTransaction)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getTransaction(
|
|
124
|
+
storageKey: string,
|
|
125
|
+
options?: {
|
|
126
|
+
remove?: boolean
|
|
127
|
+
}
|
|
128
|
+
): EVMUnsignedTransaction | undefined {
|
|
129
|
+
const txSerialized = window.localStorage.getItem(storageKey)
|
|
130
|
+
if (options?.remove) {
|
|
131
|
+
window.localStorage.removeItem(storageKey)
|
|
132
|
+
}
|
|
133
|
+
return txSerialized ? JSON.parse(txSerialized) : undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getMPCPayloadAndTransaction(
|
|
137
|
+
transactionRequest: EVMTransactionRequest
|
|
138
|
+
): Promise<{
|
|
139
|
+
transaction: EVMUnsignedTransaction
|
|
140
|
+
mpcPayloads: MPCPayloads
|
|
141
|
+
}> {
|
|
142
|
+
const transaction = await this.attachGasAndNonce(transactionRequest)
|
|
143
|
+
const txSerialized = ethers.Transaction.from(transaction).unsignedSerialized
|
|
144
|
+
const transactionHash = keccak256(txSerialized)
|
|
145
|
+
const txHash = Array.from(ethers.getBytes(transactionHash))
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
transaction,
|
|
149
|
+
mpcPayloads: [
|
|
150
|
+
{
|
|
151
|
+
index: 0,
|
|
152
|
+
payload: txHash,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
addSignature({
|
|
159
|
+
transaction,
|
|
160
|
+
mpcSignatures,
|
|
161
|
+
}: {
|
|
162
|
+
transaction: EVMUnsignedTransaction
|
|
163
|
+
mpcSignatures: RSVSignature[]
|
|
164
|
+
}): string {
|
|
165
|
+
return ethers.Transaction.from({
|
|
166
|
+
...transaction,
|
|
167
|
+
signature: this.parseSignature(mpcSignatures[0]),
|
|
168
|
+
}).serialized
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async broadcastTx(txSerialized: string): Promise<string> {
|
|
172
|
+
try {
|
|
173
|
+
const txResponse = await this.provider.broadcastTransaction(txSerialized)
|
|
174
|
+
return txResponse.hash
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('Transaction broadcast failed:', error)
|
|
177
|
+
throw new Error('Failed to broadcast transaction.')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
|
+
|
|
3
|
+
export async function fetchEVMFeeProperties(
|
|
4
|
+
providerUrl: string,
|
|
5
|
+
transaction: ethers.TransactionLike
|
|
6
|
+
): Promise<{
|
|
7
|
+
gasLimit: bigint
|
|
8
|
+
maxFeePerGas: bigint
|
|
9
|
+
maxPriorityFeePerGas: bigint
|
|
10
|
+
maxFee: bigint
|
|
11
|
+
}> {
|
|
12
|
+
const provider = new ethers.JsonRpcProvider(providerUrl)
|
|
13
|
+
const gasLimit = await provider.estimateGas(transaction)
|
|
14
|
+
const feeData = await provider.getFeeData()
|
|
15
|
+
|
|
16
|
+
const maxFeePerGas = feeData.maxFeePerGas ?? ethers.parseUnits('10', 'gwei')
|
|
17
|
+
const maxPriorityFeePerGas =
|
|
18
|
+
feeData.maxPriorityFeePerGas ?? ethers.parseUnits('10', 'gwei')
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
gasLimit,
|
|
22
|
+
maxFeePerGas,
|
|
23
|
+
maxPriorityFeePerGas,
|
|
24
|
+
maxFee: maxFeePerGas * gasLimit,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { Chain } from './Chain'
|
|
2
|
+
export { ChainSignatureContract, type SignArgs } from './ChainSignatureContract'
|
|
3
|
+
export * from './types'
|
|
4
|
+
export * as utils from './utils'
|
|
5
|
+
|
|
6
|
+
// EVM
|
|
7
|
+
export { EVM } from './EVM/EVM'
|
|
8
|
+
|
|
9
|
+
export { fetchEVMFeeProperties } from './EVM/utils'
|
|
10
|
+
|
|
11
|
+
export type { EVMTransactionRequest, EVMUnsignedTransaction } from './EVM/types'
|
|
12
|
+
|
|
13
|
+
// Bitcoin
|
|
14
|
+
export { Bitcoin } from './Bitcoin/Bitcoin'
|
|
15
|
+
|
|
16
|
+
export { BTCRpcAdapters, BTCRpcAdapter } from './Bitcoin/BTCRpcAdapter'
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
BTCTransactionRequest,
|
|
20
|
+
BTCUnsignedTransaction,
|
|
21
|
+
BTCTransaction,
|
|
22
|
+
BTCOutput,
|
|
23
|
+
BTCInput,
|
|
24
|
+
BTCNetworkIds,
|
|
25
|
+
} from './Bitcoin/types'
|
|
26
|
+
|
|
27
|
+
// Cosmos
|
|
28
|
+
export { Cosmos } from './Cosmos/Cosmos'
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
CosmosNetworkIds,
|
|
32
|
+
CosmosTransactionRequest,
|
|
33
|
+
CosmosUnsignedTransaction,
|
|
34
|
+
} from './Cosmos/types'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SignArgs } from '@chains/ChainSignatureContract'
|
|
2
|
+
|
|
3
|
+
interface SuccessResponse {
|
|
4
|
+
transactionHash: string
|
|
5
|
+
success: true
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface FailureResponse {
|
|
9
|
+
success: false
|
|
10
|
+
errorMessage: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Response = SuccessResponse | FailureResponse
|
|
14
|
+
|
|
15
|
+
export type MPCPayloads = Array<{ index: number; payload: SignArgs['payload'] }>
|
|
16
|
+
|
|
17
|
+
export type UncompressedPubKeySEC1 = `04${string}`
|
|
18
|
+
|
|
19
|
+
export type KeyDerivationPath = string
|
|
20
|
+
|
|
21
|
+
export interface RSVSignature {
|
|
22
|
+
r: string
|
|
23
|
+
s: string
|
|
24
|
+
v: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MPCSignature {
|
|
28
|
+
big_r: {
|
|
29
|
+
affine_point: string
|
|
30
|
+
}
|
|
31
|
+
s: {
|
|
32
|
+
scalar: string
|
|
33
|
+
}
|
|
34
|
+
recovery_id: number
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MPCSignature,
|
|
3
|
+
type RSVSignature,
|
|
4
|
+
type UncompressedPubKeySEC1,
|
|
5
|
+
} from '@chains/types'
|
|
6
|
+
|
|
7
|
+
export const toRSV = (signature: MPCSignature): RSVSignature => {
|
|
8
|
+
return {
|
|
9
|
+
r: signature.big_r.affine_point.substring(2),
|
|
10
|
+
s: signature.s.scalar,
|
|
11
|
+
v: signature.recovery_id,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compresses an uncompressed public key to its compressed format following SEC1 standards.
|
|
17
|
+
* In SEC1, a compressed public key consists of a prefix (02 or 03) followed by the x-coordinate.
|
|
18
|
+
* The prefix indicates whether the y-coordinate is even (02) or odd (03).
|
|
19
|
+
*
|
|
20
|
+
* @param uncompressedPubKeySEC1 - The uncompressed public key in hex format, with or without '04' prefix
|
|
21
|
+
* @returns The compressed public key in hex format
|
|
22
|
+
* @throws Error if the uncompressed public key length is invalid
|
|
23
|
+
*/
|
|
24
|
+
export const compressPubKey = (
|
|
25
|
+
uncompressedPubKeySEC1: UncompressedPubKeySEC1
|
|
26
|
+
): string => {
|
|
27
|
+
const slicedPubKey = uncompressedPubKeySEC1.slice(2)
|
|
28
|
+
|
|
29
|
+
if (slicedPubKey.length !== 128) {
|
|
30
|
+
throw new Error('Invalid uncompressed public key length')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const x = slicedPubKey.slice(0, 64)
|
|
34
|
+
const y = slicedPubKey.slice(64)
|
|
35
|
+
|
|
36
|
+
const isEven = parseInt(y.slice(-1), 16) % 2 === 0
|
|
37
|
+
const prefix = isEven ? '02' : '03'
|
|
38
|
+
|
|
39
|
+
return prefix + x
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as near from './near'
|