@alephium/web3 0.45.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/address/address.js +7 -2
- package/dist/src/api/api-alephium.d.ts +154 -1
- package/dist/src/api/api-alephium.js +15 -1
- package/dist/src/api/api-explorer.d.ts +51 -0
- package/dist/src/api/api-explorer.js +26 -0
- package/dist/src/block/block.d.ts +12 -13
- package/dist/src/block/block.js +94 -53
- package/package.json +3 -3
- package/src/address/address.ts +6 -2
- package/src/api/api-alephium.ts +162 -1
- package/src/api/api-explorer.ts +74 -0
- package/src/block/block.ts +108 -65
package/src/api/api-explorer.ts
CHANGED
|
@@ -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
|
*
|
package/src/block/block.ts
CHANGED
|
@@ -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
|
-
|
|
25
|
+
const DEFAULT_INTERVAL = 60 * 1000 // 60 seconds
|
|
26
|
+
const EXPIRE_DURATION = 20 * 1000 // 20 seconds
|
|
25
27
|
|
|
26
|
-
export
|
|
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) +
|
|
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(
|
|
44
|
-
console.info(
|
|
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
|
|
48
|
-
|
|
49
|
-
let
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
87
|
-
private
|
|
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
|
-
|
|
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.
|
|
102
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.
|
|
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
|
}
|