@atomiqlabs/chain-starknet 4.0.0-dev.35 → 4.0.0-dev.37

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.
@@ -50,6 +50,10 @@ export declare class StarknetChainInterface implements ChainInterface<StarknetTx
50
50
  deserializeTx(txData: string): Promise<StarknetTx>;
51
51
  getTxIdStatus(txId: string): Promise<"not_found" | "pending" | "success" | "reverted">;
52
52
  getTxStatus(tx: string): Promise<"not_found" | "pending" | "success" | "reverted">;
53
+ getFinalizedBlock(): Promise<{
54
+ height: number;
55
+ blockHash: string;
56
+ }>;
53
57
  txsTransfer(signer: string, token: string, amount: bigint, dstAddress: string, feeRate?: string): Promise<StarknetTx[]>;
54
58
  transfer(signer: StarknetSigner, token: string, amount: bigint, dstAddress: string, txOptions?: TransactionConfirmationOptions): Promise<string>;
55
59
  wrapSigner(signer: Account): Promise<StarknetSigner>;
@@ -89,6 +89,13 @@ class StarknetChainInterface {
89
89
  getTxStatus(tx) {
90
90
  return this.Transactions.getTxStatus(tx);
91
91
  }
92
+ async getFinalizedBlock() {
93
+ const block = await this.Blocks.getBlock("l1_accepted");
94
+ return {
95
+ height: block.block_number,
96
+ blockHash: block.block_hash
97
+ };
98
+ }
92
99
  txsTransfer(signer, token, amount, dstAddress, feeRate) {
93
100
  return this.Tokens.txsTransfer(signer, token, amount, dstAddress, feeRate);
94
101
  }
@@ -1,5 +1,6 @@
1
1
  import { StarknetModule } from "../StarknetModule";
2
- export type StarknetBlockTag = "pre_confirmed" | "latest";
2
+ import { BlockWithTxHashes } from "starknet";
3
+ export type StarknetBlockTag = "pre_confirmed" | "latest" | "l1_accepted";
3
4
  export declare class StarknetBlocks extends StarknetModule {
4
5
  private BLOCK_CACHE_TIME;
5
6
  private blockCache;
@@ -11,6 +12,12 @@ export declare class StarknetBlocks extends StarknetModule {
11
12
  */
12
13
  private fetchAndSaveBlockTime;
13
14
  private cleanupBlocks;
15
+ /**
16
+ * Gets the block for a given blocktag, with caching
17
+ *
18
+ * @param blockTag
19
+ */
20
+ getBlock(blockTag: StarknetBlockTag | number): Promise<BlockWithTxHashes>;
14
21
  /**
15
22
  * Gets the block for a given blocktag, with caching
16
23
  *
@@ -16,19 +16,19 @@ class StarknetBlocks extends StarknetModule_1.StarknetModule {
16
16
  */
17
17
  fetchAndSaveBlockTime(blockTag) {
18
18
  const blockTagStr = blockTag.toString(10);
19
- const blockTimePromise = this.provider.getBlockWithTxHashes(blockTag).then(result => result.timestamp);
19
+ const blockPromise = this.provider.getBlockWithTxHashes(blockTag);
20
20
  const timestamp = Date.now();
21
21
  this.blockCache[blockTagStr] = {
22
- blockTime: blockTimePromise,
22
+ block: blockPromise,
23
23
  timestamp
24
24
  };
25
- blockTimePromise.catch(e => {
26
- if (this.blockCache[blockTagStr] != null && this.blockCache[blockTagStr].blockTime === blockTimePromise)
25
+ blockPromise.catch(e => {
26
+ if (this.blockCache[blockTagStr] != null && this.blockCache[blockTagStr].block === blockPromise)
27
27
  delete this.blockCache[blockTagStr];
28
28
  throw e;
29
29
  });
30
30
  return {
31
- blockTime: blockTimePromise,
31
+ block: blockPromise,
32
32
  timestamp
33
33
  };
34
34
  }
@@ -52,13 +52,22 @@ class StarknetBlocks extends StarknetModule_1.StarknetModule {
52
52
  *
53
53
  * @param blockTag
54
54
  */
55
- getBlockTime(blockTag) {
55
+ getBlock(blockTag) {
56
56
  this.cleanupBlocks();
57
57
  let cachedBlockData = this.blockCache[blockTag.toString(10)];
58
58
  if (cachedBlockData == null || Date.now() - cachedBlockData.timestamp > this.BLOCK_CACHE_TIME) {
59
59
  cachedBlockData = this.fetchAndSaveBlockTime(blockTag);
60
60
  }
61
- return cachedBlockData.blockTime;
61
+ return cachedBlockData.block;
62
+ }
63
+ /**
64
+ * Gets the block for a given blocktag, with caching
65
+ *
66
+ * @param blockTag
67
+ */
68
+ async getBlockTime(blockTag) {
69
+ const block = await this.getBlock(blockTag);
70
+ return block.timestamp;
62
71
  }
63
72
  }
64
73
  exports.StarknetBlocks = StarknetBlocks;
@@ -37,8 +37,9 @@ export declare class StarknetEvents extends StarknetModule {
37
37
  * @param keys
38
38
  * @param processor called for every batch of returned signatures, should return a value if the correct signature
39
39
  * was found, or null if the search should continue
40
+ * @param startHeight
40
41
  * @param abortSignal
41
42
  * @param logFetchLimit
42
43
  */
43
- findInEventsForward<T>(contract: string, keys: string[][], processor: (signatures: StarknetEvent[]) => Promise<T>, abortSignal?: AbortSignal, logFetchLimit?: number): Promise<T>;
44
+ findInEventsForward<T>(contract: string, keys: string[][], processor: (signatures: StarknetEvent[]) => Promise<T>, startHeight?: number, abortSignal?: AbortSignal, logFetchLimit?: number): Promise<T>;
44
45
  }
@@ -61,16 +61,18 @@ class StarknetEvents extends StarknetModule_1.StarknetModule {
61
61
  * @param keys
62
62
  * @param processor called for every batch of returned signatures, should return a value if the correct signature
63
63
  * was found, or null if the search should continue
64
+ * @param startHeight
64
65
  * @param abortSignal
65
66
  * @param logFetchLimit
66
67
  */
67
- async findInEventsForward(contract, keys, processor, abortSignal, logFetchLimit) {
68
+ async findInEventsForward(contract, keys, processor, startHeight, abortSignal, logFetchLimit) {
68
69
  if (logFetchLimit == null || logFetchLimit > this.EVENTS_LIMIT)
69
70
  logFetchLimit = this.EVENTS_LIMIT;
70
71
  let eventsResult = null;
71
72
  while (eventsResult == null || eventsResult?.continuation_token != null) {
72
73
  eventsResult = await this.root.provider.getEvents({
73
74
  address: contract,
75
+ from_block: startHeight == null ? undefined : { block_number: startHeight },
74
76
  to_block: "latest",
75
77
  keys,
76
78
  chunk_size: logFetchLimit ?? this.EVENTS_LIMIT,
@@ -65,7 +65,7 @@ export declare class StarknetTransactions extends StarknetModule {
65
65
  * of a batch of starknet transactions
66
66
  *
67
67
  * @param signer
68
- * @param txs transactions to send
68
+ * @param _txs transactions to send
69
69
  * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
70
70
  * are re-sent at regular intervals)
71
71
  * @param abortSignal abort signal to abort waiting for transaction confirmations
@@ -73,7 +73,7 @@ export declare class StarknetTransactions extends StarknetModule {
73
73
  * are executed in order)
74
74
  * @param onBeforePublish a callback called before every transaction is published
75
75
  */
76
- sendAndConfirm(signer: StarknetSigner, txs: StarknetTx[], waitForConfirmation?: boolean, abortSignal?: AbortSignal, parallel?: boolean, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<string[]>;
76
+ sendAndConfirm(signer: StarknetSigner, _txs: StarknetTx[], waitForConfirmation?: boolean, abortSignal?: AbortSignal, parallel?: boolean, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<string[]>;
77
77
  /**
78
78
  * Serializes the starknet transaction, saves the transaction, signers & last valid blockheight
79
79
  *
@@ -144,8 +144,11 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
144
144
  //Add deploy account tx
145
145
  if (nonce === 0n) {
146
146
  const deployPayload = await signer.getDeployPayload();
147
- if (deployPayload != null)
148
- txs.unshift(await this.root.Accounts.getAccountDeployTransaction(deployPayload));
147
+ if (deployPayload != null) {
148
+ const tx = await this.root.Accounts.getAccountDeployTransaction(deployPayload);
149
+ tx.addedInPrepare = true;
150
+ txs.unshift(tx);
151
+ }
149
152
  }
150
153
  if (!signer.isManagingNoncesInternally) {
151
154
  if (nonce === 0n) {
@@ -160,7 +163,7 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
160
163
  nonce = BigInt(await this.root.provider.getNonceForAddress(signer.getAddress())); //Fetch the nonce
161
164
  if (tx.details.nonce == null)
162
165
  tx.details.nonce = nonce;
163
- this.logger.debug("sendAndConfirm(): transaction prepared (" + (i + 1) + "/" + txs.length + "), nonce: " + tx.details.nonce);
166
+ this.logger.debug("prepareTransactions(): transaction prepared (" + (i + 1) + "/" + txs.length + "), nonce: " + tx.details.nonce);
164
167
  nonce += BigInt(1);
165
168
  }
166
169
  }
@@ -192,7 +195,7 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
192
195
  * of a batch of starknet transactions
193
196
  *
194
197
  * @param signer
195
- * @param txs transactions to send
198
+ * @param _txs transactions to send
196
199
  * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
197
200
  * are re-sent at regular intervals)
198
201
  * @param abortSignal abort signal to abort waiting for transaction confirmations
@@ -200,7 +203,8 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
200
203
  * are executed in order)
201
204
  * @param onBeforePublish a callback called before every transaction is published
202
205
  */
203
- async sendAndConfirm(signer, txs, waitForConfirmation, abortSignal, parallel, onBeforePublish) {
206
+ async sendAndConfirm(signer, _txs, waitForConfirmation, abortSignal, parallel, onBeforePublish) {
207
+ const txs = _txs;
204
208
  await this.prepareTransactions(signer, txs);
205
209
  const signedTxs = [];
206
210
  //Don't separate the signing process from the sending when using browser-based wallet
@@ -208,6 +212,7 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
208
212
  for (let i = 0; i < txs.length; i++) {
209
213
  const tx = txs[i];
210
214
  const signedTx = await signer.signTransaction(tx);
215
+ signedTx.addedInPrepare = tx.addedInPrepare;
211
216
  signedTxs.push(signedTx);
212
217
  this.logger.debug("sendAndConfirm(): transaction signed (" + (i + 1) + "/" + txs.length + "): " + signedTx.txId);
213
218
  const nextAccountNonce = BigInt(signedTx.details.nonce) + 1n;
@@ -224,13 +229,13 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
224
229
  for (let i = 0; i < txs.length; i++) {
225
230
  let tx;
226
231
  if (signer.signTransaction == null) {
227
- const txId = await signer.sendTransaction(txs[i], onBeforePublish);
232
+ const txId = await signer.sendTransaction(txs[i], txs[i].addedInPrepare ? undefined : onBeforePublish);
228
233
  tx = txs[i];
229
234
  tx.txId = txId;
230
235
  }
231
236
  else {
232
237
  const signedTx = signedTxs[i];
233
- await this.sendSignedTransaction(signedTx, onBeforePublish);
238
+ await this.sendSignedTransaction(signedTx, signedTx.addedInPrepare ? undefined : onBeforePublish);
234
239
  tx = signedTx;
235
240
  }
236
241
  if (tx.details.nonce != null) {
@@ -240,9 +245,11 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
240
245
  this.latestPendingNonces[(0, Utils_1.toHex)(tx.details.walletAddress)] = nextAccountNonce;
241
246
  }
242
247
  }
243
- promises.push(this.confirmTransaction(tx, abortSignal));
244
- if (!waitForConfirmation)
245
- txIds.push(tx.txId);
248
+ if (!tx.addedInPrepare) {
249
+ promises.push(this.confirmTransaction(tx, abortSignal));
250
+ if (!waitForConfirmation)
251
+ txIds.push(tx.txId);
252
+ }
246
253
  this.logger.debug("sendAndConfirm(): transaction sent (" + (i + 1) + "/" + txs.length + "): " + tx.txId);
247
254
  if (promises.length >= MAX_UNCONFIRMED_TXS) {
248
255
  if (waitForConfirmation)
@@ -258,13 +265,13 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
258
265
  for (let i = 0; i < txs.length; i++) {
259
266
  let tx;
260
267
  if (signer.signTransaction == null) {
261
- const txId = await signer.sendTransaction(txs[i], onBeforePublish);
268
+ const txId = await signer.sendTransaction(txs[i], txs[i].addedInPrepare ? undefined : onBeforePublish);
262
269
  tx = txs[i];
263
270
  tx.txId = txId;
264
271
  }
265
272
  else {
266
273
  const signedTx = signedTxs[i];
267
- await this.sendSignedTransaction(signedTx, onBeforePublish);
274
+ await this.sendSignedTransaction(signedTx, signedTx.addedInPrepare ? undefined : onBeforePublish);
268
275
  tx = signedTx;
269
276
  }
270
277
  if (tx.details.nonce != null) {
@@ -280,7 +287,8 @@ class StarknetTransactions extends StarknetModule_1.StarknetModule {
280
287
  let txHash = tx.txId;
281
288
  if (i < txs.length - 1 || waitForConfirmation)
282
289
  txHash = await confirmPromise;
283
- txIds.push(txHash);
290
+ if (!tx.addedInPrepare)
291
+ txIds.push(txHash);
284
292
  }
285
293
  }
286
294
  this.logger.info("sendAndConfirm(): sent transactions, count: " + txs.length +
@@ -45,7 +45,8 @@ export declare class StarknetContractEvents<TAbi extends Abi> extends StarknetEv
45
45
  * @param keys
46
46
  * @param processor called for every event, should return a value if the correct event was found, or null
47
47
  * if the search should continue
48
+ * @param startHeight
48
49
  * @param abortSignal
49
50
  */
50
- findInContractEventsForward<T, TEvent extends ExtractAbiEventNames<TAbi>>(events: TEvent[], keys: (string | string[])[], processor: (event: StarknetAbiEvent<TAbi, TEvent>) => Promise<T>, abortSignal?: AbortSignal): Promise<T>;
51
+ findInContractEventsForward<T, TEvent extends ExtractAbiEventNames<TAbi>>(events: TEvent[], keys: (string | string[])[], processor: (event: StarknetAbiEvent<TAbi, TEvent>) => Promise<T>, startHeight?: number, abortSignal?: AbortSignal): Promise<T>;
51
52
  }
@@ -81,9 +81,10 @@ class StarknetContractEvents extends StarknetEvents_1.StarknetEvents {
81
81
  * @param keys
82
82
  * @param processor called for every event, should return a value if the correct event was found, or null
83
83
  * if the search should continue
84
+ * @param startHeight
84
85
  * @param abortSignal
85
86
  */
86
- async findInContractEventsForward(events, keys, processor, abortSignal) {
87
+ async findInContractEventsForward(events, keys, processor, startHeight, abortSignal) {
87
88
  return this.findInEventsForward(this.contract.contract.address, this.toFilter(events, keys), async (events) => {
88
89
  const parsedEvents = this.toStarknetAbiEvents(events);
89
90
  for (let event of parsedEvents) {
@@ -91,7 +92,7 @@ class StarknetContractEvents extends StarknetEvents_1.StarknetEvents {
91
92
  if (result != null)
92
93
  return result;
93
94
  }
94
- }, abortSignal);
95
+ }, startHeight, abortSignal);
95
96
  }
96
97
  }
97
98
  exports.StarknetContractEvents = StarknetContractEvents;
@@ -53,10 +53,13 @@ export declare class StarknetSpvVaultContract extends StarknetContractBase<typeo
53
53
  [btcTxId: string]: string | null;
54
54
  }>;
55
55
  private parseWithdrawalEvent;
56
- getWithdrawalStates(btcTxIds: string[]): Promise<{
56
+ getWithdrawalStates(withdrawalTxs: {
57
+ withdrawal: StarknetSpvWithdrawalData;
58
+ scStartHeight?: number;
59
+ }[]): Promise<{
57
60
  [btcTxId: string]: SpvWithdrawalState;
58
61
  }>;
59
- getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState>;
62
+ getWithdrawalState(withdrawalTx: StarknetSpvWithdrawalData, scStartBlockheight?: number): Promise<SpvWithdrawalState>;
60
63
  getWithdrawalData(btcTx: BtcTx): Promise<StarknetSpvWithdrawalData>;
61
64
  fromOpReturnData(data: Buffer): {
62
65
  recipient: string;
@@ -217,27 +217,34 @@ class StarknetSpvVaultContract extends StarknetContractBase_1.StarknetContractBa
217
217
  return null;
218
218
  }
219
219
  }
220
- async getWithdrawalStates(btcTxIds) {
220
+ async getWithdrawalStates(withdrawalTxs) {
221
221
  const result = {};
222
- btcTxIds.forEach(txId => {
223
- result[txId] = {
222
+ withdrawalTxs.forEach(withdrawalTx => {
223
+ result[withdrawalTx.withdrawal.getTxId()] = {
224
224
  type: base_1.SpvWithdrawalStateType.NOT_FOUND
225
225
  };
226
226
  });
227
- for (let i = 0; i < btcTxIds.length; i += this.Chain.config.maxGetLogKeys) {
228
- const checkBtcTxIds = btcTxIds.slice(i, i + this.Chain.config.maxGetLogKeys);
227
+ const events = ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"];
228
+ for (let i = 0; i < withdrawalTxs.length; i += this.Chain.config.maxGetLogKeys) {
229
+ const checkWithdrawalTxs = withdrawalTxs.slice(i, i + this.Chain.config.maxGetLogKeys);
229
230
  const lows = [];
230
231
  const highs = [];
231
- checkBtcTxIds.forEach(btcTxId => {
232
- const txHash = buffer_1.Buffer.from(btcTxId, "hex").reverse();
232
+ let startHeight = undefined;
233
+ checkWithdrawalTxs.forEach(withdrawalTx => {
234
+ const txHash = buffer_1.Buffer.from(withdrawalTx.withdrawal.getTxId(), "hex").reverse();
233
235
  const txHashU256 = starknet_1.cairo.uint256("0x" + txHash.toString("hex"));
234
236
  lows.push((0, Utils_1.toHex)(txHashU256.low));
235
237
  highs.push((0, Utils_1.toHex)(txHashU256.high));
238
+ if (startHeight !== null) {
239
+ if (withdrawalTx.scStartHeight == null) {
240
+ startHeight = null;
241
+ }
242
+ else {
243
+ startHeight = Math.min(startHeight ?? Infinity, withdrawalTx.scStartHeight);
244
+ }
245
+ }
236
246
  });
237
- await this.Events.findInContractEventsForward(["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"], [
238
- lows,
239
- highs
240
- ], async (event) => {
247
+ await this.Events.findInContractEventsForward(events, [lows, highs], async (event) => {
241
248
  const txId = (0, Utils_1.bigNumberishToBuffer)(event.params.btc_tx_hash, 32).reverse().toString("hex");
242
249
  if (result[txId] == null) {
243
250
  this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${txId}, but transaction not found in input params!`);
@@ -246,24 +253,23 @@ class StarknetSpvVaultContract extends StarknetContractBase_1.StarknetContractBa
246
253
  const eventResult = this.parseWithdrawalEvent(event);
247
254
  if (eventResult != null)
248
255
  result[txId] = eventResult;
249
- });
256
+ }, startHeight);
250
257
  }
251
258
  return result;
252
259
  }
253
- async getWithdrawalState(btcTxId) {
254
- const txHash = buffer_1.Buffer.from(btcTxId, "hex").reverse();
260
+ async getWithdrawalState(withdrawalTx, scStartBlockheight) {
261
+ const txHash = buffer_1.Buffer.from(withdrawalTx.getTxId(), "hex").reverse();
255
262
  const txHashU256 = starknet_1.cairo.uint256("0x" + txHash.toString("hex"));
256
263
  let result = {
257
264
  type: base_1.SpvWithdrawalStateType.NOT_FOUND
258
265
  };
259
- await this.Events.findInContractEventsForward(["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"], [
260
- (0, Utils_1.toHex)(txHashU256.low),
261
- (0, Utils_1.toHex)(txHashU256.high)
262
- ], async (event) => {
266
+ const events = ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"];
267
+ const keys = [(0, Utils_1.toHex)(txHashU256.low), (0, Utils_1.toHex)(txHashU256.high)];
268
+ await this.Events.findInContractEventsForward(events, keys, async (event) => {
263
269
  const eventResult = this.parseWithdrawalEvent(event);
264
270
  if (eventResult != null)
265
271
  result = eventResult;
266
- });
272
+ }, scStartBlockheight);
267
273
  return result;
268
274
  }
269
275
  getWithdrawalData(btcTx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-starknet",
3
- "version": "4.0.0-dev.35",
3
+ "version": "4.0.0-dev.37",
4
4
  "description": "Starknet specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "url": "git+https://github.com/atomiqlabs/atomiq-chain-starknet.git"
29
29
  },
30
30
  "dependencies": {
31
- "@atomiqlabs/base": "^10.0.0-dev.18",
31
+ "@atomiqlabs/base": "^10.0.0-dev.20",
32
32
  "@noble/hashes": "^1.7.1",
33
33
  "@scure/btc-signer": "^1.6.0",
34
34
  "abi-wan-kanabi": "2.2.4",
@@ -151,6 +151,14 @@ export class StarknetChainInterface implements ChainInterface<StarknetTx, Starkn
151
151
  return this.Transactions.getTxStatus(tx);
152
152
  }
153
153
 
154
+ async getFinalizedBlock(): Promise<{ height: number; blockHash: string }> {
155
+ const block = await this.Blocks.getBlock("l1_accepted");
156
+ return {
157
+ height: block.block_number as number,
158
+ blockHash: block.block_hash as string
159
+ }
160
+ }
161
+
154
162
  txsTransfer(signer: string, token: string, amount: bigint, dstAddress: string, feeRate?: string): Promise<StarknetTx[]> {
155
163
  return this.Tokens.txsTransfer(signer, token, amount, dstAddress, feeRate);
156
164
  }
@@ -1,7 +1,8 @@
1
1
  import {StarknetModule} from "../StarknetModule";
2
+ import {BlockWithTxHashes} from "starknet";
2
3
 
3
4
  // https://github.com/starkware-libs/starknet-specs/blob/c2e93098b9c2ca0423b7f4d15b201f52f22d8c36/api/starknet_api_openrpc.json#L1234
4
- export type StarknetBlockTag = "pre_confirmed" | "latest";
5
+ export type StarknetBlockTag = "pre_confirmed" | "latest" | "l1_accepted";
5
6
 
6
7
  export class StarknetBlocks extends StarknetModule {
7
8
 
@@ -9,7 +10,7 @@ export class StarknetBlocks extends StarknetModule {
9
10
 
10
11
  private blockCache: {
11
12
  [key: string]: {
12
- blockTime: Promise<number>,
13
+ block: Promise<BlockWithTxHashes>,
13
14
  timestamp: number
14
15
  }
15
16
  } = {};
@@ -21,23 +22,23 @@ export class StarknetBlocks extends StarknetModule {
21
22
  * @param blockTag
22
23
  */
23
24
  private fetchAndSaveBlockTime(blockTag: StarknetBlockTag | number): {
24
- blockTime: Promise<number>,
25
+ block: Promise<BlockWithTxHashes>,
25
26
  timestamp: number
26
27
  } {
27
28
  const blockTagStr = blockTag.toString(10);
28
29
 
29
- const blockTimePromise = this.provider.getBlockWithTxHashes(blockTag).then(result => result.timestamp);
30
+ const blockPromise = this.provider.getBlockWithTxHashes(blockTag);
30
31
  const timestamp = Date.now();
31
32
  this.blockCache[blockTagStr] = {
32
- blockTime: blockTimePromise,
33
+ block: blockPromise,
33
34
  timestamp
34
35
  };
35
- blockTimePromise.catch(e => {
36
- if(this.blockCache[blockTagStr]!=null && this.blockCache[blockTagStr].blockTime===blockTimePromise) delete this.blockCache[blockTagStr];
36
+ blockPromise.catch(e => {
37
+ if(this.blockCache[blockTagStr]!=null && this.blockCache[blockTagStr].block===blockPromise) delete this.blockCache[blockTagStr];
37
38
  throw e;
38
39
  })
39
40
  return {
40
- blockTime: blockTimePromise,
41
+ block: blockPromise,
41
42
  timestamp
42
43
  };
43
44
  }
@@ -62,7 +63,7 @@ export class StarknetBlocks extends StarknetModule {
62
63
  *
63
64
  * @param blockTag
64
65
  */
65
- public getBlockTime(blockTag: StarknetBlockTag | number): Promise<number> {
66
+ public getBlock(blockTag: StarknetBlockTag | number): Promise<BlockWithTxHashes> {
66
67
  this.cleanupBlocks();
67
68
  let cachedBlockData = this.blockCache[blockTag.toString(10)];
68
69
 
@@ -70,7 +71,17 @@ export class StarknetBlocks extends StarknetModule {
70
71
  cachedBlockData = this.fetchAndSaveBlockTime(blockTag);
71
72
  }
72
73
 
73
- return cachedBlockData.blockTime;
74
+ return cachedBlockData.block;
75
+ }
76
+
77
+ /**
78
+ * Gets the block for a given blocktag, with caching
79
+ *
80
+ * @param blockTag
81
+ */
82
+ public async getBlockTime(blockTag: StarknetBlockTag | number): Promise<number> {
83
+ const block = await this.getBlock(blockTag);
84
+ return block.timestamp;
74
85
  }
75
86
 
76
87
  }
@@ -76,12 +76,14 @@ export class StarknetEvents extends StarknetModule {
76
76
  * @param keys
77
77
  * @param processor called for every batch of returned signatures, should return a value if the correct signature
78
78
  * was found, or null if the search should continue
79
+ * @param startHeight
79
80
  * @param abortSignal
80
81
  * @param logFetchLimit
81
82
  */
82
83
  public async findInEventsForward<T>(
83
84
  contract: string, keys: string[][],
84
85
  processor: (signatures: StarknetEvent[]) => Promise<T>,
86
+ startHeight?: number,
85
87
  abortSignal?: AbortSignal,
86
88
  logFetchLimit?: number
87
89
  ): Promise<T> {
@@ -90,6 +92,7 @@ export class StarknetEvents extends StarknetModule {
90
92
  while(eventsResult==null || eventsResult?.continuation_token!=null) {
91
93
  eventsResult = await this.root.provider.getEvents({
92
94
  address: contract,
95
+ from_block: startHeight==null ? undefined : {block_number: startHeight},
93
96
  to_block: "latest",
94
97
  keys,
95
98
  chunk_size: logFetchLimit ?? this.EVENTS_LIMIT,
@@ -167,7 +167,7 @@ export class StarknetTransactions extends StarknetModule {
167
167
  * @param txs
168
168
  * @private
169
169
  */
170
- private async prepareTransactions(signer: StarknetSigner, txs: StarknetTx[]): Promise<void> {
170
+ private async prepareTransactions(signer: StarknetSigner, txs: (StarknetTx & {addedInPrepare?: boolean})[]): Promise<void> {
171
171
  let nonce: bigint = await this.getNonce(signer.getAddress());
172
172
  const latestPendingNonce = this.latestPendingNonces[toHex(signer.getAddress())];
173
173
  if(latestPendingNonce!=null && latestPendingNonce > nonce) {
@@ -178,7 +178,11 @@ export class StarknetTransactions extends StarknetModule {
178
178
  //Add deploy account tx
179
179
  if(nonce===0n) {
180
180
  const deployPayload = await signer.getDeployPayload();
181
- if(deployPayload!=null) txs.unshift(await this.root.Accounts.getAccountDeployTransaction(deployPayload));
181
+ if(deployPayload!=null) {
182
+ const tx: (StarknetTx & {addedInPrepare?: boolean}) = await this.root.Accounts.getAccountDeployTransaction(deployPayload);
183
+ tx.addedInPrepare = true;
184
+ txs.unshift(tx);
185
+ }
182
186
  }
183
187
 
184
188
  if(!signer.isManagingNoncesInternally) {
@@ -193,7 +197,7 @@ export class StarknetTransactions extends StarknetModule {
193
197
  if(nonce==null) nonce = BigInt(await this.root.provider.getNonceForAddress(signer.getAddress())); //Fetch the nonce
194
198
  if(tx.details.nonce==null) tx.details.nonce = nonce;
195
199
 
196
- this.logger.debug("sendAndConfirm(): transaction prepared ("+(i+1)+"/"+txs.length+"), nonce: "+tx.details.nonce);
200
+ this.logger.debug("prepareTransactions(): transaction prepared ("+(i+1)+"/"+txs.length+"), nonce: "+tx.details.nonce);
197
201
 
198
202
  nonce += BigInt(1);
199
203
  }
@@ -231,7 +235,7 @@ export class StarknetTransactions extends StarknetModule {
231
235
  * of a batch of starknet transactions
232
236
  *
233
237
  * @param signer
234
- * @param txs transactions to send
238
+ * @param _txs transactions to send
235
239
  * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
236
240
  * are re-sent at regular intervals)
237
241
  * @param abortSignal abort signal to abort waiting for transaction confirmations
@@ -239,14 +243,16 @@ export class StarknetTransactions extends StarknetModule {
239
243
  * are executed in order)
240
244
  * @param onBeforePublish a callback called before every transaction is published
241
245
  */
242
- public async sendAndConfirm(signer: StarknetSigner, txs: StarknetTx[], waitForConfirmation?: boolean, abortSignal?: AbortSignal, parallel?: boolean, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<string[]> {
246
+ public async sendAndConfirm(signer: StarknetSigner, _txs: StarknetTx[], waitForConfirmation?: boolean, abortSignal?: AbortSignal, parallel?: boolean, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<string[]> {
247
+ const txs: (StarknetTx & {addedInPrepare?: boolean})[] = _txs;
243
248
  await this.prepareTransactions(signer, txs);
244
- const signedTxs: StarknetTx[] = [];
249
+ const signedTxs: (StarknetTx & {addedInPrepare?: boolean})[] = [];
245
250
 
246
251
  //Don't separate the signing process from the sending when using browser-based wallet
247
252
  if(signer.signTransaction!=null) for(let i=0;i<txs.length;i++) {
248
253
  const tx = txs[i];
249
- const signedTx = await signer.signTransaction(tx);
254
+ const signedTx: (StarknetTx & {addedInPrepare?: boolean}) = await signer.signTransaction(tx);
255
+ signedTx.addedInPrepare = tx.addedInPrepare;
250
256
  signedTxs.push(signedTx);
251
257
  this.logger.debug("sendAndConfirm(): transaction signed ("+(i+1)+"/"+txs.length+"): "+signedTx.txId);
252
258
 
@@ -264,14 +270,14 @@ export class StarknetTransactions extends StarknetModule {
264
270
  if(parallel) {
265
271
  let promises: Promise<string>[] = [];
266
272
  for(let i=0;i<txs.length;i++) {
267
- let tx: StarknetTx;
273
+ let tx: (StarknetTx & {addedInPrepare?: boolean});
268
274
  if(signer.signTransaction==null) {
269
- const txId = await signer.sendTransaction(txs[i], onBeforePublish);
275
+ const txId = await signer.sendTransaction(txs[i], txs[i].addedInPrepare ? undefined : onBeforePublish);
270
276
  tx = txs[i];
271
277
  tx.txId = txId;
272
278
  } else {
273
279
  const signedTx = signedTxs[i];
274
- await this.sendSignedTransaction(signedTx, onBeforePublish);
280
+ await this.sendSignedTransaction(signedTx, signedTx.addedInPrepare ? undefined : onBeforePublish);
275
281
  tx = signedTx;
276
282
  }
277
283
 
@@ -283,8 +289,10 @@ export class StarknetTransactions extends StarknetModule {
283
289
  }
284
290
  }
285
291
 
286
- promises.push(this.confirmTransaction(tx, abortSignal));
287
- if(!waitForConfirmation) txIds.push(tx.txId);
292
+ if(!tx.addedInPrepare) {
293
+ promises.push(this.confirmTransaction(tx, abortSignal));
294
+ if(!waitForConfirmation) txIds.push(tx.txId);
295
+ }
288
296
  this.logger.debug("sendAndConfirm(): transaction sent ("+(i+1)+"/"+txs.length+"): "+tx.txId);
289
297
  if(promises.length >= MAX_UNCONFIRMED_TXS) {
290
298
  if(waitForConfirmation) txIds.push(...await Promise.all(promises));
@@ -296,14 +304,14 @@ export class StarknetTransactions extends StarknetModule {
296
304
  }
297
305
  } else {
298
306
  for(let i=0;i<txs.length;i++) {
299
- let tx: StarknetTx;
307
+ let tx: (StarknetTx & {addedInPrepare?: boolean});
300
308
  if(signer.signTransaction==null) {
301
- const txId = await signer.sendTransaction(txs[i], onBeforePublish);
309
+ const txId = await signer.sendTransaction(txs[i], txs[i].addedInPrepare ? undefined : onBeforePublish);
302
310
  tx = txs[i];
303
311
  tx.txId = txId;
304
312
  } else {
305
313
  const signedTx = signedTxs[i];
306
- await this.sendSignedTransaction(signedTx, onBeforePublish);
314
+ await this.sendSignedTransaction(signedTx, signedTx.addedInPrepare ? undefined : onBeforePublish);
307
315
  tx = signedTx;
308
316
  }
309
317
 
@@ -320,7 +328,7 @@ export class StarknetTransactions extends StarknetModule {
320
328
  //Don't await the last promise when !waitForConfirmation
321
329
  let txHash = tx.txId;
322
330
  if(i<txs.length-1 || waitForConfirmation) txHash = await confirmPromise;
323
- txIds.push(txHash);
331
+ if(!tx.addedInPrepare) txIds.push(txHash);
324
332
  }
325
333
  }
326
334
 
@@ -113,12 +113,14 @@ export class StarknetContractEvents<TAbi extends Abi> extends StarknetEvents {
113
113
  * @param keys
114
114
  * @param processor called for every event, should return a value if the correct event was found, or null
115
115
  * if the search should continue
116
+ * @param startHeight
116
117
  * @param abortSignal
117
118
  */
118
119
  public async findInContractEventsForward<T, TEvent extends ExtractAbiEventNames<TAbi>>(
119
120
  events: TEvent[],
120
121
  keys: (string | string[])[],
121
122
  processor: (event: StarknetAbiEvent<TAbi, TEvent>) => Promise<T>,
123
+ startHeight?: number,
122
124
  abortSignal?: AbortSignal
123
125
  ) {
124
126
  return this.findInEventsForward<T>(this.contract.contract.address, this.toFilter(events, keys), async (events: StarknetEvent[]) => {
@@ -127,7 +129,7 @@ export class StarknetContractEvents<TAbi extends Abi> extends StarknetEvents {
127
129
  const result: T = await processor(event);
128
130
  if(result!=null) return result;
129
131
  }
130
- }, abortSignal);
132
+ }, startHeight, abortSignal);
131
133
  }
132
134
 
133
135
  }
@@ -6,7 +6,7 @@ import {
6
6
  SpvVaultTokenData,
7
7
  SpvWithdrawalState,
8
8
  SpvWithdrawalStateType,
9
- SpvWithdrawalTransactionData, SwapCommitState,
9
+ SpvWithdrawalTransactionData,
10
10
  TransactionConfirmationOptions
11
11
  } from "@atomiqlabs/base";
12
12
  import {Buffer} from "buffer";
@@ -297,30 +297,38 @@ export class StarknetSpvVaultContract
297
297
  }
298
298
  }
299
299
 
300
- async getWithdrawalStates(btcTxIds: string[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
300
+ async getWithdrawalStates(withdrawalTxs: {withdrawal: StarknetSpvWithdrawalData, scStartHeight?: number}[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
301
301
  const result: {[btcTxId: string]: SpvWithdrawalState} = {};
302
- btcTxIds.forEach(txId => {
303
- result[txId] = {
302
+ withdrawalTxs.forEach(withdrawalTx => {
303
+ result[withdrawalTx.withdrawal.getTxId()] = {
304
304
  type: SpvWithdrawalStateType.NOT_FOUND
305
305
  };
306
306
  });
307
307
 
308
- for(let i=0;i<btcTxIds.length;i+=this.Chain.config.maxGetLogKeys) {
309
- const checkBtcTxIds = btcTxIds.slice(i, i+this.Chain.config.maxGetLogKeys);
308
+ const events: ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"] =
309
+ ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"];
310
+
311
+ for(let i=0;i<withdrawalTxs.length;i+=this.Chain.config.maxGetLogKeys) {
312
+ const checkWithdrawalTxs = withdrawalTxs.slice(i, i+this.Chain.config.maxGetLogKeys);
310
313
  const lows: string[] = [];
311
314
  const highs: string[] = [];
312
- checkBtcTxIds.forEach(btcTxId => {
313
- const txHash = Buffer.from(btcTxId, "hex").reverse();
315
+ let startHeight: number = undefined;
316
+ checkWithdrawalTxs.forEach(withdrawalTx => {
317
+ const txHash = Buffer.from(withdrawalTx.withdrawal.getTxId(), "hex").reverse();
314
318
  const txHashU256 = cairo.uint256("0x"+txHash.toString("hex"));
315
319
  lows.push(toHex(txHashU256.low));
316
320
  highs.push(toHex(txHashU256.high));
321
+ if(startHeight!==null) {
322
+ if(withdrawalTx.scStartHeight==null) {
323
+ startHeight = null;
324
+ } else {
325
+ startHeight = Math.min(startHeight ?? Infinity, withdrawalTx.scStartHeight);
326
+ }
327
+ }
317
328
  });
329
+
318
330
  await this.Events.findInContractEventsForward(
319
- ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"],
320
- [
321
- lows,
322
- highs
323
- ],
331
+ events,[lows, highs],
324
332
  async (event) => {
325
333
  const txId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex");
326
334
  if(result[txId]==null) {
@@ -329,29 +337,31 @@ export class StarknetSpvVaultContract
329
337
  }
330
338
  const eventResult = this.parseWithdrawalEvent(event);
331
339
  if(eventResult!=null) result[txId] = eventResult;
332
- }
340
+ },
341
+ startHeight
333
342
  );
334
343
  }
335
344
 
336
345
  return result;
337
346
  }
338
347
 
339
- async getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState> {
340
- const txHash = Buffer.from(btcTxId, "hex").reverse();
348
+ async getWithdrawalState(withdrawalTx: StarknetSpvWithdrawalData, scStartBlockheight?: number): Promise<SpvWithdrawalState> {
349
+ const txHash = Buffer.from(withdrawalTx.getTxId(), "hex").reverse();
341
350
  const txHashU256 = cairo.uint256("0x"+txHash.toString("hex"));
342
351
  let result: SpvWithdrawalState = {
343
352
  type: SpvWithdrawalStateType.NOT_FOUND
344
353
  };
354
+ const events: ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"] =
355
+ ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"];
356
+ const keys = [toHex(txHashU256.low), toHex(txHashU256.high)];
357
+
345
358
  await this.Events.findInContractEventsForward(
346
- ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"],
347
- [
348
- toHex(txHashU256.low),
349
- toHex(txHashU256.high)
350
- ],
359
+ events, keys,
351
360
  async (event) => {
352
361
  const eventResult = this.parseWithdrawalEvent(event);
353
362
  if(eventResult!=null) result = eventResult;
354
- }
363
+ },
364
+ scStartBlockheight
355
365
  );
356
366
  return result;
357
367
  }