@atomiqlabs/chain-solana 12.0.13 → 12.0.15

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 (114) hide show
  1. package/LICENSE +201 -201
  2. package/dist/index.d.ts +29 -29
  3. package/dist/index.js +45 -45
  4. package/dist/solana/SolanaChainType.d.ts +11 -11
  5. package/dist/solana/SolanaChainType.js +2 -2
  6. package/dist/solana/SolanaChains.d.ts +20 -20
  7. package/dist/solana/SolanaChains.js +25 -25
  8. package/dist/solana/SolanaInitializer.d.ts +18 -18
  9. package/dist/solana/SolanaInitializer.js +63 -63
  10. package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +228 -228
  11. package/dist/solana/btcrelay/SolanaBtcRelay.js +441 -441
  12. package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +29 -29
  13. package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +34 -34
  14. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +46 -46
  15. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +78 -78
  16. package/dist/solana/btcrelay/program/programIdl.json +671 -671
  17. package/dist/solana/chain/SolanaAction.d.ts +26 -26
  18. package/dist/solana/chain/SolanaAction.js +86 -86
  19. package/dist/solana/chain/SolanaChainInterface.d.ts +65 -65
  20. package/dist/solana/chain/SolanaChainInterface.js +125 -125
  21. package/dist/solana/chain/SolanaModule.d.ts +14 -14
  22. package/dist/solana/chain/SolanaModule.js +13 -13
  23. package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
  24. package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
  25. package/dist/solana/chain/modules/SolanaBlocks.d.ts +28 -28
  26. package/dist/solana/chain/modules/SolanaBlocks.js +72 -72
  27. package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
  28. package/dist/solana/chain/modules/SolanaEvents.js +238 -225
  29. package/dist/solana/chain/modules/SolanaFees.d.ts +121 -121
  30. package/dist/solana/chain/modules/SolanaFees.js +379 -379
  31. package/dist/solana/chain/modules/SolanaSignatures.d.ts +23 -23
  32. package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
  33. package/dist/solana/chain/modules/SolanaSlots.d.ts +31 -31
  34. package/dist/solana/chain/modules/SolanaSlots.js +68 -68
  35. package/dist/solana/chain/modules/SolanaTokens.d.ts +136 -136
  36. package/dist/solana/chain/modules/SolanaTokens.js +248 -248
  37. package/dist/solana/chain/modules/SolanaTransactions.d.ts +124 -124
  38. package/dist/solana/chain/modules/SolanaTransactions.js +323 -323
  39. package/dist/solana/events/SolanaChainEvents.d.ts +88 -88
  40. package/dist/solana/events/SolanaChainEvents.js +256 -256
  41. package/dist/solana/events/SolanaChainEventsBrowser.d.ts +75 -75
  42. package/dist/solana/events/SolanaChainEventsBrowser.js +172 -172
  43. package/dist/solana/program/SolanaProgramBase.d.ts +40 -40
  44. package/dist/solana/program/SolanaProgramBase.js +43 -43
  45. package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
  46. package/dist/solana/program/SolanaProgramModule.js +11 -11
  47. package/dist/solana/program/modules/SolanaProgramEvents.d.ts +53 -53
  48. package/dist/solana/program/modules/SolanaProgramEvents.js +114 -114
  49. package/dist/solana/swaps/SolanaSwapData.d.ts +71 -71
  50. package/dist/solana/swaps/SolanaSwapData.js +292 -292
  51. package/dist/solana/swaps/SolanaSwapModule.d.ts +10 -10
  52. package/dist/solana/swaps/SolanaSwapModule.js +11 -11
  53. package/dist/solana/swaps/SolanaSwapProgram.d.ts +224 -224
  54. package/dist/solana/swaps/SolanaSwapProgram.js +570 -570
  55. package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
  56. package/dist/solana/swaps/SwapTypeEnum.js +42 -42
  57. package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +94 -94
  58. package/dist/solana/swaps/modules/SolanaDataAccount.js +231 -231
  59. package/dist/solana/swaps/modules/SolanaLpVault.d.ts +71 -71
  60. package/dist/solana/swaps/modules/SolanaLpVault.js +173 -173
  61. package/dist/solana/swaps/modules/SwapClaim.d.ts +129 -129
  62. package/dist/solana/swaps/modules/SwapClaim.js +291 -291
  63. package/dist/solana/swaps/modules/SwapInit.d.ts +217 -217
  64. package/dist/solana/swaps/modules/SwapInit.js +519 -519
  65. package/dist/solana/swaps/modules/SwapRefund.d.ts +82 -82
  66. package/dist/solana/swaps/modules/SwapRefund.js +262 -262
  67. package/dist/solana/swaps/programIdl.json +945 -945
  68. package/dist/solana/swaps/programTypes.d.ts +943 -943
  69. package/dist/solana/swaps/programTypes.js +945 -945
  70. package/dist/solana/wallet/SolanaKeypairWallet.d.ts +9 -9
  71. package/dist/solana/wallet/SolanaKeypairWallet.js +33 -33
  72. package/dist/solana/wallet/SolanaSigner.d.ts +11 -11
  73. package/dist/solana/wallet/SolanaSigner.js +17 -17
  74. package/dist/utils/Utils.d.ts +53 -53
  75. package/dist/utils/Utils.js +170 -170
  76. package/package.json +41 -41
  77. package/src/index.ts +36 -36
  78. package/src/solana/SolanaChainType.ts +27 -27
  79. package/src/solana/SolanaChains.ts +23 -23
  80. package/src/solana/SolanaInitializer.ts +102 -102
  81. package/src/solana/btcrelay/SolanaBtcRelay.ts +589 -589
  82. package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +57 -57
  83. package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +102 -102
  84. package/src/solana/btcrelay/program/programIdl.json +670 -670
  85. package/src/solana/chain/SolanaAction.ts +108 -108
  86. package/src/solana/chain/SolanaChainInterface.ts +192 -192
  87. package/src/solana/chain/SolanaModule.ts +20 -20
  88. package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
  89. package/src/solana/chain/modules/SolanaBlocks.ts +78 -78
  90. package/src/solana/chain/modules/SolanaEvents.ts +270 -256
  91. package/src/solana/chain/modules/SolanaFees.ts +450 -450
  92. package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
  93. package/src/solana/chain/modules/SolanaSlots.ts +82 -82
  94. package/src/solana/chain/modules/SolanaTokens.ts +307 -307
  95. package/src/solana/chain/modules/SolanaTransactions.ts +365 -365
  96. package/src/solana/events/SolanaChainEvents.ts +299 -299
  97. package/src/solana/events/SolanaChainEventsBrowser.ts +209 -209
  98. package/src/solana/program/SolanaProgramBase.ts +79 -79
  99. package/src/solana/program/SolanaProgramModule.ts +15 -15
  100. package/src/solana/program/modules/SolanaProgramEvents.ts +155 -155
  101. package/src/solana/swaps/SolanaSwapData.ts +430 -430
  102. package/src/solana/swaps/SolanaSwapModule.ts +16 -16
  103. package/src/solana/swaps/SolanaSwapProgram.ts +854 -854
  104. package/src/solana/swaps/SwapTypeEnum.ts +29 -29
  105. package/src/solana/swaps/modules/SolanaDataAccount.ts +307 -307
  106. package/src/solana/swaps/modules/SolanaLpVault.ts +215 -215
  107. package/src/solana/swaps/modules/SwapClaim.ts +389 -389
  108. package/src/solana/swaps/modules/SwapInit.ts +663 -663
  109. package/src/solana/swaps/modules/SwapRefund.ts +323 -323
  110. package/src/solana/swaps/programIdl.json +944 -944
  111. package/src/solana/swaps/programTypes.ts +1885 -1885
  112. package/src/solana/wallet/SolanaKeypairWallet.ts +36 -36
  113. package/src/solana/wallet/SolanaSigner.ts +24 -24
  114. package/src/utils/Utils.ts +180 -180
@@ -1,589 +1,589 @@
1
- import {
2
- Connection,
3
- PublicKey,
4
- Signer,
5
- SystemProgram,
6
- Transaction
7
- } from "@solana/web3.js";
8
- import {SolanaBtcStoredHeader, SolanaBtcStoredHeaderType} from "./headers/SolanaBtcStoredHeader";
9
- import {SolanaBtcHeader} from "./headers/SolanaBtcHeader";
10
- import * as programIdl from "./program/programIdl.json";
11
- import {BitcoinRpc, BtcBlock, BtcRelay, StatePredictorUtils} from "@atomiqlabs/base";
12
- import {MethodsBuilder} from "@coral-xyz/anchor/dist/cjs/program/namespace/methods";
13
- import {SolanaProgramBase} from "../program/SolanaProgramBase";
14
- import {SolanaAction} from "../chain/SolanaAction";
15
- import {Buffer} from "buffer";
16
- import {SolanaTx} from "../chain/modules/SolanaTransactions";
17
- import {SolanaSigner} from "../wallet/SolanaSigner";
18
- import * as BN from "bn.js";
19
- import {SolanaChainInterface} from "../chain/SolanaChainInterface";
20
- import {getLogger} from "../../utils/Utils";
21
-
22
- const MAX_CLOSE_IX_PER_TX = 10;
23
-
24
- function serializeBlockHeader(e: BtcBlock): SolanaBtcHeader {
25
- return new SolanaBtcHeader({
26
- version: e.getVersion(),
27
- reversedPrevBlockhash: [...Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
28
- merkleRoot: [...Buffer.from(e.getMerkleRoot(), "hex").reverse()],
29
- timestamp: e.getTimestamp(),
30
- nbits: e.getNbits(),
31
- nonce: e.getNonce(),
32
- hash: Buffer.from(e.getHash(), "hex").reverse()
33
- });
34
- };
35
-
36
- export class SolanaBtcRelay<B extends BtcBlock> extends SolanaProgramBase<any> implements BtcRelay<SolanaBtcStoredHeader, {tx: Transaction, signers: Signer[]}, B, SolanaSigner> {
37
-
38
- /**
39
- * Creates initialization action for initializing the btc relay
40
- *
41
- * @param signer
42
- * @param header
43
- * @param epochStart
44
- * @param pastBlocksTimestamps
45
- * @constructor
46
- * @private
47
- */
48
- private async Initialize(signer: PublicKey, header: B, epochStart: number, pastBlocksTimestamps: number[]): Promise<SolanaAction> {
49
- const serializedBlock = serializeBlockHeader(header);
50
- return new SolanaAction(signer, this.Chain,
51
- await this.program.methods
52
- .initialize(
53
- serializedBlock,
54
- header.getHeight(),
55
- header.getChainWork(),
56
- epochStart,
57
- pastBlocksTimestamps
58
- )
59
- .accounts({
60
- signer,
61
- mainState: this.BtcRelayMainState,
62
- headerTopic: this.BtcRelayHeader(serializedBlock.hash),
63
- systemProgram: SystemProgram.programId
64
- })
65
- .instruction(),
66
- 100_000
67
- )
68
- }
69
-
70
- /**
71
- * Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
72
- * such that the verify instruction will always be the 0th in the transaction, this is required because
73
- * swap program expects the verify instruction to be at the 0th position
74
- *
75
- * @param signer
76
- * @param reversedTxId
77
- * @param confirmations
78
- * @param position
79
- * @param reversedMerkleProof
80
- * @param committedHeader
81
- */
82
- public async Verify(
83
- signer: PublicKey,
84
- reversedTxId: Buffer,
85
- confirmations: number,
86
- position: number,
87
- reversedMerkleProof: Buffer[],
88
- committedHeader: SolanaBtcStoredHeader
89
- ): Promise<SolanaAction> {
90
- return new SolanaAction(signer, this.Chain,
91
- await this.program.methods
92
- .verifyTransaction(
93
- reversedTxId,
94
- confirmations,
95
- position,
96
- reversedMerkleProof,
97
- committedHeader
98
- )
99
- .accounts({
100
- signer,
101
- mainState: this.BtcRelayMainState
102
- })
103
- .instruction(),
104
- null,
105
- null,
106
- null,
107
- true
108
- );
109
- }
110
-
111
- public async CloseForkAccount(signer: PublicKey, forkId: number): Promise<SolanaAction> {
112
- return new SolanaAction(signer, this.Chain,
113
- await this.program.methods
114
- .closeForkAccount(
115
- new BN(forkId)
116
- )
117
- .accounts({
118
- signer,
119
- forkState: this.BtcRelayFork(forkId, signer),
120
- systemProgram: SystemProgram.programId,
121
- })
122
- .instruction(),
123
- 20000
124
- )
125
- }
126
-
127
- BtcRelayMainState = this.pda("state");
128
- BtcRelayHeader = this.pda("header", (hash: Buffer) => [hash]);
129
- BtcRelayFork = this.pda("fork",
130
- (forkId: number, pubkey: PublicKey) => [new BN(forkId).toArrayLike(Buffer, "le", 8), pubkey.toBuffer()]
131
- );
132
-
133
- bitcoinRpc: BitcoinRpc<B>;
134
-
135
- readonly maxHeadersPerTx: number = 5;
136
- readonly maxForkHeadersPerTx: number = 4;
137
- readonly maxShortForkHeadersPerTx: number = 4;
138
-
139
- constructor(
140
- chainInterface: SolanaChainInterface,
141
- bitcoinRpc: BitcoinRpc<B>,
142
- programAddress?: string
143
- ) {
144
- super(chainInterface, programIdl, programAddress);
145
- this.bitcoinRpc = bitcoinRpc;
146
- }
147
-
148
- /**
149
- * Gets set of block commitments representing current main chain from the mainState
150
- *
151
- * @param mainState
152
- * @private
153
- */
154
- private getBlockCommitmentsSet(mainState: any): Set<string> {
155
- const storedCommitments = new Set<string>();
156
- mainState.blockCommitments.forEach(e => {
157
- storedCommitments.add(Buffer.from(e).toString("hex"));
158
- });
159
- return storedCommitments;
160
- }
161
-
162
- /**
163
- * Computes subsequent commited headers as they will appear on the blockchain when transactions
164
- * are submitted & confirmed
165
- *
166
- * @param initialStoredHeader
167
- * @param syncedHeaders
168
- * @private
169
- */
170
- private computeCommitedHeaders(initialStoredHeader: SolanaBtcStoredHeader, syncedHeaders: SolanaBtcHeader[]) {
171
- const computedCommitedHeaders = [initialStoredHeader];
172
- for(let blockHeader of syncedHeaders) {
173
- computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
174
- }
175
- return computedCommitedHeaders;
176
- }
177
-
178
- /**
179
- * A common logic for submitting blockheaders in a transaction
180
- *
181
- * @param signer
182
- * @param headers headers to sync to the btc relay
183
- * @param storedHeader current latest stored block header for a given fork
184
- * @param tipWork work of the current tip in a given fork
185
- * @param forkId forkId to submit to, forkId=0 means main chain
186
- * @param feeRate feeRate for the transaction
187
- * @param createTx transaction generator function
188
- * @private
189
- */
190
- private async _saveHeaders(
191
- signer: PublicKey,
192
- headers: BtcBlock[],
193
- storedHeader: SolanaBtcStoredHeader,
194
- tipWork: Buffer,
195
- forkId: number,
196
- feeRate: string,
197
- createTx: (blockHeaders: SolanaBtcHeader[]) => MethodsBuilder<any, any>
198
- ) {
199
- const blockHeaderObj = headers.map(serializeBlockHeader);
200
-
201
- const tx = await createTx(blockHeaderObj)
202
- .remainingAccounts(blockHeaderObj.map(e => {
203
- return {
204
- pubkey: this.BtcRelayHeader(e.hash),
205
- isSigner: false,
206
- isWritable: false
207
- }
208
- }))
209
- .transaction();
210
- tx.feePayer = signer;
211
-
212
- this.Chain.Fees.applyFeeRateBegin(tx, null, feeRate);
213
- this.Chain.Fees.applyFeeRateEnd(tx, null, feeRate);
214
-
215
- const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
216
- const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
217
- if(forkId!==0 && StatePredictorUtils.gtBuffer(Buffer.from(lastStoredHeader.chainWork), tipWork)) {
218
- //Fork's work is higher than main chain's work, this fork will become a main chain
219
- forkId = 0;
220
- }
221
-
222
- return {
223
- forkId: forkId,
224
- lastStoredHeader,
225
- tx: {
226
- tx,
227
- signers: []
228
- },
229
- computedCommitedHeaders
230
- }
231
- }
232
-
233
- /**
234
- * Returns data about current main chain tip stored in the btc relay
235
- */
236
- public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
237
- const data: any = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
238
- if(data==null) return null;
239
-
240
- return {
241
- blockheight: data.blockHeight,
242
- commitHash: Buffer.from(data.tipCommitHash).toString("hex"),
243
- blockhash: Buffer.from(data.tipBlockHash).reverse().toString("hex"),
244
- chainWork: Buffer.from(data.chainWork)
245
- }
246
- }
247
-
248
- /**
249
- * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
250
- * btc relay contract is not synced up to the desired blockheight
251
- *
252
- * @param blockData
253
- * @param requiredBlockheight
254
- */
255
- public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
256
- header: SolanaBtcStoredHeader,
257
- height: number
258
- } | null> {
259
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
260
-
261
- if(requiredBlockheight!=null && mainState.blockHeight < requiredBlockheight) {
262
- return null;
263
- }
264
-
265
- const storedCommitments = this.getBlockCommitmentsSet(mainState);
266
- const blockHashBuffer = Buffer.from(blockData.blockhash, 'hex').reverse();
267
- const topicKey = this.BtcRelayHeader(blockHashBuffer);
268
-
269
- const data = await this.Events.findInEvents(topicKey, async (event) => {
270
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
271
- const eventData: any = event.data;
272
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
273
- if(blockHashBuffer.equals(Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
274
- return {
275
- header: new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType),
276
- height: mainState.blockHeight as number,
277
- commitHash
278
- };
279
- }
280
- });
281
- if(data!=null) this.logger.debug("retrieveLogAndBlockheight(): block found," +
282
- " commit hash: "+data.commitHash+" blockhash: "+blockData.blockhash+" height: "+data.height);
283
-
284
- return data;
285
- }
286
-
287
- /**
288
- * Retrieves blockheader data by blockheader's commit hash,
289
- *
290
- * @param commitmentHashStr
291
- * @param blockData
292
- */
293
- public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<SolanaBtcStoredHeader> {
294
- const blockHashBuffer = Buffer.from(blockData.blockhash, "hex").reverse();
295
- const topicKey = this.BtcRelayHeader(blockHashBuffer);
296
-
297
- const data = await this.Events.findInEvents(topicKey, async (event) => {
298
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
299
- const eventData: any = event.data;
300
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
301
- if(commitmentHashStr===commitHash)
302
- return new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType);
303
- }
304
- });
305
- if(data!=null) this.logger.debug("retrieveLogByCommitHash(): block found," +
306
- " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+data.blockheight);
307
-
308
- return data;
309
- }
310
-
311
- /**
312
- * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
313
- */
314
- public async retrieveLatestKnownBlockLog(): Promise<{
315
- resultStoredHeader: SolanaBtcStoredHeader,
316
- resultBitcoinHeader: B
317
- }> {
318
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
319
- const storedCommitments = this.getBlockCommitmentsSet(mainState);
320
-
321
- const data = await this.Events.findInEvents(this.program.programId, async (event) => {
322
- if(event.name==="StoreFork" || event.name==="StoreHeader") {
323
- const eventData: any = event.data;
324
- const blockHashHex = Buffer.from(eventData.blockHash).reverse().toString("hex");
325
- const isInMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
326
- const commitHash = Buffer.from(eventData.commitHash).toString("hex");
327
- //Check if this fork is part of main chain
328
- if(isInMainChain && storedCommitments.has(commitHash))
329
- return {
330
- resultStoredHeader: new SolanaBtcStoredHeader(eventData.header),
331
- resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
332
- commitHash: commitHash
333
- };
334
- }
335
- }, null, 10);
336
- if(data!=null) this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
337
- " commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
338
- " height: "+data.resultStoredHeader.blockheight);
339
-
340
- return data;
341
- }
342
-
343
- /**
344
- * Saves initial block header when the btc relay is in uninitialized state
345
- *
346
- * @param signer
347
- * @param header a bitcoin blockheader to submit
348
- * @param epochStart timestamp of the start of the epoch (block timestamp at blockheight-(blockheight%2016))
349
- * @param pastBlocksTimestamps timestamp of the past 10 blocks
350
- * @param feeRate fee rate to use for the transaction
351
- */
352
- async saveInitialHeader(
353
- signer: string,
354
- header: B,
355
- epochStart: number,
356
- pastBlocksTimestamps: number[],
357
- feeRate?: string
358
- ): Promise<{ tx: Transaction; signers: Signer[]; }> {
359
- if(pastBlocksTimestamps.length!==10) throw new Error("Invalid prevBlocksTimestamps");
360
-
361
- const action = await this.Initialize(new PublicKey(signer), header, epochStart, pastBlocksTimestamps);
362
-
363
- this.logger.debug("saveInitialHeader(): saving initial header, blockhash: "+header.getHash()+
364
- " blockheight: "+header.getHeight()+" epochStart: "+epochStart+" past block timestamps: "+pastBlocksTimestamps.join());
365
-
366
- return await action.tx(feeRate);
367
- }
368
-
369
- /**
370
- * Saves blockheaders as a bitcoin main chain to the btc relay
371
- *
372
- * @param signer
373
- * @param mainHeaders
374
- * @param storedHeader
375
- * @param feeRate
376
- */
377
- public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, feeRate?: string) {
378
- this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
379
- const _signer = new PublicKey(signer);
380
- return this._saveHeaders(_signer, mainHeaders, storedHeader, null, 0, feeRate,
381
- (blockHeaders) => this.program.methods
382
- .submitBlockHeaders(
383
- blockHeaders,
384
- storedHeader
385
- )
386
- .accounts({
387
- signer: _signer,
388
- mainState: this.BtcRelayMainState,
389
- })
390
- );
391
- }
392
-
393
- /**
394
- * Creates a new long fork and submits the headers to it
395
- *
396
- * @param signer
397
- * @param forkHeaders
398
- * @param storedHeader
399
- * @param tipWork
400
- * @param feeRate
401
- */
402
- public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
403
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
404
- let forkId: BN = mainState.forkCounter;
405
-
406
- const _signer = new PublicKey(signer);
407
-
408
- this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
409
- " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
410
- return await this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId.toNumber(), feeRate,
411
- (blockHeaders) => this.program.methods
412
- .submitForkHeaders(
413
- blockHeaders,
414
- storedHeader,
415
- forkId,
416
- true
417
- )
418
- .accounts({
419
- signer: _signer,
420
- mainState: this.BtcRelayMainState,
421
- forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
422
- systemProgram: SystemProgram.programId,
423
- })
424
- );
425
- }
426
-
427
- /**
428
- * Continues submitting blockheaders to a given fork
429
- *
430
- * @param signer
431
- * @param forkHeaders
432
- * @param storedHeader
433
- * @param forkId
434
- * @param tipWork
435
- * @param feeRate
436
- */
437
- public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
438
- this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
439
- " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
440
-
441
- const _signer = new PublicKey(signer);
442
-
443
- return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId, feeRate,
444
- (blockHeaders) => this.program.methods
445
- .submitForkHeaders(
446
- blockHeaders,
447
- storedHeader,
448
- new BN(forkId),
449
- false
450
- )
451
- .accounts({
452
- signer: _signer,
453
- mainState: this.BtcRelayMainState,
454
- forkState: this.BtcRelayFork(forkId, _signer),
455
- systemProgram: SystemProgram.programId,
456
- })
457
- )
458
- }
459
-
460
- /**
461
- * Submits short fork with given blockheaders
462
- *
463
- * @param signer
464
- * @param forkHeaders
465
- * @param storedHeader
466
- * @param tipWork
467
- * @param feeRate
468
- */
469
- public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
470
- this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
471
- " count: "+forkHeaders.length);
472
-
473
- const _signer = new PublicKey(signer);
474
-
475
- return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, -1, feeRate,
476
- (blockHeaders) => this.program.methods
477
- .submitShortForkHeaders(
478
- blockHeaders,
479
- storedHeader
480
- )
481
- .accounts({
482
- signer: _signer,
483
- mainState: this.BtcRelayMainState
484
- })
485
- );
486
- }
487
-
488
- /**
489
- * Sweeps fork data PDAs back to self
490
- *
491
- * @param signer
492
- * @param lastSweepId lastCheckedId returned from the previous sweepForkData() call
493
- * @returns {number} lastCheckedId that should be passed to the next call of sweepForkData()
494
- */
495
- public async sweepForkData(signer: SolanaSigner, lastSweepId?: number): Promise<number | null> {
496
- const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
497
- let forkId: number = mainState.forkCounter.toNumber();
498
-
499
- const txs: SolanaTx[] = [];
500
- let action = new SolanaAction(signer.getPublicKey(), this.Chain);
501
-
502
- let lastCheckedId = lastSweepId;
503
- for(
504
- let i = lastSweepId==null ? 0 : lastSweepId+1;
505
- i<=forkId; i++
506
- ) {
507
- lastCheckedId = i;
508
-
509
- const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
510
- let forkState: any = await this.program.account.forkState.fetchNullable(accountAddr);
511
- if(forkState==null) continue;
512
-
513
- this.logger.info("sweepForkData(): sweeping forkId: "+i);
514
- action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
515
-
516
- if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
517
- await action.addToTxs(txs);
518
- action = new SolanaAction(signer.getPublicKey(), this.Chain);
519
- }
520
- }
521
-
522
- if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
523
- await action.addToTxs(txs);
524
- }
525
-
526
- if(txs.length>0) {
527
- const signatures = await this.Chain.sendAndConfirm(signer, txs, true);
528
- this.logger.info("sweepForkData(): forks swept, signatures: "+signatures.join());
529
- }
530
-
531
- return lastCheckedId;
532
- }
533
-
534
- /**
535
- * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
536
- *
537
- * @param requiredBlockheight
538
- * @param feeRate
539
- */
540
- public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
541
- const tipData = await this.getTipData();
542
- const currBlockheight = tipData.blockheight;
543
-
544
- const blockheightDelta = requiredBlockheight-currBlockheight;
545
-
546
- if(blockheightDelta<=0) return 0n;
547
-
548
- const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
549
- this.logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
550
- " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
551
-
552
- return synchronizationFee;
553
- }
554
-
555
- /**
556
- * Returns fee required (in SOL) to synchronize a single block to btc relay
557
- *
558
- * @param feeRate
559
- */
560
- public async getFeePerBlock(feeRate?: string): Promise<bigint> {
561
- // feeRate = feeRate || await this.getMainFeeRate(null);
562
- // return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
563
- return 50000n;
564
- }
565
-
566
- /**
567
- * Gets fee rate required for submitting blockheaders to the main chain
568
- */
569
- public getMainFeeRate(signer: string | null): Promise<string> {
570
- const _signer = signer==null ? null : new PublicKey(signer);
571
- return this.Chain.Fees.getFeeRate(_signer==null ? [this.BtcRelayMainState] : [
572
- _signer,
573
- this.BtcRelayMainState
574
- ]);
575
- }
576
-
577
- /**
578
- * Gets fee rate required for submitting blockheaders to the specific fork
579
- */
580
- public getForkFeeRate(signer: string, forkId: number): Promise<string> {
581
- const _signer = new PublicKey(signer);
582
- return this.Chain.Fees.getFeeRate([
583
- _signer,
584
- this.BtcRelayMainState,
585
- this.BtcRelayFork(forkId, _signer)
586
- ]);
587
- }
588
-
589
- }
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ Signer,
5
+ SystemProgram,
6
+ Transaction
7
+ } from "@solana/web3.js";
8
+ import {SolanaBtcStoredHeader, SolanaBtcStoredHeaderType} from "./headers/SolanaBtcStoredHeader";
9
+ import {SolanaBtcHeader} from "./headers/SolanaBtcHeader";
10
+ import * as programIdl from "./program/programIdl.json";
11
+ import {BitcoinRpc, BtcBlock, BtcRelay, StatePredictorUtils} from "@atomiqlabs/base";
12
+ import {MethodsBuilder} from "@coral-xyz/anchor/dist/cjs/program/namespace/methods";
13
+ import {SolanaProgramBase} from "../program/SolanaProgramBase";
14
+ import {SolanaAction} from "../chain/SolanaAction";
15
+ import {Buffer} from "buffer";
16
+ import {SolanaTx} from "../chain/modules/SolanaTransactions";
17
+ import {SolanaSigner} from "../wallet/SolanaSigner";
18
+ import * as BN from "bn.js";
19
+ import {SolanaChainInterface} from "../chain/SolanaChainInterface";
20
+ import {getLogger} from "../../utils/Utils";
21
+
22
+ const MAX_CLOSE_IX_PER_TX = 10;
23
+
24
+ function serializeBlockHeader(e: BtcBlock): SolanaBtcHeader {
25
+ return new SolanaBtcHeader({
26
+ version: e.getVersion(),
27
+ reversedPrevBlockhash: [...Buffer.from(e.getPrevBlockhash(), "hex").reverse()],
28
+ merkleRoot: [...Buffer.from(e.getMerkleRoot(), "hex").reverse()],
29
+ timestamp: e.getTimestamp(),
30
+ nbits: e.getNbits(),
31
+ nonce: e.getNonce(),
32
+ hash: Buffer.from(e.getHash(), "hex").reverse()
33
+ });
34
+ };
35
+
36
+ export class SolanaBtcRelay<B extends BtcBlock> extends SolanaProgramBase<any> implements BtcRelay<SolanaBtcStoredHeader, {tx: Transaction, signers: Signer[]}, B, SolanaSigner> {
37
+
38
+ /**
39
+ * Creates initialization action for initializing the btc relay
40
+ *
41
+ * @param signer
42
+ * @param header
43
+ * @param epochStart
44
+ * @param pastBlocksTimestamps
45
+ * @constructor
46
+ * @private
47
+ */
48
+ private async Initialize(signer: PublicKey, header: B, epochStart: number, pastBlocksTimestamps: number[]): Promise<SolanaAction> {
49
+ const serializedBlock = serializeBlockHeader(header);
50
+ return new SolanaAction(signer, this.Chain,
51
+ await this.program.methods
52
+ .initialize(
53
+ serializedBlock,
54
+ header.getHeight(),
55
+ header.getChainWork(),
56
+ epochStart,
57
+ pastBlocksTimestamps
58
+ )
59
+ .accounts({
60
+ signer,
61
+ mainState: this.BtcRelayMainState,
62
+ headerTopic: this.BtcRelayHeader(serializedBlock.hash),
63
+ systemProgram: SystemProgram.programId
64
+ })
65
+ .instruction(),
66
+ 100_000
67
+ )
68
+ }
69
+
70
+ /**
71
+ * Creates verify action to be used with the swap program, specifies the action to be firstIxBeforeComputeBudget,
72
+ * such that the verify instruction will always be the 0th in the transaction, this is required because
73
+ * swap program expects the verify instruction to be at the 0th position
74
+ *
75
+ * @param signer
76
+ * @param reversedTxId
77
+ * @param confirmations
78
+ * @param position
79
+ * @param reversedMerkleProof
80
+ * @param committedHeader
81
+ */
82
+ public async Verify(
83
+ signer: PublicKey,
84
+ reversedTxId: Buffer,
85
+ confirmations: number,
86
+ position: number,
87
+ reversedMerkleProof: Buffer[],
88
+ committedHeader: SolanaBtcStoredHeader
89
+ ): Promise<SolanaAction> {
90
+ return new SolanaAction(signer, this.Chain,
91
+ await this.program.methods
92
+ .verifyTransaction(
93
+ reversedTxId,
94
+ confirmations,
95
+ position,
96
+ reversedMerkleProof,
97
+ committedHeader
98
+ )
99
+ .accounts({
100
+ signer,
101
+ mainState: this.BtcRelayMainState
102
+ })
103
+ .instruction(),
104
+ null,
105
+ null,
106
+ null,
107
+ true
108
+ );
109
+ }
110
+
111
+ public async CloseForkAccount(signer: PublicKey, forkId: number): Promise<SolanaAction> {
112
+ return new SolanaAction(signer, this.Chain,
113
+ await this.program.methods
114
+ .closeForkAccount(
115
+ new BN(forkId)
116
+ )
117
+ .accounts({
118
+ signer,
119
+ forkState: this.BtcRelayFork(forkId, signer),
120
+ systemProgram: SystemProgram.programId,
121
+ })
122
+ .instruction(),
123
+ 20000
124
+ )
125
+ }
126
+
127
+ BtcRelayMainState = this.pda("state");
128
+ BtcRelayHeader = this.pda("header", (hash: Buffer) => [hash]);
129
+ BtcRelayFork = this.pda("fork",
130
+ (forkId: number, pubkey: PublicKey) => [new BN(forkId).toArrayLike(Buffer, "le", 8), pubkey.toBuffer()]
131
+ );
132
+
133
+ bitcoinRpc: BitcoinRpc<B>;
134
+
135
+ readonly maxHeadersPerTx: number = 5;
136
+ readonly maxForkHeadersPerTx: number = 4;
137
+ readonly maxShortForkHeadersPerTx: number = 4;
138
+
139
+ constructor(
140
+ chainInterface: SolanaChainInterface,
141
+ bitcoinRpc: BitcoinRpc<B>,
142
+ programAddress?: string
143
+ ) {
144
+ super(chainInterface, programIdl, programAddress);
145
+ this.bitcoinRpc = bitcoinRpc;
146
+ }
147
+
148
+ /**
149
+ * Gets set of block commitments representing current main chain from the mainState
150
+ *
151
+ * @param mainState
152
+ * @private
153
+ */
154
+ private getBlockCommitmentsSet(mainState: any): Set<string> {
155
+ const storedCommitments = new Set<string>();
156
+ mainState.blockCommitments.forEach(e => {
157
+ storedCommitments.add(Buffer.from(e).toString("hex"));
158
+ });
159
+ return storedCommitments;
160
+ }
161
+
162
+ /**
163
+ * Computes subsequent commited headers as they will appear on the blockchain when transactions
164
+ * are submitted & confirmed
165
+ *
166
+ * @param initialStoredHeader
167
+ * @param syncedHeaders
168
+ * @private
169
+ */
170
+ private computeCommitedHeaders(initialStoredHeader: SolanaBtcStoredHeader, syncedHeaders: SolanaBtcHeader[]) {
171
+ const computedCommitedHeaders = [initialStoredHeader];
172
+ for(let blockHeader of syncedHeaders) {
173
+ computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
174
+ }
175
+ return computedCommitedHeaders;
176
+ }
177
+
178
+ /**
179
+ * A common logic for submitting blockheaders in a transaction
180
+ *
181
+ * @param signer
182
+ * @param headers headers to sync to the btc relay
183
+ * @param storedHeader current latest stored block header for a given fork
184
+ * @param tipWork work of the current tip in a given fork
185
+ * @param forkId forkId to submit to, forkId=0 means main chain
186
+ * @param feeRate feeRate for the transaction
187
+ * @param createTx transaction generator function
188
+ * @private
189
+ */
190
+ private async _saveHeaders(
191
+ signer: PublicKey,
192
+ headers: BtcBlock[],
193
+ storedHeader: SolanaBtcStoredHeader,
194
+ tipWork: Buffer,
195
+ forkId: number,
196
+ feeRate: string,
197
+ createTx: (blockHeaders: SolanaBtcHeader[]) => MethodsBuilder<any, any>
198
+ ) {
199
+ const blockHeaderObj = headers.map(serializeBlockHeader);
200
+
201
+ const tx = await createTx(blockHeaderObj)
202
+ .remainingAccounts(blockHeaderObj.map(e => {
203
+ return {
204
+ pubkey: this.BtcRelayHeader(e.hash),
205
+ isSigner: false,
206
+ isWritable: false
207
+ }
208
+ }))
209
+ .transaction();
210
+ tx.feePayer = signer;
211
+
212
+ this.Chain.Fees.applyFeeRateBegin(tx, null, feeRate);
213
+ this.Chain.Fees.applyFeeRateEnd(tx, null, feeRate);
214
+
215
+ const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
216
+ const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
217
+ if(forkId!==0 && StatePredictorUtils.gtBuffer(Buffer.from(lastStoredHeader.chainWork), tipWork)) {
218
+ //Fork's work is higher than main chain's work, this fork will become a main chain
219
+ forkId = 0;
220
+ }
221
+
222
+ return {
223
+ forkId: forkId,
224
+ lastStoredHeader,
225
+ tx: {
226
+ tx,
227
+ signers: []
228
+ },
229
+ computedCommitedHeaders
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Returns data about current main chain tip stored in the btc relay
235
+ */
236
+ public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
237
+ const data: any = await this.program.account.mainState.fetchNullable(this.BtcRelayMainState);
238
+ if(data==null) return null;
239
+
240
+ return {
241
+ blockheight: data.blockHeight,
242
+ commitHash: Buffer.from(data.tipCommitHash).toString("hex"),
243
+ blockhash: Buffer.from(data.tipBlockHash).reverse().toString("hex"),
244
+ chainWork: Buffer.from(data.chainWork)
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
250
+ * btc relay contract is not synced up to the desired blockheight
251
+ *
252
+ * @param blockData
253
+ * @param requiredBlockheight
254
+ */
255
+ public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
256
+ header: SolanaBtcStoredHeader,
257
+ height: number
258
+ } | null> {
259
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
260
+
261
+ if(requiredBlockheight!=null && mainState.blockHeight < requiredBlockheight) {
262
+ return null;
263
+ }
264
+
265
+ const storedCommitments = this.getBlockCommitmentsSet(mainState);
266
+ const blockHashBuffer = Buffer.from(blockData.blockhash, 'hex').reverse();
267
+ const topicKey = this.BtcRelayHeader(blockHashBuffer);
268
+
269
+ const data = await this.Events.findInEvents(topicKey, async (event) => {
270
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
271
+ const eventData: any = event.data;
272
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
273
+ if(blockHashBuffer.equals(Buffer.from(eventData.blockHash)) && storedCommitments.has(commitHash))
274
+ return {
275
+ header: new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType),
276
+ height: mainState.blockHeight as number,
277
+ commitHash
278
+ };
279
+ }
280
+ });
281
+ if(data!=null) this.logger.debug("retrieveLogAndBlockheight(): block found," +
282
+ " commit hash: "+data.commitHash+" blockhash: "+blockData.blockhash+" height: "+data.height);
283
+
284
+ return data;
285
+ }
286
+
287
+ /**
288
+ * Retrieves blockheader data by blockheader's commit hash,
289
+ *
290
+ * @param commitmentHashStr
291
+ * @param blockData
292
+ */
293
+ public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<SolanaBtcStoredHeader> {
294
+ const blockHashBuffer = Buffer.from(blockData.blockhash, "hex").reverse();
295
+ const topicKey = this.BtcRelayHeader(blockHashBuffer);
296
+
297
+ const data = await this.Events.findInEvents(topicKey, async (event) => {
298
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
299
+ const eventData: any = event.data;
300
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
301
+ if(commitmentHashStr===commitHash)
302
+ return new SolanaBtcStoredHeader(eventData.header as SolanaBtcStoredHeaderType);
303
+ }
304
+ });
305
+ if(data!=null) this.logger.debug("retrieveLogByCommitHash(): block found," +
306
+ " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+data.blockheight);
307
+
308
+ return data;
309
+ }
310
+
311
+ /**
312
+ * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
313
+ */
314
+ public async retrieveLatestKnownBlockLog(): Promise<{
315
+ resultStoredHeader: SolanaBtcStoredHeader,
316
+ resultBitcoinHeader: B
317
+ }> {
318
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
319
+ const storedCommitments = this.getBlockCommitmentsSet(mainState);
320
+
321
+ const data = await this.Events.findInEvents(this.program.programId, async (event) => {
322
+ if(event.name==="StoreFork" || event.name==="StoreHeader") {
323
+ const eventData: any = event.data;
324
+ const blockHashHex = Buffer.from(eventData.blockHash).reverse().toString("hex");
325
+ const isInMainChain = await this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false);
326
+ const commitHash = Buffer.from(eventData.commitHash).toString("hex");
327
+ //Check if this fork is part of main chain
328
+ if(isInMainChain && storedCommitments.has(commitHash))
329
+ return {
330
+ resultStoredHeader: new SolanaBtcStoredHeader(eventData.header),
331
+ resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
332
+ commitHash: commitHash
333
+ };
334
+ }
335
+ }, null, 10);
336
+ if(data!=null) this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
337
+ " commit hash: "+data.commitHash+" blockhash: "+data.resultBitcoinHeader.getHash()+
338
+ " height: "+data.resultStoredHeader.blockheight);
339
+
340
+ return data;
341
+ }
342
+
343
+ /**
344
+ * Saves initial block header when the btc relay is in uninitialized state
345
+ *
346
+ * @param signer
347
+ * @param header a bitcoin blockheader to submit
348
+ * @param epochStart timestamp of the start of the epoch (block timestamp at blockheight-(blockheight%2016))
349
+ * @param pastBlocksTimestamps timestamp of the past 10 blocks
350
+ * @param feeRate fee rate to use for the transaction
351
+ */
352
+ async saveInitialHeader(
353
+ signer: string,
354
+ header: B,
355
+ epochStart: number,
356
+ pastBlocksTimestamps: number[],
357
+ feeRate?: string
358
+ ): Promise<{ tx: Transaction; signers: Signer[]; }> {
359
+ if(pastBlocksTimestamps.length!==10) throw new Error("Invalid prevBlocksTimestamps");
360
+
361
+ const action = await this.Initialize(new PublicKey(signer), header, epochStart, pastBlocksTimestamps);
362
+
363
+ this.logger.debug("saveInitialHeader(): saving initial header, blockhash: "+header.getHash()+
364
+ " blockheight: "+header.getHeight()+" epochStart: "+epochStart+" past block timestamps: "+pastBlocksTimestamps.join());
365
+
366
+ return await action.tx(feeRate);
367
+ }
368
+
369
+ /**
370
+ * Saves blockheaders as a bitcoin main chain to the btc relay
371
+ *
372
+ * @param signer
373
+ * @param mainHeaders
374
+ * @param storedHeader
375
+ * @param feeRate
376
+ */
377
+ public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, feeRate?: string) {
378
+ this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
379
+ const _signer = new PublicKey(signer);
380
+ return this._saveHeaders(_signer, mainHeaders, storedHeader, null, 0, feeRate,
381
+ (blockHeaders) => this.program.methods
382
+ .submitBlockHeaders(
383
+ blockHeaders,
384
+ storedHeader
385
+ )
386
+ .accounts({
387
+ signer: _signer,
388
+ mainState: this.BtcRelayMainState,
389
+ })
390
+ );
391
+ }
392
+
393
+ /**
394
+ * Creates a new long fork and submits the headers to it
395
+ *
396
+ * @param signer
397
+ * @param forkHeaders
398
+ * @param storedHeader
399
+ * @param tipWork
400
+ * @param feeRate
401
+ */
402
+ public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
403
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
404
+ let forkId: BN = mainState.forkCounter;
405
+
406
+ const _signer = new PublicKey(signer);
407
+
408
+ this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
409
+ " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
410
+ return await this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId.toNumber(), feeRate,
411
+ (blockHeaders) => this.program.methods
412
+ .submitForkHeaders(
413
+ blockHeaders,
414
+ storedHeader,
415
+ forkId,
416
+ true
417
+ )
418
+ .accounts({
419
+ signer: _signer,
420
+ mainState: this.BtcRelayMainState,
421
+ forkState: this.BtcRelayFork(forkId.toNumber(), _signer),
422
+ systemProgram: SystemProgram.programId,
423
+ })
424
+ );
425
+ }
426
+
427
+ /**
428
+ * Continues submitting blockheaders to a given fork
429
+ *
430
+ * @param signer
431
+ * @param forkHeaders
432
+ * @param storedHeader
433
+ * @param forkId
434
+ * @param tipWork
435
+ * @param feeRate
436
+ */
437
+ public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
438
+ this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
439
+ " count: "+forkHeaders.length+" forkId: "+forkId.toString(10));
440
+
441
+ const _signer = new PublicKey(signer);
442
+
443
+ return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, forkId, feeRate,
444
+ (blockHeaders) => this.program.methods
445
+ .submitForkHeaders(
446
+ blockHeaders,
447
+ storedHeader,
448
+ new BN(forkId),
449
+ false
450
+ )
451
+ .accounts({
452
+ signer: _signer,
453
+ mainState: this.BtcRelayMainState,
454
+ forkState: this.BtcRelayFork(forkId, _signer),
455
+ systemProgram: SystemProgram.programId,
456
+ })
457
+ )
458
+ }
459
+
460
+ /**
461
+ * Submits short fork with given blockheaders
462
+ *
463
+ * @param signer
464
+ * @param forkHeaders
465
+ * @param storedHeader
466
+ * @param tipWork
467
+ * @param feeRate
468
+ */
469
+ public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: SolanaBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
470
+ this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
471
+ " count: "+forkHeaders.length);
472
+
473
+ const _signer = new PublicKey(signer);
474
+
475
+ return this._saveHeaders(_signer, forkHeaders, storedHeader, tipWork, -1, feeRate,
476
+ (blockHeaders) => this.program.methods
477
+ .submitShortForkHeaders(
478
+ blockHeaders,
479
+ storedHeader
480
+ )
481
+ .accounts({
482
+ signer: _signer,
483
+ mainState: this.BtcRelayMainState
484
+ })
485
+ );
486
+ }
487
+
488
+ /**
489
+ * Sweeps fork data PDAs back to self
490
+ *
491
+ * @param signer
492
+ * @param lastSweepId lastCheckedId returned from the previous sweepForkData() call
493
+ * @returns {number} lastCheckedId that should be passed to the next call of sweepForkData()
494
+ */
495
+ public async sweepForkData(signer: SolanaSigner, lastSweepId?: number): Promise<number | null> {
496
+ const mainState: any = await this.program.account.mainState.fetch(this.BtcRelayMainState);
497
+ let forkId: number = mainState.forkCounter.toNumber();
498
+
499
+ const txs: SolanaTx[] = [];
500
+ let action = new SolanaAction(signer.getPublicKey(), this.Chain);
501
+
502
+ let lastCheckedId = lastSweepId;
503
+ for(
504
+ let i = lastSweepId==null ? 0 : lastSweepId+1;
505
+ i<=forkId; i++
506
+ ) {
507
+ lastCheckedId = i;
508
+
509
+ const accountAddr = this.BtcRelayFork(i, signer.getPublicKey());
510
+ let forkState: any = await this.program.account.forkState.fetchNullable(accountAddr);
511
+ if(forkState==null) continue;
512
+
513
+ this.logger.info("sweepForkData(): sweeping forkId: "+i);
514
+ action.add(await this.CloseForkAccount(signer.getPublicKey(), i));
515
+
516
+ if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
517
+ await action.addToTxs(txs);
518
+ action = new SolanaAction(signer.getPublicKey(), this.Chain);
519
+ }
520
+ }
521
+
522
+ if(action.ixsLength()>=MAX_CLOSE_IX_PER_TX) {
523
+ await action.addToTxs(txs);
524
+ }
525
+
526
+ if(txs.length>0) {
527
+ const signatures = await this.Chain.sendAndConfirm(signer, txs, true);
528
+ this.logger.info("sweepForkData(): forks swept, signatures: "+signatures.join());
529
+ }
530
+
531
+ return lastCheckedId;
532
+ }
533
+
534
+ /**
535
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
536
+ *
537
+ * @param requiredBlockheight
538
+ * @param feeRate
539
+ */
540
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
541
+ const tipData = await this.getTipData();
542
+ const currBlockheight = tipData.blockheight;
543
+
544
+ const blockheightDelta = requiredBlockheight-currBlockheight;
545
+
546
+ if(blockheightDelta<=0) return 0n;
547
+
548
+ const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
549
+ this.logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
550
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
551
+
552
+ return synchronizationFee;
553
+ }
554
+
555
+ /**
556
+ * Returns fee required (in SOL) to synchronize a single block to btc relay
557
+ *
558
+ * @param feeRate
559
+ */
560
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
561
+ // feeRate = feeRate || await this.getMainFeeRate(null);
562
+ // return BASE_FEE_SOL_PER_BLOCKHEADER.add(this.Fees.getPriorityFee(200000, feeRate, false));
563
+ return 50000n;
564
+ }
565
+
566
+ /**
567
+ * Gets fee rate required for submitting blockheaders to the main chain
568
+ */
569
+ public getMainFeeRate(signer: string | null): Promise<string> {
570
+ const _signer = signer==null ? null : new PublicKey(signer);
571
+ return this.Chain.Fees.getFeeRate(_signer==null ? [this.BtcRelayMainState] : [
572
+ _signer,
573
+ this.BtcRelayMainState
574
+ ]);
575
+ }
576
+
577
+ /**
578
+ * Gets fee rate required for submitting blockheaders to the specific fork
579
+ */
580
+ public getForkFeeRate(signer: string, forkId: number): Promise<string> {
581
+ const _signer = new PublicKey(signer);
582
+ return this.Chain.Fees.getFeeRate([
583
+ _signer,
584
+ this.BtcRelayMainState,
585
+ this.BtcRelayFork(forkId, _signer)
586
+ ]);
587
+ }
588
+
589
+ }