@alephium/web3 0.44.0 → 1.0.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.
@@ -97,6 +97,23 @@ export interface BlockEntryLite {
97
97
  hashRate: string
98
98
  }
99
99
 
100
+ export interface ContractLiveness {
101
+ /** @format address */
102
+ parent?: string
103
+ creation: ContractLivenessLocation
104
+ destruction?: ContractLivenessLocation
105
+ interfaceId?: StdInterfaceId
106
+ }
107
+
108
+ export interface ContractLivenessLocation {
109
+ /** @format block-hash */
110
+ blockHash: string
111
+ /** @format 32-byte-hash */
112
+ txHash: string
113
+ /** @format int64 */
114
+ timestamp: number
115
+ }
116
+
100
117
  export interface ContractOutput {
101
118
  /** @format int32 */
102
119
  hint: number
@@ -140,6 +157,10 @@ export interface ExplorerInfo {
140
157
  lastFinalizedInputTime: number
141
158
  }
142
159
 
160
+ export interface FungibleToken {
161
+ type: string
162
+ }
163
+
143
164
  export interface FungibleTokenMetadata {
144
165
  /** @format 32-byte-hash */
145
166
  id: string
@@ -207,12 +228,24 @@ export interface MempoolTransaction {
207
228
  lastSeen: number
208
229
  }
209
230
 
231
+ export interface NFT {
232
+ type: string
233
+ }
234
+
235
+ export interface NFTCollection {
236
+ type: string
237
+ }
238
+
210
239
  export interface NFTCollectionMetadata {
211
240
  /** @format address */
212
241
  address: string
213
242
  collectionUri: string
214
243
  }
215
244
 
245
+ export interface NFTCollectionWithRoyalty {
246
+ type: string
247
+ }
248
+
216
249
  export interface NFTMetadata {
217
250
  /** @format 32-byte-hash */
218
251
  id: string
@@ -223,6 +256,10 @@ export interface NFTMetadata {
223
256
  nftIndex: string
224
257
  }
225
258
 
259
+ export interface NonStandard {
260
+ type: string
261
+ }
262
+
226
263
  export interface NotFound {
227
264
  detail: string
228
265
  resource: string
@@ -296,6 +333,8 @@ export interface ServiceUnavailable {
296
333
  detail: string
297
334
  }
298
335
 
336
+ export type StdInterfaceId = FungibleToken | NFT | NFTCollection | NFTCollectionWithRoyalty | NonStandard | Unknown
337
+
299
338
  export interface SubContracts {
300
339
  subContracts?: string[]
301
340
  }
@@ -377,6 +416,11 @@ export interface Unauthorized {
377
416
  detail: string
378
417
  }
379
418
 
419
+ export interface Unknown {
420
+ id: string
421
+ type: string
422
+ }
423
+
380
424
  export type Val = ValAddress | ValArray | ValBool | ValByteVec | ValI256 | ValU256
381
425
 
382
426
  export interface ValAddress {
@@ -988,6 +1032,21 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
988
1032
  ...params
989
1033
  }).then(convertHttpResponse),
990
1034
 
1035
+ /**
1036
+ * @description Get public key of p2pkh addresses, the address needs to have at least one input.
1037
+ *
1038
+ * @tags Addresses
1039
+ * @name GetAddressesAddressPublicKey
1040
+ * @request GET:/addresses/{address}/public-key
1041
+ */
1042
+ getAddressesAddressPublicKey: (address: string, params: RequestParams = {}) =>
1043
+ this.request<string, BadRequest | Unauthorized | NotFound | InternalServerError | ServiceUnavailable>({
1044
+ path: `/addresses/${address}/public-key`,
1045
+ method: 'GET',
1046
+ format: 'json',
1047
+ ...params
1048
+ }).then(convertHttpResponse),
1049
+
991
1050
  /**
992
1051
  * @description Get address tokens with balance
993
1052
  *
@@ -1674,6 +1733,21 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
1674
1733
  }).then(convertHttpResponse)
1675
1734
  }
1676
1735
  contracts = {
1736
+ /**
1737
+ * @description Get contract liveness
1738
+ *
1739
+ * @tags Contracts
1740
+ * @name GetContractsContractAddressCurrentLiveness
1741
+ * @request GET:/contracts/{contract_address}/current-liveness
1742
+ */
1743
+ getContractsContractAddressCurrentLiveness: (contractAddress: string, params: RequestParams = {}) =>
1744
+ this.request<ContractLiveness, BadRequest | Unauthorized | NotFound | InternalServerError | ServiceUnavailable>({
1745
+ path: `/contracts/${contractAddress}/current-liveness`,
1746
+ method: 'GET',
1747
+ format: 'json',
1748
+ ...params
1749
+ }).then(convertHttpResponse),
1750
+
1677
1751
  /**
1678
1752
  * @description Get contract parent address if exist
1679
1753
  *
@@ -20,94 +20,90 @@ import { Subscription, SubscribeOptions } from '../utils/subscription'
20
20
  import * as node from '../api/api-alephium'
21
21
  import { NodeProvider } from '../api'
22
22
  import * as web3 from '../global'
23
+ import { TOTAL_NUMBER_OF_CHAINS } from '../constants'
23
24
 
24
- export type ReorgCallback = (orphanBlocks: node.BlockEntry[], newBlocks: node.BlockEntry[]) => Promise<void> | void
25
+ const DEFAULT_INTERVAL = 60 * 1000 // 60 seconds
26
+ const EXPIRE_DURATION = 20 * 1000 // 20 seconds
25
27
 
26
- export interface BlockSubscribeOptions extends SubscribeOptions<node.BlockEntry> {
28
+ export type ReorgCallback = (
29
+ fromGroup: number,
30
+ toGroup: number,
31
+ orphanBlocks: node.BlockEntry[],
32
+ newBlocks: node.BlockEntry[]
33
+ ) => Promise<void> | void
34
+
35
+ export interface BlockSubscribeOptions extends SubscribeOptions<node.BlockEntry[]> {
27
36
  reorgCallback?: ReorgCallback
28
37
  }
29
38
 
30
- export abstract class BlockSubscriptionBase extends Subscription<node.BlockEntry> {
39
+ export abstract class BlockSubscriptionBase extends Subscription<node.BlockEntry[]> {
31
40
  abstract readonly reorgCallback?: ReorgCallback
32
- abstract readonly fromGroup: number
33
- abstract readonly toGroup: number
34
41
 
35
- abstract getHashesAtHeight(height: number): Promise<string[]>
42
+ abstract getHashesAtHeight(fromGroup: number, toGroup: number, height: number): Promise<string[]>
36
43
  abstract getBlockByHash(hash: string): Promise<node.BlockEntry>
37
44
 
38
45
  protected getParentHash(block: node.BlockEntry): string {
39
- const index = Math.floor(block.deps.length / 2) + this.toGroup
46
+ const index = Math.floor(block.deps.length / 2) + block.chainTo
40
47
  return block.deps[index]
41
48
  }
42
49
 
43
- protected async handleReorg(blockHash: string, blockHeight: number) {
44
- console.info(`reorg occur, hash: ${blockHash}, height: ${blockHeight}`)
50
+ protected async handleReorg(fromGroup: number, toGroup: number, orphanBlockHash: string, newBlockHash: string) {
51
+ console.info(
52
+ `reorg occur in chain ${fromGroup} -> ${toGroup}, orphan hash: ${orphanBlockHash}, new hash: ${newBlockHash}`
53
+ )
45
54
  if (this.reorgCallback === undefined) return
46
55
 
47
- const orphans: string[] = []
48
- const newHashes: string[] = []
49
- let fromHash = blockHash
50
- let fromHeight = blockHeight
56
+ const orphanBlocks: node.BlockEntry[] = []
57
+ let fromHash = orphanBlockHash
58
+ let canonicalHash: string | undefined = undefined
51
59
  while (true) {
52
- const hashes = await this.getHashesAtHeight(fromHeight)
53
- const canonicalHash = hashes[0]
54
- if (canonicalHash !== fromHash) {
55
- orphans.push(fromHash)
56
- newHashes.push(canonicalHash)
57
- const block = await this.getBlockByHash(fromHash)
58
- fromHash = this.getParentHash(block)
59
- fromHeight -= 1
60
- } else {
60
+ const orphanBlock = await this.getBlockByHash(fromHash)
61
+ orphanBlocks.push(orphanBlock)
62
+ const hashes = await this.getHashesAtHeight(fromGroup, toGroup, orphanBlock.height - 1)
63
+ const parentHash = this.getParentHash(orphanBlock)
64
+ if (hashes[0] === parentHash) {
65
+ canonicalHash = hashes[0]
61
66
  break
62
67
  }
63
- }
64
-
65
- const orphanBlocks: node.BlockEntry[] = []
66
- for (const hash of orphans.reverse()) {
67
- const block = await this.getBlockByHash(hash)
68
- orphanBlocks.push(block)
68
+ fromHash = parentHash
69
69
  }
70
70
 
71
71
  const newBlocks: node.BlockEntry[] = []
72
- for (const hash of newHashes.reverse()) {
73
- const block = await this.getBlockByHash(hash)
74
- newBlocks.push(block)
72
+ fromHash = newBlockHash
73
+ while (fromHash !== canonicalHash) {
74
+ const newBlock = await this.getBlockByHash(fromHash)
75
+ newBlocks.push(newBlock)
76
+ fromHash = this.getParentHash(newBlock)
75
77
  }
76
- console.info(`orphan hashes: ${orphanBlocks.map((b) => b.hash)}, new hashes: ${newBlocks.map((b) => b.hash)}`)
77
- await this.reorgCallback(orphanBlocks, newBlocks)
78
+ const orphans = orphanBlocks.reverse()
79
+ const news = newBlocks.reverse()
80
+ console.info(`orphan hashes: ${orphans.map((b) => b.hash)}, new hashes: ${news.map((b) => b.hash)}`)
81
+ await this.reorgCallback(fromGroup, toGroup, orphans, news)
78
82
  }
79
83
  }
80
84
 
81
85
  export class BlockSubscription extends BlockSubscriptionBase {
82
86
  readonly nodeProvider: NodeProvider
83
- readonly fromGroup: number
84
- readonly toGroup: number
85
87
  readonly reorgCallback?: ReorgCallback
86
- private currentBlockHeight: number
87
- private parentBlockHash: string | undefined
88
+ private fromTimeStamp: number
89
+ private parents: ({ hash: string; height: number } | undefined)[]
90
+ private cache: Map<string, number>
88
91
 
89
92
  constructor(
90
93
  options: BlockSubscribeOptions,
91
- fromGroup: number,
92
- toGroup: number,
93
- fromBlockHeight: number,
94
+ fromTimeStamp: number,
94
95
  nodeProvider: NodeProvider | undefined = undefined
95
96
  ) {
96
97
  super(options)
97
98
  this.nodeProvider = nodeProvider ?? web3.getCurrentNodeProvider()
98
- this.fromGroup = fromGroup
99
- this.toGroup = toGroup
100
99
  this.reorgCallback = options.reorgCallback
101
- this.currentBlockHeight = fromBlockHeight
102
- this.parentBlockHash = undefined
100
+ this.fromTimeStamp = fromTimeStamp
101
+ this.parents = new Array(TOTAL_NUMBER_OF_CHAINS).fill(undefined)
102
+ this.cache = new Map()
103
103
  }
104
104
 
105
- override async getHashesAtHeight(height: number): Promise<string[]> {
106
- const result = await this.nodeProvider.blockflow.getBlockflowHashes({
107
- fromGroup: this.fromGroup,
108
- toGroup: this.toGroup,
109
- height
110
- })
105
+ override async getHashesAtHeight(fromGroup: number, toGroup: number, height: number): Promise<string[]> {
106
+ const result = await this.nodeProvider.blockflow.getBlockflowHashes({ fromGroup, toGroup, height })
111
107
  return result.headers
112
108
  }
113
109
 
@@ -115,25 +111,72 @@ export class BlockSubscription extends BlockSubscriptionBase {
115
111
  return await this.nodeProvider.blockflow.getBlockflowBlocksBlockHash(hash)
116
112
  }
117
113
 
118
- override async polling(): Promise<void> {
114
+ private async getMissingBlocksAndHandleReorg(fromHash: string, fromHeight: number, toBlock: node.BlockEntry) {
115
+ const blocks: node.BlockEntry[] = []
116
+ let lastBlock = toBlock
117
+ while (lastBlock.height - 1 > fromHeight) {
118
+ const parentHash = this.getParentHash(lastBlock)
119
+ const block = await this.getBlockByHash(parentHash)
120
+ blocks.push(block)
121
+ lastBlock = block
122
+ }
123
+ const parentHash = this.getParentHash(lastBlock)
124
+ if (parentHash !== fromHash) {
125
+ await this.handleReorg(toBlock.chainFrom, toBlock.chainTo, fromHash, parentHash)
126
+ }
127
+ return blocks.reverse()
128
+ }
129
+
130
+ private async handleBlocks(blocks: node.BlockEntry[][], now: number) {
131
+ const allBlocks: node.BlockEntry[] = []
132
+ for (let index = 0; index < blocks.length; index += 1) {
133
+ const blocksPerChain = blocks[index].filter((b) => !this.cache.has(b.hash))
134
+ if (blocksPerChain.length === 0) continue
135
+
136
+ allBlocks.push(...blocksPerChain)
137
+ const parent = this.parents[index]
138
+ if (parent !== undefined) {
139
+ const missingBlocks = await this.getMissingBlocksAndHandleReorg(parent.hash, parent.height, blocksPerChain[0])
140
+ allBlocks.push(...missingBlocks)
141
+ }
142
+ const latestBlock = blocksPerChain[blocksPerChain.length - 1]
143
+ this.parents[index] = { hash: latestBlock.hash, height: latestBlock.height }
144
+ }
145
+
146
+ const sortedBlocks = allBlocks.sort((a, b) => a.timestamp - b.timestamp)
119
147
  try {
120
- const chainInfo = await this.nodeProvider.blockflow.getBlockflowChainInfo({
121
- fromGroup: this.fromGroup,
122
- toGroup: this.toGroup
148
+ await this.messageCallback(sortedBlocks)
149
+ } finally {
150
+ const threshold = now - EXPIRE_DURATION
151
+ Array.from(this.cache.entries()).forEach(([hash, ts]) => {
152
+ if (ts < threshold) this.cache.delete(hash)
123
153
  })
154
+ const index = sortedBlocks.findIndex((b) => b.timestamp >= threshold)
155
+ if (index !== -1) {
156
+ sortedBlocks.slice(index).forEach((b) => this.cache.set(b.hash, b.timestamp))
157
+ }
158
+ }
159
+ }
124
160
 
125
- while (this.currentBlockHeight <= chainInfo.currentHeight) {
126
- const hashes = await this.getHashesAtHeight(this.currentBlockHeight)
127
- const block = await this.getBlockByHash(hashes[0])
128
- if (this.parentBlockHash !== undefined && this.getParentHash(block) !== this.parentBlockHash) {
129
- await this.handleReorg(this.parentBlockHash, this.currentBlockHeight - 1)
130
- }
131
- await this.messageCallback(block)
132
- this.currentBlockHeight += 1
133
- this.parentBlockHash = hashes[0]
161
+ override async polling(): Promise<void> {
162
+ const now = Date.now()
163
+ if (this.fromTimeStamp >= now) return
164
+
165
+ while (this.fromTimeStamp < now) {
166
+ if (this.isCancelled()) return
167
+ const toTs = Math.min(this.fromTimeStamp + DEFAULT_INTERVAL, now)
168
+ try {
169
+ const result = await this.nodeProvider.blockflow.getBlockflowBlocks({ fromTs: this.fromTimeStamp, toTs })
170
+ await this.handleBlocks(result.blocks, now)
171
+ } catch (err) {
172
+ await this.errorCallback(err, this)
173
+ }
174
+
175
+ if (this.fromTimeStamp + EXPIRE_DURATION < now) {
176
+ this.fromTimeStamp = Math.min(toTs + 1, now - EXPIRE_DURATION)
177
+ } else {
178
+ return
134
179
  }
135
- } catch (err) {
136
- await this.errorCallback(err, this)
137
180
  }
138
181
  }
139
182
  }