@atomiqlabs/chain-starknet 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE +201 -0
  2. package/dist/get_serialized_block.d.ts +1 -0
  3. package/dist/get_serialized_block.js +28 -0
  4. package/dist/index.d.ts +34 -0
  5. package/dist/index.js +50 -0
  6. package/dist/starknet/StarknetChainType.d.ts +9 -0
  7. package/dist/starknet/StarknetChainType.js +2 -0
  8. package/dist/starknet/StarknetInitializer.d.ts +18 -0
  9. package/dist/starknet/StarknetInitializer.js +49 -0
  10. package/dist/starknet/base/StarknetAction.d.ts +27 -0
  11. package/dist/starknet/base/StarknetAction.js +73 -0
  12. package/dist/starknet/base/StarknetBase.d.ts +34 -0
  13. package/dist/starknet/base/StarknetBase.js +29 -0
  14. package/dist/starknet/base/StarknetModule.d.ts +14 -0
  15. package/dist/starknet/base/StarknetModule.js +13 -0
  16. package/dist/starknet/base/modules/ERC20Abi.d.ts +755 -0
  17. package/dist/starknet/base/modules/ERC20Abi.js +1032 -0
  18. package/dist/starknet/base/modules/StarknetAccounts.d.ts +6 -0
  19. package/dist/starknet/base/modules/StarknetAccounts.js +24 -0
  20. package/dist/starknet/base/modules/StarknetAddresses.d.ts +9 -0
  21. package/dist/starknet/base/modules/StarknetAddresses.js +26 -0
  22. package/dist/starknet/base/modules/StarknetBlocks.d.ts +19 -0
  23. package/dist/starknet/base/modules/StarknetBlocks.js +49 -0
  24. package/dist/starknet/base/modules/StarknetEvents.d.ts +44 -0
  25. package/dist/starknet/base/modules/StarknetEvents.js +88 -0
  26. package/dist/starknet/base/modules/StarknetFees.d.ts +55 -0
  27. package/dist/starknet/base/modules/StarknetFees.js +102 -0
  28. package/dist/starknet/base/modules/StarknetSignatures.d.ts +30 -0
  29. package/dist/starknet/base/modules/StarknetSignatures.js +71 -0
  30. package/dist/starknet/base/modules/StarknetTokens.d.ts +67 -0
  31. package/dist/starknet/base/modules/StarknetTokens.js +97 -0
  32. package/dist/starknet/base/modules/StarknetTransactions.d.ts +87 -0
  33. package/dist/starknet/base/modules/StarknetTransactions.js +226 -0
  34. package/dist/starknet/btcrelay/BtcRelayAbi.d.ts +250 -0
  35. package/dist/starknet/btcrelay/BtcRelayAbi.js +341 -0
  36. package/dist/starknet/btcrelay/StarknetBtcRelay.d.ts +166 -0
  37. package/dist/starknet/btcrelay/StarknetBtcRelay.js +323 -0
  38. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.d.ts +32 -0
  39. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.js +74 -0
  40. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.d.ts +52 -0
  41. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.js +113 -0
  42. package/dist/starknet/contract/StarknetContractBase.d.ts +13 -0
  43. package/dist/starknet/contract/StarknetContractBase.js +18 -0
  44. package/dist/starknet/contract/modules/StarknetContractEvents.d.ts +40 -0
  45. package/dist/starknet/contract/modules/StarknetContractEvents.js +77 -0
  46. package/dist/starknet/events/StarknetChainEvents.d.ts +19 -0
  47. package/dist/starknet/events/StarknetChainEvents.js +51 -0
  48. package/dist/starknet/events/StarknetChainEventsBrowser.d.ts +73 -0
  49. package/dist/starknet/events/StarknetChainEventsBrowser.js +210 -0
  50. package/dist/starknet/swaps/EscrowManagerAbi.d.ts +445 -0
  51. package/dist/starknet/swaps/EscrowManagerAbi.js +601 -0
  52. package/dist/starknet/swaps/StarknetSwapContract.d.ts +215 -0
  53. package/dist/starknet/swaps/StarknetSwapContract.js +452 -0
  54. package/dist/starknet/swaps/StarknetSwapData.d.ts +74 -0
  55. package/dist/starknet/swaps/StarknetSwapData.js +316 -0
  56. package/dist/starknet/swaps/StarknetSwapModule.d.ts +9 -0
  57. package/dist/starknet/swaps/StarknetSwapModule.js +12 -0
  58. package/dist/starknet/swaps/handlers/IHandler.d.ts +13 -0
  59. package/dist/starknet/swaps/handlers/IHandler.js +2 -0
  60. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.d.ts +13 -0
  61. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.js +13 -0
  62. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.d.ts +22 -0
  63. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.js +44 -0
  64. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +25 -0
  65. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +48 -0
  66. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +26 -0
  67. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +40 -0
  68. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +20 -0
  69. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +29 -0
  70. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +64 -0
  71. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +86 -0
  72. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -0
  73. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.js +27 -0
  74. package/dist/starknet/swaps/modules/StarknetLpVault.d.ts +69 -0
  75. package/dist/starknet/swaps/modules/StarknetLpVault.js +122 -0
  76. package/dist/starknet/swaps/modules/StarknetSwapClaim.d.ts +53 -0
  77. package/dist/starknet/swaps/modules/StarknetSwapClaim.js +100 -0
  78. package/dist/starknet/swaps/modules/StarknetSwapInit.d.ts +84 -0
  79. package/dist/starknet/swaps/modules/StarknetSwapInit.js +164 -0
  80. package/dist/starknet/swaps/modules/StarknetSwapRefund.d.ts +64 -0
  81. package/dist/starknet/swaps/modules/StarknetSwapRefund.js +131 -0
  82. package/dist/starknet/swaps/modules/SwapClaim.d.ts +54 -0
  83. package/dist/starknet/swaps/modules/SwapClaim.js +115 -0
  84. package/dist/starknet/swaps/modules/SwapInit.d.ts +79 -0
  85. package/dist/starknet/swaps/modules/SwapInit.js +174 -0
  86. package/dist/starknet/swaps/modules/SwapRefund.d.ts +63 -0
  87. package/dist/starknet/swaps/modules/SwapRefund.js +149 -0
  88. package/dist/starknet/wallet/StarknetKeypairWallet.d.ts +6 -0
  89. package/dist/starknet/wallet/StarknetKeypairWallet.js +26 -0
  90. package/dist/starknet/wallet/StarknetSigner.d.ts +12 -0
  91. package/dist/starknet/wallet/StarknetSigner.js +46 -0
  92. package/dist/utils/Utils.d.ts +38 -0
  93. package/dist/utils/Utils.js +255 -0
  94. package/package.json +39 -0
  95. package/src/index.ts +41 -0
  96. package/src/starknet/StarknetChainType.ts +20 -0
  97. package/src/starknet/StarknetInitializer.ts +75 -0
  98. package/src/starknet/base/StarknetAction.ts +90 -0
  99. package/src/starknet/base/StarknetBase.ts +56 -0
  100. package/src/starknet/base/StarknetModule.ts +20 -0
  101. package/src/starknet/base/modules/ERC20Abi.ts +1029 -0
  102. package/src/starknet/base/modules/StarknetAccounts.ts +26 -0
  103. package/src/starknet/base/modules/StarknetAddresses.ts +23 -0
  104. package/src/starknet/base/modules/StarknetBlocks.ts +59 -0
  105. package/src/starknet/base/modules/StarknetEvents.ts +105 -0
  106. package/src/starknet/base/modules/StarknetFees.ts +136 -0
  107. package/src/starknet/base/modules/StarknetSignatures.ts +91 -0
  108. package/src/starknet/base/modules/StarknetTokens.ts +116 -0
  109. package/src/starknet/base/modules/StarknetTransactions.ts +254 -0
  110. package/src/starknet/btcrelay/BtcRelayAbi.ts +338 -0
  111. package/src/starknet/btcrelay/StarknetBtcRelay.ts +415 -0
  112. package/src/starknet/btcrelay/headers/StarknetBtcHeader.ts +101 -0
  113. package/src/starknet/btcrelay/headers/StarknetBtcStoredHeader.ts +142 -0
  114. package/src/starknet/contract/StarknetContractBase.ts +29 -0
  115. package/src/starknet/contract/modules/StarknetContractEvents.ts +108 -0
  116. package/src/starknet/events/StarknetChainEvents.ts +63 -0
  117. package/src/starknet/events/StarknetChainEventsBrowser.ts +289 -0
  118. package/src/starknet/swaps/EscrowManagerAbi.ts +600 -0
  119. package/src/starknet/swaps/StarknetSwapContract.ts +694 -0
  120. package/src/starknet/swaps/StarknetSwapData.ts +441 -0
  121. package/src/starknet/swaps/StarknetSwapModule.ts +17 -0
  122. package/src/starknet/swaps/handlers/IHandler.ts +20 -0
  123. package/src/starknet/swaps/handlers/claim/ClaimHandlers.ts +23 -0
  124. package/src/starknet/swaps/handlers/claim/HashlockClaimHandler.ts +54 -0
  125. package/src/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +73 -0
  126. package/src/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +67 -0
  127. package/src/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +49 -0
  128. package/src/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +151 -0
  129. package/src/starknet/swaps/handlers/refund/TimelockRefundHandler.ts +39 -0
  130. package/src/starknet/swaps/modules/StarknetLpVault.ts +148 -0
  131. package/src/starknet/swaps/modules/StarknetSwapClaim.ts +142 -0
  132. package/src/starknet/swaps/modules/StarknetSwapInit.ts +226 -0
  133. package/src/starknet/swaps/modules/StarknetSwapRefund.ts +202 -0
  134. package/src/starknet/wallet/StarknetKeypairWallet.ts +34 -0
  135. package/src/starknet/wallet/StarknetSigner.ts +55 -0
  136. package/src/utils/Utils.ts +247 -0
@@ -0,0 +1,415 @@
1
+ import {Buffer} from "buffer";
2
+ import {StarknetBtcHeader} from "./headers/StarknetBtcHeader";
3
+ import {BigIntBufferUtils, BitcoinRpc, BtcBlock, BtcRelay, StatePredictorUtils} from "@atomiqlabs/base";
4
+ import {
5
+ bigNumberishToBuffer,
6
+ bufferToU32Array,
7
+ toHex,
8
+ u32ReverseEndianness
9
+ } from "../../utils/Utils";
10
+ import {StarknetContractBase} from "../contract/StarknetContractBase";
11
+ import {StarknetBtcStoredHeader} from "./headers/StarknetBtcStoredHeader";
12
+ import {StarknetTx} from "../base/modules/StarknetTransactions";
13
+ import {StarknetSigner} from "../wallet/StarknetSigner";
14
+ import {BtcRelayAbi} from "./BtcRelayAbi";
15
+ import {BigNumberish, constants, hash, Provider} from "starknet";
16
+ import {StarknetFees} from "../base/modules/StarknetFees";
17
+ import {StarknetRetryPolicy} from "../base/StarknetBase";
18
+ import {StarknetAction} from "../base/StarknetAction";
19
+
20
+ function serializeBlockHeader(e: BtcBlock): StarknetBtcHeader {
21
+ return new StarknetBtcHeader({
22
+ reversed_version: u32ReverseEndianness(e.getVersion()),
23
+ previous_blockhash: bufferToU32Array(Buffer.from(e.getPrevBlockhash(), "hex").reverse()),
24
+ merkle_root: bufferToU32Array(Buffer.from(e.getMerkleRoot(), "hex").reverse()),
25
+ reversed_timestamp: u32ReverseEndianness(e.getTimestamp()),
26
+ nbits: u32ReverseEndianness(e.getNbits()),
27
+ nonce: u32ReverseEndianness(e.getNonce()),
28
+ hash: Buffer.from(e.getHash(), "hex").reverse()
29
+ });
30
+ }
31
+
32
+ const GAS_PER_BLOCKHEADER = 750;
33
+ const GAS_PER_BLOCKHEADER_FORK = 750;
34
+
35
+ const btcRelayAddreses = {
36
+ [constants.StarknetChainId.SN_SEPOLIA]: "0x032afcea912ba13f6a1878fe38af23eaec3e6b4c7db31a3571550d3cf80d3e31",
37
+ [constants.StarknetChainId.SN_MAIN]: "0x05cc69b09e8c53520f9e328f6eca72cf02fe46ce290b757d42414e2238001603"
38
+ };
39
+
40
+ function serializeCalldata(headers: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, span: BigNumberish[]) {
41
+ span.push(toHex(headers.length));
42
+ headers.forEach(header => {
43
+ span.push(...header.serialize());
44
+ });
45
+ span.push(...storedHeader.serialize());
46
+ return span;
47
+ }
48
+
49
+ export class StarknetBtcRelay<B extends BtcBlock>
50
+ extends StarknetContractBase<typeof BtcRelayAbi>
51
+ implements BtcRelay<StarknetBtcStoredHeader, StarknetTx, B, StarknetSigner> {
52
+
53
+ public SaveMainHeaders(signer: string, mainHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
54
+
55
+ return new StarknetAction(signer, this,
56
+ {
57
+ contractAddress: this.contract.address,
58
+ entrypoint: "submit_main_blockheaders",
59
+ calldata: serializeCalldata(mainHeaders, storedHeader, [])
60
+ },
61
+ {l1: GAS_PER_BLOCKHEADER * mainHeaders.length, l2: 0}
62
+ )
63
+ }
64
+
65
+ public SaveShortForkHeaders(signer: string, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader): StarknetAction {
66
+ return new StarknetAction(signer, this,
67
+ {
68
+ contractAddress: this.contract.address,
69
+ entrypoint: "submit_short_fork_blockheaders",
70
+ calldata: serializeCalldata(forkHeaders, storedHeader, [])
71
+ },
72
+ {l1: GAS_PER_BLOCKHEADER * forkHeaders.length, l2: 0}
73
+ )
74
+ }
75
+
76
+ public SaveLongForkHeaders(signer: string, forkId: number, forkHeaders: StarknetBtcHeader[], storedHeader: StarknetBtcStoredHeader, totalForkHeaders: number = 100): StarknetAction {
77
+ return new StarknetAction(signer, this,
78
+ {
79
+ contractAddress: this.contract.address,
80
+ entrypoint: "submit_fork_blockheaders",
81
+ calldata: serializeCalldata(forkHeaders, storedHeader, [toHex(forkId)])
82
+ },
83
+ {l1: (GAS_PER_BLOCKHEADER * forkHeaders.length) + (GAS_PER_BLOCKHEADER_FORK * totalForkHeaders), l2: 0}
84
+ )
85
+ }
86
+
87
+ bitcoinRpc: BitcoinRpc<B>;
88
+
89
+ readonly maxHeadersPerTx: number = 100;
90
+ readonly maxForkHeadersPerTx: number = 100;
91
+ readonly maxShortForkHeadersPerTx: number = 100;
92
+
93
+ constructor(
94
+ chainId: constants.StarknetChainId,
95
+ provider: Provider,
96
+ bitcoinRpc: BitcoinRpc<B>,
97
+ contractAddress: string = btcRelayAddreses[chainId],
98
+ retryPolicy?: StarknetRetryPolicy,
99
+ solanaFeeEstimator: StarknetFees = new StarknetFees(provider)
100
+ ) {
101
+ super(chainId, provider, contractAddress, BtcRelayAbi, retryPolicy, solanaFeeEstimator);
102
+ this.bitcoinRpc = bitcoinRpc;
103
+ }
104
+
105
+ /**
106
+ * Computes subsequent commited headers as they will appear on the blockchain when transactions
107
+ * are submitted & confirmed
108
+ *
109
+ * @param initialStoredHeader
110
+ * @param syncedHeaders
111
+ * @private
112
+ */
113
+ private computeCommitedHeaders(initialStoredHeader: StarknetBtcStoredHeader, syncedHeaders: StarknetBtcHeader[]) {
114
+ const computedCommitedHeaders = [initialStoredHeader];
115
+ for(let blockHeader of syncedHeaders) {
116
+ computedCommitedHeaders.push(computedCommitedHeaders[computedCommitedHeaders.length-1].computeNext(blockHeader));
117
+ }
118
+ return computedCommitedHeaders;
119
+ }
120
+
121
+ /**
122
+ * A common logic for submitting blockheaders in a transaction
123
+ *
124
+ * @param signer
125
+ * @param headers headers to sync to the btc relay
126
+ * @param storedHeader current latest stored block header for a given fork
127
+ * @param tipWork work of the current tip in a given fork
128
+ * @param forkId forkId to submit to, forkId=0 means main chain, forkId=-1 means short fork
129
+ * @param feeRate feeRate for the transaction
130
+ * @private
131
+ */
132
+ private async _saveHeaders(
133
+ signer: string,
134
+ headers: BtcBlock[],
135
+ storedHeader: StarknetBtcStoredHeader,
136
+ tipWork: Buffer,
137
+ forkId: number,
138
+ feeRate: string
139
+ ) {
140
+ const blockHeaderObj = headers.map(serializeBlockHeader);
141
+ let starknetAction: StarknetAction;
142
+ switch(forkId) {
143
+ case -1:
144
+ starknetAction = this.SaveShortForkHeaders(signer, blockHeaderObj, storedHeader);
145
+ break;
146
+ case 0:
147
+ starknetAction = this.SaveMainHeaders(signer, blockHeaderObj, storedHeader);
148
+ break;
149
+ default:
150
+ starknetAction = this.SaveLongForkHeaders(signer, forkId, blockHeaderObj, storedHeader);
151
+ break;
152
+ }
153
+
154
+ const tx = await starknetAction.tx(feeRate);
155
+
156
+ const computedCommitedHeaders = this.computeCommitedHeaders(storedHeader, blockHeaderObj);
157
+ const lastStoredHeader = computedCommitedHeaders[computedCommitedHeaders.length-1];
158
+ if(forkId!==0 && StatePredictorUtils.gtBuffer(lastStoredHeader.getBlockHash(), tipWork)) {
159
+ //Fork's work is higher than main chain's work, this fork will become a main chain
160
+ forkId = 0;
161
+ }
162
+
163
+ return {
164
+ forkId: forkId,
165
+ lastStoredHeader,
166
+ tx,
167
+ computedCommitedHeaders
168
+ }
169
+ }
170
+
171
+ private getBlock(commitHash?: BigNumberish, blockHash?: Buffer): Promise<[StarknetBtcStoredHeader, bigint] | null> {
172
+ const keys = [commitHash == null ? null : toHex(commitHash)];
173
+ if (blockHash != null) {
174
+ const starknetBlockHash = hash.computePoseidonHashOnElements(bufferToU32Array(Buffer.from([...blockHash]).reverse()));
175
+ keys.push(starknetBlockHash);
176
+ }
177
+ return this.Events.findInContractEvents(
178
+ ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
179
+ keys,
180
+ (event) => {
181
+ return Promise.resolve([StarknetBtcStoredHeader.fromSerializedFeltArray(event.data), BigInt(event.params.commit_hash)]);
182
+ }
183
+ );
184
+ }
185
+
186
+ private async getBlockHeight(): Promise<number> {
187
+ return Number(await this.contract.get_blockheight());
188
+ }
189
+
190
+ /**
191
+ * Returns data about current main chain tip stored in the btc relay
192
+ */
193
+ public async getTipData(): Promise<{ commitHash: string; blockhash: string, chainWork: Buffer, blockheight: number }> {
194
+ const commitHash = await this.contract.get_tip_commit_hash();
195
+ if(commitHash==null || BigInt(commitHash)===BigInt(0)) return null;
196
+
197
+ const result = await this.getBlock(commitHash);
198
+ if(result==null) return null;
199
+
200
+ const [storedBlockHeader] = result;
201
+
202
+ return {
203
+ blockheight: storedBlockHeader.getBlockheight(),
204
+ commitHash: bigNumberishToBuffer(commitHash, 32).toString("hex"),
205
+ blockhash: storedBlockHeader.getBlockHash().toString("hex"),
206
+ chainWork: storedBlockHeader.getChainWork()
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Retrieves blockheader with a specific blockhash, returns null if requiredBlockheight is provided and
212
+ * btc relay contract is not synced up to the desired blockheight
213
+ *
214
+ * @param blockData
215
+ * @param requiredBlockheight
216
+ */
217
+ public async retrieveLogAndBlockheight(blockData: {blockhash: string}, requiredBlockheight?: number): Promise<{
218
+ header: StarknetBtcStoredHeader,
219
+ height: number
220
+ } | null> {
221
+ //TODO: we can fetch the blockheight and events in parallel
222
+ const blockHeight = await this.getBlockHeight();
223
+ if(requiredBlockheight!=null && blockHeight < requiredBlockheight) {
224
+ return null;
225
+ }
226
+ const result = await this.getBlock(null, Buffer.from(blockData.blockhash, "hex"));
227
+ if(result==null) return null;
228
+
229
+ const [storedBlockHeader, commitHash] = result;
230
+
231
+ //Check if block is part of the main chain
232
+ const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
233
+ if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
234
+
235
+ this.logger.debug("retrieveLogAndBlockheight(): block found," +
236
+ " commit hash: "+toHex(commitHash)+" blockhash: "+blockData.blockhash+" current btc relay height: "+blockHeight);
237
+
238
+ return {header: storedBlockHeader, height: blockHeight};
239
+ }
240
+
241
+ /**
242
+ * Retrieves blockheader data by blockheader's commit hash,
243
+ *
244
+ * @param commitmentHashStr
245
+ * @param blockData
246
+ */
247
+ public async retrieveLogByCommitHash(commitmentHashStr: string, blockData: {blockhash: string}): Promise<StarknetBtcStoredHeader> {
248
+ const result = await this.getBlock(commitmentHashStr, Buffer.from(blockData.blockhash, "hex"));
249
+ if(result==null) return null;
250
+
251
+ const [storedBlockHeader, commitHash] = result;
252
+
253
+ //Check if block is part of the main chain
254
+ const chainCommitment = await this.contract.get_commit_hash(storedBlockHeader.block_height);
255
+ if(BigInt(chainCommitment)!==BigInt(commitHash)) return null;
256
+
257
+ this.logger.debug("retrieveLogByCommitHash(): block found," +
258
+ " commit hash: "+commitmentHashStr+" blockhash: "+blockData.blockhash+" height: "+storedBlockHeader.block_height);
259
+
260
+ return storedBlockHeader;
261
+ }
262
+
263
+ /**
264
+ * Retrieves latest known stored blockheader & blockheader from bitcoin RPC that is in the main chain
265
+ */
266
+ public async retrieveLatestKnownBlockLog(): Promise<{
267
+ resultStoredHeader: StarknetBtcStoredHeader,
268
+ resultBitcoinHeader: B
269
+ }> {
270
+ const data = await this.Events.findInContractEvents(
271
+ ["btc_relay::events::StoreHeader", "btc_relay::events::StoreForkHeader"],
272
+ null,
273
+ async (event) => {
274
+ const storedHeader = StarknetBtcStoredHeader.fromSerializedFeltArray(event.data);
275
+
276
+ const blockHashHex = storedHeader.getBlockHash().toString("hex");
277
+ const commitHash = event.params.commit_hash;
278
+
279
+ const [isInBtcMainChain, btcRelayCommitHash] = await Promise.all([
280
+ this.bitcoinRpc.isInMainChain(blockHashHex).catch(() => false),
281
+ this.contract.get_commit_hash(storedHeader.block_height)
282
+ ]);
283
+
284
+ if(!isInBtcMainChain) return null;
285
+ if(BigInt(commitHash)!==BigInt(btcRelayCommitHash)) return null;
286
+
287
+ return {
288
+ resultStoredHeader: storedHeader,
289
+ resultBitcoinHeader: await this.bitcoinRpc.getBlockHeader(blockHashHex),
290
+ commitHash: commitHash
291
+ }
292
+ }
293
+ )
294
+
295
+ if(data!=null) this.logger.debug("retrieveLatestKnownBlockLog(): block found," +
296
+ " commit hash: "+toHex(data.commitHash)+" blockhash: "+data.resultBitcoinHeader.getHash()+
297
+ " height: "+data.resultStoredHeader.getBlockheight());
298
+
299
+ return data;
300
+ }
301
+
302
+ /**
303
+ * Saves blockheaders as a bitcoin main chain to the btc relay
304
+ *
305
+ * @param signer
306
+ * @param mainHeaders
307
+ * @param storedHeader
308
+ * @param feeRate
309
+ */
310
+ public saveMainHeaders(signer: string, mainHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, feeRate?: string) {
311
+ this.logger.debug("saveMainHeaders(): submitting main blockheaders, count: "+mainHeaders.length);
312
+ return this._saveHeaders(signer, mainHeaders, storedHeader, null, 0, feeRate);
313
+ }
314
+
315
+ /**
316
+ * Creates a new long fork and submits the headers to it
317
+ *
318
+ * @param signer
319
+ * @param forkHeaders
320
+ * @param storedHeader
321
+ * @param tipWork
322
+ * @param feeRate
323
+ */
324
+ public async saveNewForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
325
+ let forkId: number = Math.floor(Math.random() * 0xFFFFFFFFFFFF);
326
+
327
+ this.logger.debug("saveNewForkHeaders(): submitting new fork & blockheaders," +
328
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
329
+
330
+ return await this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
331
+ }
332
+
333
+ /**
334
+ * Continues submitting blockheaders to a given fork
335
+ *
336
+ * @param signer
337
+ * @param forkHeaders
338
+ * @param storedHeader
339
+ * @param forkId
340
+ * @param tipWork
341
+ * @param feeRate
342
+ */
343
+ public saveForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, forkId: number, tipWork: Buffer, feeRate?: string) {
344
+ this.logger.debug("saveForkHeaders(): submitting blockheaders to existing fork," +
345
+ " count: "+forkHeaders.length+" forkId: 0x"+forkId.toString(16));
346
+
347
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, forkId, feeRate);
348
+ }
349
+
350
+ /**
351
+ * Submits short fork with given blockheaders
352
+ *
353
+ * @param signer
354
+ * @param forkHeaders
355
+ * @param storedHeader
356
+ * @param tipWork
357
+ * @param feeRate
358
+ */
359
+ public saveShortForkHeaders(signer: string, forkHeaders: BtcBlock[], storedHeader: StarknetBtcStoredHeader, tipWork: Buffer, feeRate?: string) {
360
+ this.logger.debug("saveShortForkHeaders(): submitting short fork blockheaders," +
361
+ " count: "+forkHeaders.length);
362
+
363
+ return this._saveHeaders(signer, forkHeaders, storedHeader, tipWork, -1, feeRate);
364
+ }
365
+
366
+ /**
367
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
368
+ *
369
+ * @param requiredBlockheight
370
+ * @param feeRate
371
+ */
372
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
373
+ const tipData = await this.getTipData();
374
+ const currBlockheight = tipData.blockheight;
375
+
376
+ const blockheightDelta = requiredBlockheight-currBlockheight;
377
+
378
+ if(blockheightDelta<=0) return 0n;
379
+
380
+ const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
381
+ this.logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
382
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
383
+
384
+ return synchronizationFee;
385
+ }
386
+
387
+ /**
388
+ * Returns fee required (in SOL) to synchronize a single block to btc relay
389
+ *
390
+ * @param feeRate
391
+ */
392
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
393
+ feeRate ??= await this.Fees.getFeeRate();
394
+ return StarknetFees.getGasFee(GAS_PER_BLOCKHEADER, feeRate);
395
+ }
396
+
397
+ /**
398
+ * Gets fee rate required for submitting blockheaders to the main chain
399
+ */
400
+ public getMainFeeRate(signer: string | null): Promise<string> {
401
+ return this.Fees.getFeeRate();
402
+ }
403
+
404
+ /**
405
+ * Gets fee rate required for submitting blockheaders to the specific fork
406
+ */
407
+ public getForkFeeRate(signer: string, forkId: number): Promise<string> {
408
+ return this.Fees.getFeeRate();
409
+ }
410
+
411
+ saveInitialHeader(signer: string, header: B, epochStart: number, pastBlocksTimestamps: number[], feeRate?: string): Promise<StarknetTx> {
412
+ throw new Error("Not supported, starknet contract is initialized with constructor!");
413
+ }
414
+
415
+ }
@@ -0,0 +1,101 @@
1
+ import {BtcHeader} from "@atomiqlabs/base";
2
+ import {Buffer} from "buffer";
3
+ import {BigNumberish} from "starknet";
4
+ import {toHex, u32ArrayToBuffer, u32ReverseEndianness} from "../../../utils/Utils";
5
+ import * as createHash from "create-hash";
6
+
7
+ export type StarknetBtcHeaderType = {
8
+ reversed_version: BigNumberish;
9
+ previous_blockhash: BigNumberish[];
10
+ merkle_root: BigNumberish[];
11
+ reversed_timestamp: BigNumberish;
12
+ nbits: BigNumberish;
13
+ nonce: BigNumberish;
14
+ hash?: Buffer
15
+ }
16
+
17
+ export class StarknetBtcHeader implements BtcHeader {
18
+
19
+ reversed_version: number;
20
+ previous_blockhash: number[];
21
+ merkle_root: number[];
22
+ reversed_timestamp: number;
23
+ nbits: number;
24
+ nonce: number;
25
+ hash?: Buffer;
26
+
27
+ constructor(obj: StarknetBtcHeaderType) {
28
+ this.reversed_version = Number(obj.reversed_version);
29
+ this.previous_blockhash = obj.previous_blockhash.map(val => Number(val));
30
+ this.merkle_root = obj.merkle_root.map(val => Number(val));
31
+ this.reversed_timestamp = Number(obj.reversed_timestamp);
32
+ this.nbits = Number(obj.nbits);
33
+ this.nonce = Number(obj.nonce);
34
+ this.hash = obj.hash;
35
+ }
36
+
37
+ getMerkleRoot(): Buffer {
38
+ return u32ArrayToBuffer(this.merkle_root);
39
+ }
40
+
41
+ getNbits(): number {
42
+ return u32ReverseEndianness(this.nbits);
43
+ }
44
+
45
+ getNonce(): number {
46
+ return u32ReverseEndianness(this.nonce);
47
+ }
48
+
49
+ getReversedPrevBlockhash(): Buffer {
50
+ return u32ArrayToBuffer(this.previous_blockhash);
51
+ }
52
+
53
+ getTimestamp(): number {
54
+ return u32ReverseEndianness(this.reversed_timestamp);
55
+ }
56
+
57
+ getVersion(): number {
58
+ return u32ReverseEndianness(this.reversed_version);
59
+ }
60
+
61
+ getHash(): Buffer {
62
+ if(this.hash!=null) return this.hash;
63
+ const buffer = Buffer.alloc(80);
64
+ buffer.writeUInt32BE(this.reversed_version);
65
+ u32ArrayToBuffer(this.previous_blockhash).copy(buffer, 4);
66
+ u32ArrayToBuffer(this.merkle_root).copy(buffer, 36);
67
+ buffer.writeUInt32BE(this.reversed_timestamp, 68);
68
+ buffer.writeUInt32BE(this.nbits, 72);
69
+ buffer.writeUInt32BE(this.nonce, 76);
70
+ return createHash("sha256").update(createHash("sha256").update(buffer).digest()).digest();
71
+ }
72
+
73
+ serialize(): BigNumberish[] {
74
+ return [
75
+ this.reversed_version,
76
+ ...this.previous_blockhash,
77
+ ...this.merkle_root,
78
+ this.reversed_timestamp,
79
+ this.nbits,
80
+ this.nonce
81
+ ];
82
+ }
83
+
84
+ static fromSerializedFeltArray(span: BigNumberish[]): StarknetBtcHeader {
85
+ const reversed_version = toHex(span.shift());
86
+ const previous_blockhash = span.splice(0, 8).map(toHex);
87
+ const merkle_root = span.splice(0, 8).map(toHex);
88
+ const reversed_timestamp = toHex(span.shift());
89
+ const nbits = toHex(span.shift());
90
+ const nonce = toHex(span.shift());
91
+ return new StarknetBtcHeader({
92
+ reversed_version,
93
+ previous_blockhash,
94
+ merkle_root,
95
+ reversed_timestamp,
96
+ nbits,
97
+ nonce
98
+ });
99
+ }
100
+
101
+ }
@@ -0,0 +1,142 @@
1
+ import {BtcStoredHeader, StatePredictorUtils} from "@atomiqlabs/base";
2
+ import {StarknetBtcHeader, StarknetBtcHeaderType} from "./StarknetBtcHeader";
3
+ import {Buffer} from "buffer";
4
+ import {BigNumberish, cairo, Uint256} from "starknet";
5
+ import {bigNumberishToBuffer, bufferToU32Array, u32ArrayToBuffer, isUint256, toHex} from "../../../utils/Utils";
6
+
7
+ export type StarknetBtcStoredHeaderType = {
8
+ blockheader: StarknetBtcHeader | StarknetBtcHeaderType,
9
+ block_hash: BigNumberish[],
10
+ chain_work: BigNumberish | Uint256,
11
+ block_height: BigNumberish,
12
+ last_diff_adjustment: BigNumberish,
13
+ prev_block_timestamps: BigNumberish[]
14
+ }
15
+
16
+ export class StarknetBtcStoredHeader implements BtcStoredHeader<StarknetBtcHeader> {
17
+
18
+ blockheader: StarknetBtcHeader;
19
+ block_hash: number[];
20
+ chain_work: Uint256;
21
+ block_height: number;
22
+ last_diff_adjustment: number;
23
+ prev_block_timestamps: number[];
24
+
25
+ constructor(obj: StarknetBtcStoredHeaderType) {
26
+ this.blockheader = obj.blockheader instanceof StarknetBtcHeader ? obj.blockheader : new StarknetBtcHeader(obj.blockheader);
27
+ this.block_hash = obj.block_hash.map(val => Number(val));
28
+ this.chain_work = isUint256(obj.chain_work) ? obj.chain_work : cairo.uint256(obj.chain_work);
29
+ this.block_height = Number(obj.block_height);
30
+ this.last_diff_adjustment = Number(obj.last_diff_adjustment);
31
+ this.prev_block_timestamps = obj.prev_block_timestamps.map(val => Number(val));
32
+ }
33
+
34
+ getBlockheight(): number {
35
+ return this.block_height;
36
+ }
37
+
38
+ getChainWork(): Buffer {
39
+ return bigNumberishToBuffer(this.chain_work, 32);
40
+ }
41
+
42
+ getHeader(): StarknetBtcHeader {
43
+ return this.blockheader;
44
+ }
45
+
46
+ getLastDiffAdjustment(): number {
47
+ return this.last_diff_adjustment;
48
+ }
49
+
50
+ getPrevBlockTimestamps(): number[] {
51
+ return this.prev_block_timestamps;
52
+ }
53
+
54
+ getBlockHash(): Buffer {
55
+ return u32ArrayToBuffer(this.block_hash).reverse();
56
+ }
57
+
58
+ /**
59
+ * Computes prevBlockTimestamps for a next block, shifting the old block timestamps to the left & appending
60
+ * this block's timestamp to the end
61
+ *
62
+ * @private
63
+ */
64
+ private computeNextBlockTimestamps(): number[] {
65
+ const prevBlockTimestamps = [...this.prev_block_timestamps];
66
+ for(let i=1;i<10;i++) {
67
+ prevBlockTimestamps[i-1] = prevBlockTimestamps[i];
68
+ }
69
+ prevBlockTimestamps[9] = this.blockheader.getTimestamp();
70
+ return prevBlockTimestamps;
71
+ }
72
+
73
+ /**
74
+ * Computes total chain work after a new header with "nbits" is added to the chain
75
+ *
76
+ * @param nbits
77
+ * @private
78
+ */
79
+ private computeNextChainWork(nbits: number): Buffer {
80
+ const chainWork = [...this.getChainWork()];
81
+ StatePredictorUtils.addInPlace(chainWork, [...StatePredictorUtils.getChainwork(nbits)]);
82
+ return Buffer.from(chainWork);
83
+ }
84
+
85
+ /**
86
+ * Computes lastDiffAdjustment, this changes only once every DIFF_ADJUSTMENT_PERIOD blocks
87
+ *
88
+ * @param headerTimestamp
89
+ * @private
90
+ */
91
+ private computeNextLastDiffAdjustment(headerTimestamp: number) {
92
+ const blockheight = this.block_height+1;
93
+
94
+ let lastDiffAdjustment = this.last_diff_adjustment;
95
+ if(blockheight % StatePredictorUtils.DIFF_ADJUSTMENT_PERIOD === 0) {
96
+ lastDiffAdjustment = headerTimestamp;
97
+ }
98
+
99
+ return lastDiffAdjustment;
100
+ }
101
+
102
+ computeNext(header: StarknetBtcHeader): StarknetBtcStoredHeader {
103
+ return new StarknetBtcStoredHeader({
104
+ chain_work: "0x"+this.computeNextChainWork(header.getNbits()).toString("hex"),
105
+ prev_block_timestamps: this.computeNextBlockTimestamps(),
106
+ block_height: this.block_height+1,
107
+ last_diff_adjustment: this.computeNextLastDiffAdjustment(header.getTimestamp()),
108
+ block_hash: bufferToU32Array(header.getHash()),
109
+ blockheader: header
110
+ });
111
+ }
112
+
113
+ serialize(): BigNumberish[] {
114
+ return [
115
+ ...this.blockheader.serialize(),
116
+ ...this.block_hash,
117
+ this.chain_work.low,
118
+ this.chain_work.high,
119
+ this.block_height,
120
+ this.last_diff_adjustment,
121
+ ...this.prev_block_timestamps
122
+ ]
123
+ }
124
+
125
+ static fromSerializedFeltArray(span: BigNumberish[]): StarknetBtcStoredHeader {
126
+ const blockheader = StarknetBtcHeader.fromSerializedFeltArray(span);
127
+ const block_hash = span.splice(0, 8).map(toHex);
128
+ const chain_work = {low: span.shift(), high: span.shift()};
129
+ const block_height = toHex(span.shift());
130
+ const last_diff_adjustment = toHex(span.shift());
131
+ const prev_block_timestamps = span.splice(0, 10).map(toHex);
132
+ return new StarknetBtcStoredHeader({
133
+ blockheader,
134
+ block_hash,
135
+ chain_work,
136
+ block_height,
137
+ last_diff_adjustment,
138
+ prev_block_timestamps
139
+ });
140
+ }
141
+
142
+ }
@@ -0,0 +1,29 @@
1
+ import {StarknetBase, StarknetRetryPolicy} from "../base/StarknetBase";
2
+ import {constants, Contract, Provider, TypedContractV2} from "starknet";
3
+ import {StarknetFees} from "../base/modules/StarknetFees";
4
+ import {Abi} from "abi-wan-kanabi";
5
+ import {StarknetContractEvents} from "./modules/StarknetContractEvents";
6
+
7
+ /**
8
+ * Base class providing program specific utilities
9
+ */
10
+ export class StarknetContractBase<T extends Abi> extends StarknetBase {
11
+
12
+ contract: TypedContractV2<T>;
13
+
14
+ public readonly Events: StarknetContractEvents<T>;
15
+
16
+ constructor(
17
+ chainId: constants.StarknetChainId,
18
+ provider: Provider,
19
+ contractAddress: string,
20
+ contractAbi: T,
21
+ retryPolicy?: StarknetRetryPolicy,
22
+ solanaFeeEstimator: StarknetFees = new StarknetFees(provider)
23
+ ) {
24
+ super(chainId, provider, retryPolicy, solanaFeeEstimator);
25
+ this.contract = new Contract(contractAbi, contractAddress, provider).typedv2(contractAbi);
26
+ this.Events = new StarknetContractEvents(this, contractAbi);
27
+ }
28
+
29
+ }