@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.
@@ -151,7 +151,7 @@ class HttpClient {
151
151
  exports.HttpClient = HttpClient;
152
152
  /**
153
153
  * @title Alephium API
154
- * @version 2.14.0
154
+ * @version 3.1.0
155
155
  * @baseUrl ../
156
156
  */
157
157
  class Api extends HttpClient {
@@ -675,6 +675,20 @@ class Api extends HttpClient {
675
675
  format: 'json',
676
676
  ...params
677
677
  }).then(utils_1.convertHttpResponse),
678
+ /**
679
+ * No description
680
+ *
681
+ * @tags Blockflow
682
+ * @name GetBlockflowMainChainBlockByGhostUncleGhostUncleHash
683
+ * @summary Get a mainchain block by ghost uncle hash
684
+ * @request GET:/blockflow/main-chain-block-by-ghost-uncle/{ghost_uncle_hash}
685
+ */
686
+ getBlockflowMainChainBlockByGhostUncleGhostUncleHash: (ghostUncleHash, params = {}) => this.request({
687
+ path: `/blockflow/main-chain-block-by-ghost-uncle/${ghostUncleHash}`,
688
+ method: 'GET',
689
+ format: 'json',
690
+ ...params
691
+ }).then(utils_1.convertHttpResponse),
678
692
  /**
679
693
  * No description
680
694
  *
@@ -78,6 +78,21 @@ export interface BlockEntryLite {
78
78
  /** @format bigint */
79
79
  hashRate: string;
80
80
  }
81
+ export interface ContractLiveness {
82
+ /** @format address */
83
+ parent?: string;
84
+ creation: ContractLivenessLocation;
85
+ destruction?: ContractLivenessLocation;
86
+ interfaceId?: StdInterfaceId;
87
+ }
88
+ export interface ContractLivenessLocation {
89
+ /** @format block-hash */
90
+ blockHash: string;
91
+ /** @format 32-byte-hash */
92
+ txHash: string;
93
+ /** @format int64 */
94
+ timestamp: number;
95
+ }
81
96
  export interface ContractOutput {
82
97
  /** @format int32 */
83
98
  hint: number;
@@ -117,6 +132,9 @@ export interface ExplorerInfo {
117
132
  /** @format int64 */
118
133
  lastFinalizedInputTime: number;
119
134
  }
135
+ export interface FungibleToken {
136
+ type: string;
137
+ }
120
138
  export interface FungibleTokenMetadata {
121
139
  /** @format 32-byte-hash */
122
140
  id: string;
@@ -176,11 +194,20 @@ export interface MempoolTransaction {
176
194
  /** @format int64 */
177
195
  lastSeen: number;
178
196
  }
197
+ export interface NFT {
198
+ type: string;
199
+ }
200
+ export interface NFTCollection {
201
+ type: string;
202
+ }
179
203
  export interface NFTCollectionMetadata {
180
204
  /** @format address */
181
205
  address: string;
182
206
  collectionUri: string;
183
207
  }
208
+ export interface NFTCollectionWithRoyalty {
209
+ type: string;
210
+ }
184
211
  export interface NFTMetadata {
185
212
  /** @format 32-byte-hash */
186
213
  id: string;
@@ -190,6 +217,9 @@ export interface NFTMetadata {
190
217
  /** @format uint256 */
191
218
  nftIndex: string;
192
219
  }
220
+ export interface NonStandard {
221
+ type: string;
222
+ }
193
223
  export interface NotFound {
194
224
  detail: string;
195
225
  resource: string;
@@ -254,6 +284,7 @@ export interface PerChainTimedCount {
254
284
  export interface ServiceUnavailable {
255
285
  detail: string;
256
286
  }
287
+ export type StdInterfaceId = FungibleToken | NFT | NFTCollection | NFTCollectionWithRoyalty | NonStandard | Unknown;
257
288
  export interface SubContracts {
258
289
  subContracts?: string[];
259
290
  }
@@ -324,6 +355,10 @@ export type TransactionLike = AcceptedTransaction | PendingTransaction;
324
355
  export interface Unauthorized {
325
356
  detail: string;
326
357
  }
358
+ export interface Unknown {
359
+ id: string;
360
+ type: string;
361
+ }
327
362
  export type Val = ValAddress | ValArray | ValBool | ValByteVec | ValI256 | ValU256;
328
363
  export interface ValAddress {
329
364
  /** @format address */
@@ -633,6 +668,14 @@ export declare class Api<SecurityDataType extends unknown> extends HttpClient<Se
633
668
  * @request GET:/addresses/{address}/tokens/{token_id}/balance
634
669
  */
635
670
  getAddressesAddressTokensTokenIdBalance: (address: string, tokenId: string, params?: RequestParams) => Promise<AddressTokenBalance>;
671
+ /**
672
+ * @description Get public key of p2pkh addresses, the address needs to have at least one input.
673
+ *
674
+ * @tags Addresses
675
+ * @name GetAddressesAddressPublicKey
676
+ * @request GET:/addresses/{address}/public-key
677
+ */
678
+ getAddressesAddressPublicKey: (address: string, params?: RequestParams) => Promise<string>;
636
679
  /**
637
680
  * @description Get address tokens with balance
638
681
  *
@@ -1037,6 +1080,14 @@ export declare class Api<SecurityDataType extends unknown> extends HttpClient<Se
1037
1080
  }, params?: RequestParams) => Promise<Event[]>;
1038
1081
  };
1039
1082
  contracts: {
1083
+ /**
1084
+ * @description Get contract liveness
1085
+ *
1086
+ * @tags Contracts
1087
+ * @name GetContractsContractAddressCurrentLiveness
1088
+ * @request GET:/contracts/{contract_address}/current-liveness
1089
+ */
1090
+ getContractsContractAddressCurrentLiveness: (contractAddress: string, params?: RequestParams) => Promise<ContractLiveness>;
1040
1091
  /**
1041
1092
  * @description Get contract parent address if exist
1042
1093
  *
@@ -377,6 +377,19 @@ class Api extends HttpClient {
377
377
  format: 'json',
378
378
  ...params
379
379
  }).then(utils_1.convertHttpResponse),
380
+ /**
381
+ * @description Get public key of p2pkh addresses, the address needs to have at least one input.
382
+ *
383
+ * @tags Addresses
384
+ * @name GetAddressesAddressPublicKey
385
+ * @request GET:/addresses/{address}/public-key
386
+ */
387
+ getAddressesAddressPublicKey: (address, params = {}) => this.request({
388
+ path: `/addresses/${address}/public-key`,
389
+ method: 'GET',
390
+ format: 'json',
391
+ ...params
392
+ }).then(utils_1.convertHttpResponse),
380
393
  /**
381
394
  * @description Get address tokens with balance
382
395
  *
@@ -781,6 +794,19 @@ class Api extends HttpClient {
781
794
  }).then(utils_1.convertHttpResponse)
782
795
  };
783
796
  this.contracts = {
797
+ /**
798
+ * @description Get contract liveness
799
+ *
800
+ * @tags Contracts
801
+ * @name GetContractsContractAddressCurrentLiveness
802
+ * @request GET:/contracts/{contract_address}/current-liveness
803
+ */
804
+ getContractsContractAddressCurrentLiveness: (contractAddress, params = {}) => this.request({
805
+ path: `/contracts/${contractAddress}/current-liveness`,
806
+ method: 'GET',
807
+ format: 'json',
808
+ ...params
809
+ }).then(utils_1.convertHttpResponse),
784
810
  /**
785
811
  * @description Get contract parent address if exist
786
812
  *
@@ -1,28 +1,27 @@
1
1
  import { Subscription, SubscribeOptions } from '../utils/subscription';
2
2
  import * as node from '../api/api-alephium';
3
3
  import { NodeProvider } from '../api';
4
- export type ReorgCallback = (orphanBlocks: node.BlockEntry[], newBlocks: node.BlockEntry[]) => Promise<void> | void;
5
- export interface BlockSubscribeOptions extends SubscribeOptions<node.BlockEntry> {
4
+ export type ReorgCallback = (fromGroup: number, toGroup: number, orphanBlocks: node.BlockEntry[], newBlocks: node.BlockEntry[]) => Promise<void> | void;
5
+ export interface BlockSubscribeOptions extends SubscribeOptions<node.BlockEntry[]> {
6
6
  reorgCallback?: ReorgCallback;
7
7
  }
8
- export declare abstract class BlockSubscriptionBase extends Subscription<node.BlockEntry> {
8
+ export declare abstract class BlockSubscriptionBase extends Subscription<node.BlockEntry[]> {
9
9
  abstract readonly reorgCallback?: ReorgCallback;
10
- abstract readonly fromGroup: number;
11
- abstract readonly toGroup: number;
12
- abstract getHashesAtHeight(height: number): Promise<string[]>;
10
+ abstract getHashesAtHeight(fromGroup: number, toGroup: number, height: number): Promise<string[]>;
13
11
  abstract getBlockByHash(hash: string): Promise<node.BlockEntry>;
14
12
  protected getParentHash(block: node.BlockEntry): string;
15
- protected handleReorg(blockHash: string, blockHeight: number): Promise<void>;
13
+ protected handleReorg(fromGroup: number, toGroup: number, orphanBlockHash: string, newBlockHash: string): Promise<void>;
16
14
  }
17
15
  export declare class BlockSubscription extends BlockSubscriptionBase {
18
16
  readonly nodeProvider: NodeProvider;
19
- readonly fromGroup: number;
20
- readonly toGroup: number;
21
17
  readonly reorgCallback?: ReorgCallback;
22
- private currentBlockHeight;
23
- private parentBlockHash;
24
- constructor(options: BlockSubscribeOptions, fromGroup: number, toGroup: number, fromBlockHeight: number, nodeProvider?: NodeProvider | undefined);
25
- getHashesAtHeight(height: number): Promise<string[]>;
18
+ private fromTimeStamp;
19
+ private parents;
20
+ private cache;
21
+ constructor(options: BlockSubscribeOptions, fromTimeStamp: number, nodeProvider?: NodeProvider | undefined);
22
+ getHashesAtHeight(fromGroup: number, toGroup: number, height: number): Promise<string[]>;
26
23
  getBlockByHash(hash: string): Promise<node.BlockEntry>;
24
+ private getMissingBlocksAndHandleReorg;
25
+ private handleBlocks;
27
26
  polling(): Promise<void>;
28
27
  }
@@ -43,88 +43,129 @@ Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.BlockSubscription = exports.BlockSubscriptionBase = void 0;
44
44
  const subscription_1 = require("../utils/subscription");
45
45
  const web3 = __importStar(require("../global"));
46
+ const constants_1 = require("../constants");
47
+ const DEFAULT_INTERVAL = 60 * 1000; // 60 seconds
48
+ const EXPIRE_DURATION = 20 * 1000; // 20 seconds
46
49
  class BlockSubscriptionBase extends subscription_1.Subscription {
47
50
  getParentHash(block) {
48
- const index = Math.floor(block.deps.length / 2) + this.toGroup;
51
+ const index = Math.floor(block.deps.length / 2) + block.chainTo;
49
52
  return block.deps[index];
50
53
  }
51
- async handleReorg(blockHash, blockHeight) {
52
- console.info(`reorg occur, hash: ${blockHash}, height: ${blockHeight}`);
54
+ async handleReorg(fromGroup, toGroup, orphanBlockHash, newBlockHash) {
55
+ console.info(`reorg occur in chain ${fromGroup} -> ${toGroup}, orphan hash: ${orphanBlockHash}, new hash: ${newBlockHash}`);
53
56
  if (this.reorgCallback === undefined)
54
57
  return;
55
- const orphans = [];
56
- const newHashes = [];
57
- let fromHash = blockHash;
58
- let fromHeight = blockHeight;
58
+ const orphanBlocks = [];
59
+ let fromHash = orphanBlockHash;
60
+ let canonicalHash = undefined;
59
61
  while (true) {
60
- const hashes = await this.getHashesAtHeight(fromHeight);
61
- const canonicalHash = hashes[0];
62
- if (canonicalHash !== fromHash) {
63
- orphans.push(fromHash);
64
- newHashes.push(canonicalHash);
65
- const block = await this.getBlockByHash(fromHash);
66
- fromHash = this.getParentHash(block);
67
- fromHeight -= 1;
68
- }
69
- else {
62
+ const orphanBlock = await this.getBlockByHash(fromHash);
63
+ orphanBlocks.push(orphanBlock);
64
+ const hashes = await this.getHashesAtHeight(fromGroup, toGroup, orphanBlock.height - 1);
65
+ const parentHash = this.getParentHash(orphanBlock);
66
+ if (hashes[0] === parentHash) {
67
+ canonicalHash = hashes[0];
70
68
  break;
71
69
  }
72
- }
73
- const orphanBlocks = [];
74
- for (const hash of orphans.reverse()) {
75
- const block = await this.getBlockByHash(hash);
76
- orphanBlocks.push(block);
70
+ fromHash = parentHash;
77
71
  }
78
72
  const newBlocks = [];
79
- for (const hash of newHashes.reverse()) {
80
- const block = await this.getBlockByHash(hash);
81
- newBlocks.push(block);
73
+ fromHash = newBlockHash;
74
+ while (fromHash !== canonicalHash) {
75
+ const newBlock = await this.getBlockByHash(fromHash);
76
+ newBlocks.push(newBlock);
77
+ fromHash = this.getParentHash(newBlock);
82
78
  }
83
- console.info(`orphan hashes: ${orphanBlocks.map((b) => b.hash)}, new hashes: ${newBlocks.map((b) => b.hash)}`);
84
- await this.reorgCallback(orphanBlocks, newBlocks);
79
+ const orphans = orphanBlocks.reverse();
80
+ const news = newBlocks.reverse();
81
+ console.info(`orphan hashes: ${orphans.map((b) => b.hash)}, new hashes: ${news.map((b) => b.hash)}`);
82
+ await this.reorgCallback(fromGroup, toGroup, orphans, news);
85
83
  }
86
84
  }
87
85
  exports.BlockSubscriptionBase = BlockSubscriptionBase;
88
86
  class BlockSubscription extends BlockSubscriptionBase {
89
- constructor(options, fromGroup, toGroup, fromBlockHeight, nodeProvider = undefined) {
87
+ constructor(options, fromTimeStamp, nodeProvider = undefined) {
90
88
  super(options);
91
89
  this.nodeProvider = nodeProvider ?? web3.getCurrentNodeProvider();
92
- this.fromGroup = fromGroup;
93
- this.toGroup = toGroup;
94
90
  this.reorgCallback = options.reorgCallback;
95
- this.currentBlockHeight = fromBlockHeight;
96
- this.parentBlockHash = undefined;
91
+ this.fromTimeStamp = fromTimeStamp;
92
+ this.parents = new Array(constants_1.TOTAL_NUMBER_OF_CHAINS).fill(undefined);
93
+ this.cache = new Map();
97
94
  }
98
- async getHashesAtHeight(height) {
99
- const result = await this.nodeProvider.blockflow.getBlockflowHashes({
100
- fromGroup: this.fromGroup,
101
- toGroup: this.toGroup,
102
- height
103
- });
95
+ async getHashesAtHeight(fromGroup, toGroup, height) {
96
+ const result = await this.nodeProvider.blockflow.getBlockflowHashes({ fromGroup, toGroup, height });
104
97
  return result.headers;
105
98
  }
106
99
  async getBlockByHash(hash) {
107
100
  return await this.nodeProvider.blockflow.getBlockflowBlocksBlockHash(hash);
108
101
  }
109
- async polling() {
102
+ async getMissingBlocksAndHandleReorg(fromHash, fromHeight, toBlock) {
103
+ const blocks = [];
104
+ let lastBlock = toBlock;
105
+ while (lastBlock.height - 1 > fromHeight) {
106
+ const parentHash = this.getParentHash(lastBlock);
107
+ const block = await this.getBlockByHash(parentHash);
108
+ blocks.push(block);
109
+ lastBlock = block;
110
+ }
111
+ const parentHash = this.getParentHash(lastBlock);
112
+ if (parentHash !== fromHash) {
113
+ await this.handleReorg(toBlock.chainFrom, toBlock.chainTo, fromHash, parentHash);
114
+ }
115
+ return blocks.reverse();
116
+ }
117
+ async handleBlocks(blocks, now) {
118
+ const allBlocks = [];
119
+ for (let index = 0; index < blocks.length; index += 1) {
120
+ const blocksPerChain = blocks[index].filter((b) => !this.cache.has(b.hash));
121
+ if (blocksPerChain.length === 0)
122
+ continue;
123
+ allBlocks.push(...blocksPerChain);
124
+ const parent = this.parents[index];
125
+ if (parent !== undefined) {
126
+ const missingBlocks = await this.getMissingBlocksAndHandleReorg(parent.hash, parent.height, blocksPerChain[0]);
127
+ allBlocks.push(...missingBlocks);
128
+ }
129
+ const latestBlock = blocksPerChain[blocksPerChain.length - 1];
130
+ this.parents[index] = { hash: latestBlock.hash, height: latestBlock.height };
131
+ }
132
+ const sortedBlocks = allBlocks.sort((a, b) => a.timestamp - b.timestamp);
110
133
  try {
111
- const chainInfo = await this.nodeProvider.blockflow.getBlockflowChainInfo({
112
- fromGroup: this.fromGroup,
113
- toGroup: this.toGroup
134
+ await this.messageCallback(sortedBlocks);
135
+ }
136
+ finally {
137
+ const threshold = now - EXPIRE_DURATION;
138
+ Array.from(this.cache.entries()).forEach(([hash, ts]) => {
139
+ if (ts < threshold)
140
+ this.cache.delete(hash);
114
141
  });
115
- while (this.currentBlockHeight <= chainInfo.currentHeight) {
116
- const hashes = await this.getHashesAtHeight(this.currentBlockHeight);
117
- const block = await this.getBlockByHash(hashes[0]);
118
- if (this.parentBlockHash !== undefined && this.getParentHash(block) !== this.parentBlockHash) {
119
- await this.handleReorg(this.parentBlockHash, this.currentBlockHeight - 1);
120
- }
121
- await this.messageCallback(block);
122
- this.currentBlockHeight += 1;
123
- this.parentBlockHash = hashes[0];
142
+ const index = sortedBlocks.findIndex((b) => b.timestamp >= threshold);
143
+ if (index !== -1) {
144
+ sortedBlocks.slice(index).forEach((b) => this.cache.set(b.hash, b.timestamp));
124
145
  }
125
146
  }
126
- catch (err) {
127
- await this.errorCallback(err, this);
147
+ }
148
+ async polling() {
149
+ const now = Date.now();
150
+ if (this.fromTimeStamp >= now)
151
+ return;
152
+ while (this.fromTimeStamp < now) {
153
+ if (this.isCancelled())
154
+ return;
155
+ const toTs = Math.min(this.fromTimeStamp + DEFAULT_INTERVAL, now);
156
+ try {
157
+ const result = await this.nodeProvider.blockflow.getBlockflowBlocks({ fromTs: this.fromTimeStamp, toTs });
158
+ await this.handleBlocks(result.blocks, now);
159
+ }
160
+ catch (err) {
161
+ await this.errorCallback(err, this);
162
+ }
163
+ if (this.fromTimeStamp + EXPIRE_DURATION < now) {
164
+ this.fromTimeStamp = Math.min(toTs + 1, now - EXPIRE_DURATION);
165
+ }
166
+ else {
167
+ return;
168
+ }
128
169
  }
129
170
  }
130
171
  }
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@alephium/web3",
3
- "version": "0.44.0",
3
+ "version": "1.0.0",
4
4
  "description": "A JS/TS library to interact with the Alephium platform",
5
5
  "license": "GPL",
6
6
  "main": "dist/src/index.js",
7
7
  "browser": "dist/alephium-web3.min.js",
8
8
  "types": "dist/src/index.d.ts",
9
9
  "exports": {
10
- "node": "./dist/src/index.js",
11
- "default": "./dist/alephium-web3.min.js"
10
+ "node": {
11
+ "types": "./dist/src/index.d.ts",
12
+ "default": "./dist/src/index.js"
13
+ },
14
+ "default": {
15
+ "types": "./dist/src/index.d.ts",
16
+ "default": "./dist/alephium-web3.min.js"
17
+ }
12
18
  },
13
19
  "typesVersions": {
14
20
  "*": {
@@ -27,8 +33,8 @@
27
33
  },
28
34
  "author": "Alephium dev <dev@alephium.org>",
29
35
  "config": {
30
- "alephium_version": "2.14.0",
31
- "explorer_backend_version": "1.17.0"
36
+ "alephium_version": "3.1.0",
37
+ "explorer_backend_version": "1.19.3"
32
38
  },
33
39
  "type": "commonjs",
34
40
  "dependencies": {
@@ -28,6 +28,7 @@ import { MultiSig, lockupScriptCodec } from '../codec/lockup-script-codec'
28
28
  import { compactSignedIntCodec } from '../codec'
29
29
 
30
30
  const ec = new EC('secp256k1')
31
+ const PublicKeyHashSize = 32
31
32
 
32
33
  export enum AddressType {
33
34
  P2PKH = 0x00,
@@ -59,10 +60,13 @@ function decodeAndValidateAddress(address: string): Uint8Array {
59
60
  }
60
61
  const n = multisig.publicKeyHashes.value.length
61
62
  const m = compactSignedIntCodec.toI32(multisig.m)
62
- if (n < m) {
63
+ if (n < m || m <= 0) {
63
64
  throw new Error(`Invalid multisig address, n: ${n}, m: ${m}`)
64
65
  }
65
- return decoded
66
+ const encodedNSize = compactSignedIntCodec.encodeI32(n).length
67
+ const encodedMSize = multisig.m.rest.length + 1
68
+ const size = encodedNSize + PublicKeyHashSize * n + encodedMSize + 1 // 1 for the P2MPKH prefix
69
+ if (decoded.length === size) return decoded
66
70
  } else if (addressType === AddressType.P2PKH || addressType === AddressType.P2SH || addressType === AddressType.P2C) {
67
71
  // [type, ...hash]
68
72
  if (decoded.length === 33) return decoded