@alephium/web3 0.39.3 → 0.41.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.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/api/api-alephium.d.ts +1 -1
- package/dist/src/api/api-alephium.js +1 -1
- package/dist/src/api/node-provider.d.ts +2 -0
- package/dist/src/api/node-provider.js +12 -6
- package/dist/src/api/utils.d.ts +1 -1
- package/dist/src/block/block.d.ts +28 -0
- package/dist/src/block/block.js +131 -0
- package/dist/src/block/index.d.ts +1 -0
- package/dist/src/block/index.js +22 -0
- package/dist/src/codec/contract-output-codec.js +4 -4
- package/dist/src/codec/instr-codec.d.ts +3 -0
- package/dist/src/codec/instr-codec.js +19 -3
- package/dist/src/codec/lockup-script-codec.js +2 -2
- package/dist/src/codec/method-codec.d.ts +3 -1
- package/dist/src/codec/method-codec.js +27 -2
- package/dist/src/codec/script-codec.d.ts +11 -6
- package/dist/src/codec/script-codec.js +13 -2
- package/dist/src/codec/transaction-codec.js +2 -2
- package/dist/src/codec/unlock-script-codec.d.ts +2 -2
- package/dist/src/codec/unsigned-tx-codec.d.ts +2 -2
- package/dist/src/contract/contract.d.ts +25 -7
- package/dist/src/contract/contract.js +136 -51
- package/dist/src/contract/events.d.ts +1 -2
- package/dist/src/contract/events.js +28 -14
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/signer/tx-builder.js +4 -4
- package/dist/src/transaction/status.js +28 -4
- package/dist/src/utils/address.js +29 -16
- package/dist/src/utils/exchange.js +25 -15
- package/dist/src/utils/number.d.ts +1 -1
- package/dist/src/utils/sign.js +6 -6
- package/dist/src/utils/subscription.d.ts +4 -4
- package/dist/src/utils/subscription.js +1 -1
- package/package.json +5 -5
- package/src/api/api-alephium.ts +1 -1
- package/src/api/node-provider.ts +8 -1
- package/src/api/utils.ts +1 -1
- package/src/block/block.ts +139 -0
- package/src/block/index.ts +19 -0
- package/src/codec/contract-output-codec.ts +1 -1
- package/src/codec/instr-codec.ts +14 -1
- package/src/codec/lockup-script-codec.ts +3 -3
- package/src/codec/method-codec.ts +41 -3
- package/src/codec/script-codec.ts +23 -5
- package/src/codec/transaction-codec.ts +1 -1
- package/src/codec/unlock-script-codec.ts +2 -2
- package/src/codec/unsigned-tx-codec.ts +2 -2
- package/src/contract/contract.ts +178 -67
- package/src/contract/events.ts +6 -18
- package/src/index.ts +1 -0
- package/src/signer/tx-builder.ts +2 -2
- package/src/transaction/status.ts +4 -4
- package/src/utils/address.ts +15 -2
- package/src/utils/exchange.ts +32 -10
- package/src/utils/number.ts +1 -1
- package/src/utils/sign.ts +1 -1
- package/src/utils/subscription.ts +4 -4
- package/std/fungible_token_interface.ral +1 -0
- package/std/nft_collection_interface.ral +1 -0
- package/std/nft_collection_with_royalty_interface.ral +1 -0
- package/std/nft_interface.ral +1 -0
|
@@ -28,6 +28,9 @@ const blakejs_1 = __importDefault(require("blakejs"));
|
|
|
28
28
|
const bs58_1 = __importDefault(require("./bs58"));
|
|
29
29
|
const djb2_1 = __importDefault(require("./djb2"));
|
|
30
30
|
const utils_1 = require("./utils");
|
|
31
|
+
const lockup_script_codec_1 = require("../codec/lockup-script-codec");
|
|
32
|
+
const buffer_1 = require("buffer/");
|
|
33
|
+
const codec_1 = require("../codec");
|
|
31
34
|
const ec = new elliptic_1.ec('secp256k1');
|
|
32
35
|
var AddressType;
|
|
33
36
|
(function (AddressType) {
|
|
@@ -52,9 +55,19 @@ function decodeAndValidateAddress(address) {
|
|
|
52
55
|
throw new Error('Address is empty');
|
|
53
56
|
const addressType = decoded[0];
|
|
54
57
|
if (addressType === AddressType.P2MPKH) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
let multisig;
|
|
59
|
+
try {
|
|
60
|
+
multisig = lockup_script_codec_1.lockupScriptCodec.decode(buffer_1.Buffer.from(decoded)).script;
|
|
61
|
+
}
|
|
62
|
+
catch (_) {
|
|
63
|
+
throw new Error(`Invalid multisig address: ${address}`);
|
|
64
|
+
}
|
|
65
|
+
const n = multisig.publicKeyHashes.value.length;
|
|
66
|
+
const m = codec_1.compactSignedIntCodec.toI32(multisig.m);
|
|
67
|
+
if (n < m) {
|
|
68
|
+
throw new Error(`Invalid multisig address, n: ${n}, m: ${m}`);
|
|
69
|
+
}
|
|
70
|
+
return decoded;
|
|
58
71
|
}
|
|
59
72
|
else if (addressType === AddressType.P2PKH || addressType === AddressType.P2SH || addressType === AddressType.P2C) {
|
|
60
73
|
// [type, ...hash]
|
|
@@ -150,27 +163,27 @@ exports.publicKeyFromPrivateKey = publicKeyFromPrivateKey;
|
|
|
150
163
|
function addressFromPublicKey(publicKey, _keyType) {
|
|
151
164
|
const keyType = _keyType ?? 'default';
|
|
152
165
|
if (keyType === 'default') {
|
|
153
|
-
const addressType = Buffer.from([AddressType.P2PKH]);
|
|
154
|
-
const hash = Buffer.from(blakejs_1.default.blake2b(Buffer.from(publicKey, 'hex'), undefined, 32));
|
|
155
|
-
const bytes = Buffer.concat([addressType, hash]);
|
|
166
|
+
const addressType = buffer_1.Buffer.from([AddressType.P2PKH]);
|
|
167
|
+
const hash = buffer_1.Buffer.from(blakejs_1.default.blake2b(buffer_1.Buffer.from(publicKey, 'hex'), undefined, 32));
|
|
168
|
+
const bytes = buffer_1.Buffer.concat([addressType, hash]);
|
|
156
169
|
return bs58_1.default.encode(bytes);
|
|
157
170
|
}
|
|
158
171
|
else {
|
|
159
|
-
const lockupScript = Buffer.from(`0101000000000458144020${publicKey}8685`, 'hex');
|
|
172
|
+
const lockupScript = buffer_1.Buffer.from(`0101000000000458144020${publicKey}8685`, 'hex');
|
|
160
173
|
return addressFromScript(lockupScript);
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
176
|
exports.addressFromPublicKey = addressFromPublicKey;
|
|
164
177
|
function addressFromScript(script) {
|
|
165
178
|
const scriptHash = blakejs_1.default.blake2b(script, undefined, 32);
|
|
166
|
-
const addressType = Buffer.from([AddressType.P2SH]);
|
|
167
|
-
return bs58_1.default.encode(Buffer.concat([addressType, scriptHash]));
|
|
179
|
+
const addressType = buffer_1.Buffer.from([AddressType.P2SH]);
|
|
180
|
+
return bs58_1.default.encode(buffer_1.Buffer.concat([addressType, scriptHash]));
|
|
168
181
|
}
|
|
169
182
|
exports.addressFromScript = addressFromScript;
|
|
170
183
|
function addressFromContractId(contractId) {
|
|
171
|
-
const addressType = Buffer.from([AddressType.P2C]);
|
|
172
|
-
const hash = Buffer.from((0, utils_1.hexToBinUnsafe)(contractId));
|
|
173
|
-
const bytes = Buffer.concat([addressType, hash]);
|
|
184
|
+
const addressType = buffer_1.Buffer.from([AddressType.P2C]);
|
|
185
|
+
const hash = buffer_1.Buffer.from((0, utils_1.hexToBinUnsafe)(contractId));
|
|
186
|
+
const bytes = buffer_1.Buffer.concat([addressType, hash]);
|
|
174
187
|
return bs58_1.default.encode(bytes);
|
|
175
188
|
}
|
|
176
189
|
exports.addressFromContractId = addressFromContractId;
|
|
@@ -181,7 +194,7 @@ function addressFromTokenId(tokenId) {
|
|
|
181
194
|
exports.addressFromTokenId = addressFromTokenId;
|
|
182
195
|
function contractIdFromTx(txId, outputIndex) {
|
|
183
196
|
const txIdBin = (0, utils_1.hexToBinUnsafe)(txId);
|
|
184
|
-
const data = Buffer.concat([txIdBin, Buffer.from([outputIndex])]);
|
|
197
|
+
const data = buffer_1.Buffer.concat([txIdBin, buffer_1.Buffer.from([outputIndex])]);
|
|
185
198
|
const hash = blakejs_1.default.blake2b(data, undefined, 32);
|
|
186
199
|
return (0, utils_1.binToHex)(hash);
|
|
187
200
|
}
|
|
@@ -190,10 +203,10 @@ function subContractId(parentContractId, pathInHex, group) {
|
|
|
190
203
|
if (group < 0 || group >= constants_1.TOTAL_NUMBER_OF_GROUPS) {
|
|
191
204
|
throw new Error(`Invalid group ${group}`);
|
|
192
205
|
}
|
|
193
|
-
const data = Buffer.concat([(0, utils_1.hexToBinUnsafe)(parentContractId), (0, utils_1.hexToBinUnsafe)(pathInHex)]);
|
|
194
|
-
const bytes = Buffer.concat([
|
|
206
|
+
const data = buffer_1.Buffer.concat([(0, utils_1.hexToBinUnsafe)(parentContractId), (0, utils_1.hexToBinUnsafe)(pathInHex)]);
|
|
207
|
+
const bytes = buffer_1.Buffer.concat([
|
|
195
208
|
blakejs_1.default.blake2b(blakejs_1.default.blake2b(data, undefined, 32), undefined, 32).slice(0, -1),
|
|
196
|
-
Buffer.from([group])
|
|
209
|
+
buffer_1.Buffer.from([group])
|
|
197
210
|
]);
|
|
198
211
|
return (0, utils_1.binToHex)(bytes);
|
|
199
212
|
}
|
|
@@ -18,11 +18,14 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.getAddressFromUnlockScript = exports.getSenderAddress = exports.getALPHDepositInfo = exports.isALPHTransferTx = exports.validateExchangeAddress = void 0;
|
|
21
|
-
const
|
|
21
|
+
const utils_1 = require("../utils");
|
|
22
|
+
const unlock_script_codec_1 = require("../codec/unlock-script-codec");
|
|
23
|
+
const buffer_1 = require("buffer/");
|
|
24
|
+
const script_codec_1 = require("../codec/script-codec");
|
|
22
25
|
function validateExchangeAddress(address) {
|
|
23
26
|
let decoded;
|
|
24
27
|
try {
|
|
25
|
-
decoded =
|
|
28
|
+
decoded = utils_1.bs58.decode(address);
|
|
26
29
|
}
|
|
27
30
|
catch (_) {
|
|
28
31
|
throw new Error('Invalid base58 string');
|
|
@@ -30,7 +33,7 @@ function validateExchangeAddress(address) {
|
|
|
30
33
|
if (decoded.length === 0)
|
|
31
34
|
throw new Error('Address is empty');
|
|
32
35
|
const addressType = decoded[0];
|
|
33
|
-
if (addressType !==
|
|
36
|
+
if (addressType !== utils_1.AddressType.P2PKH && addressType !== utils_1.AddressType.P2SH) {
|
|
34
37
|
throw new Error('Invalid address type');
|
|
35
38
|
}
|
|
36
39
|
if (decoded.length !== 33) {
|
|
@@ -83,27 +86,34 @@ var UnlockScriptType;
|
|
|
83
86
|
UnlockScriptType[UnlockScriptType["P2SH"] = 2] = "P2SH";
|
|
84
87
|
})(UnlockScriptType || (UnlockScriptType = {}));
|
|
85
88
|
function getAddressFromUnlockScript(unlockScript) {
|
|
86
|
-
|
|
89
|
+
if (!(0, utils_1.isHexString)(unlockScript)) {
|
|
90
|
+
throw new Error(`Invalid unlock script ${unlockScript}, expected a hex string`);
|
|
91
|
+
}
|
|
92
|
+
const decoded = (0, utils_1.hexToBinUnsafe)(unlockScript);
|
|
87
93
|
if (decoded.length === 0)
|
|
88
94
|
throw new Error('UnlockScript is empty');
|
|
89
95
|
const unlockScriptType = decoded[0];
|
|
90
96
|
const unlockScriptBody = decoded.slice(1);
|
|
91
97
|
if (unlockScriptType === UnlockScriptType.P2PKH) {
|
|
92
|
-
|
|
98
|
+
if (unlockScriptBody.length !== 33) {
|
|
99
|
+
throw new Error(`Invalid p2pkh unlock script: ${unlockScript}`);
|
|
100
|
+
}
|
|
101
|
+
return (0, utils_1.addressFromPublicKey)((0, utils_1.binToHex)(unlockScriptBody));
|
|
93
102
|
}
|
|
94
|
-
|
|
103
|
+
if (unlockScriptType === UnlockScriptType.P2MPKH) {
|
|
95
104
|
throw new Error('Naive multi-sig address is not supported for exchanges as it will be replaced by P2SH');
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
if (unlockScriptType === UnlockScriptType.P2SH) {
|
|
107
|
+
let p2sh;
|
|
108
|
+
try {
|
|
109
|
+
p2sh = unlock_script_codec_1.unlockScriptCodec.decode(buffer_1.Buffer.from(decoded)).script;
|
|
110
|
+
}
|
|
111
|
+
catch (_) {
|
|
112
|
+
throw new Error(`Invalid p2sh unlock script: ${unlockScript}`);
|
|
113
|
+
}
|
|
114
|
+
return (0, utils_1.addressFromScript)(script_codec_1.scriptCodec.encode(p2sh.script));
|
|
106
115
|
}
|
|
116
|
+
throw new Error('Invalid unlock script type');
|
|
107
117
|
}
|
|
108
118
|
exports.getAddressFromUnlockScript = getAddressFromUnlockScript;
|
|
109
119
|
function checkALPHOutput(tx) {
|
package/dist/src/utils/sign.js
CHANGED
|
@@ -42,7 +42,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.verifySignature = exports.sign = void 0;
|
|
44
44
|
const elliptic_1 = require("elliptic");
|
|
45
|
-
const
|
|
45
|
+
const utils_1 = require("../utils");
|
|
46
46
|
const necc = __importStar(require("@noble/secp256k1"));
|
|
47
47
|
const crypto_1 = require("crypto");
|
|
48
48
|
const ec = new elliptic_1.ec('secp256k1');
|
|
@@ -63,11 +63,11 @@ function sign(hash, privateKey, _keyType) {
|
|
|
63
63
|
if (keyType === 'default') {
|
|
64
64
|
const key = ec.keyFromPrivate(privateKey);
|
|
65
65
|
const signature = key.sign(hash);
|
|
66
|
-
return (0,
|
|
66
|
+
return (0, utils_1.encodeSignature)(signature);
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
|
-
const signature = necc.schnorr.signSync((0,
|
|
70
|
-
return (0,
|
|
69
|
+
const signature = necc.schnorr.signSync((0, utils_1.hexToBinUnsafe)(hash), (0, utils_1.hexToBinUnsafe)(privateKey));
|
|
70
|
+
return (0, utils_1.binToHex)(signature);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
exports.sign = sign;
|
|
@@ -76,10 +76,10 @@ function verifySignature(hash, publicKey, signature, _keyType) {
|
|
|
76
76
|
try {
|
|
77
77
|
if (keyType === 'default') {
|
|
78
78
|
const key = ec.keyFromPublic(publicKey, 'hex');
|
|
79
|
-
return key.verify(hash, (0,
|
|
79
|
+
return key.verify(hash, (0, utils_1.signatureDecode)(ec, signature));
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
return necc.schnorr.verifySync((0,
|
|
82
|
+
return necc.schnorr.verifySync((0, utils_1.hexToBinUnsafe)(signature), (0, utils_1.hexToBinUnsafe)(hash), (0, utils_1.hexToBinUnsafe)(publicKey));
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
|
-
type MessageCallback<Message> = (message: Message) => Promise<void
|
|
3
|
-
type ErrorCallback<Message> = (error: any, subscription: Subscription<Message>) => Promise<void
|
|
2
|
+
type MessageCallback<Message> = (message: Message) => Promise<void> | void;
|
|
3
|
+
type ErrorCallback<Message> = (error: any, subscription: Subscription<Message>) => Promise<void> | void;
|
|
4
4
|
export interface SubscribeOptions<Message> {
|
|
5
5
|
pollingInterval: number;
|
|
6
6
|
messageCallback: MessageCallback<Message>;
|
|
@@ -14,9 +14,9 @@ export declare abstract class Subscription<Message> {
|
|
|
14
14
|
protected eventEmitter: EventEmitter;
|
|
15
15
|
protected cancelled: boolean;
|
|
16
16
|
constructor(options: SubscribeOptions<Message>);
|
|
17
|
-
|
|
17
|
+
subscribe(): void;
|
|
18
18
|
unsubscribe(): void;
|
|
19
19
|
isCancelled(): boolean;
|
|
20
|
-
abstract polling(): Promise<void>;
|
|
20
|
+
protected abstract polling(): Promise<void>;
|
|
21
21
|
}
|
|
22
22
|
export {};
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alephium/web3",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"node": "./dist/src/index.js",
|
|
11
|
+
"default": "./dist/alephium-web3.min.js"
|
|
12
12
|
},
|
|
13
13
|
"typesVersions": {
|
|
14
14
|
"*": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"author": "Alephium dev <dev@alephium.org>",
|
|
29
29
|
"config": {
|
|
30
|
-
"alephium_version": "2.
|
|
30
|
+
"alephium_version": "2.14.0",
|
|
31
31
|
"explorer_backend_version": "1.17.0"
|
|
32
32
|
},
|
|
33
33
|
"type": "commonjs",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"build": "rm -rf dist/* && npx tsc --build . && webpack",
|
|
91
91
|
"test": "jest -i --config ./jest-config.json",
|
|
92
92
|
"update-schemas": "npm run update-schema:alephium && npm run update-schema:explorer",
|
|
93
|
-
"update-schema:alephium": "npx swagger-typescript-api --disable-throw-on-error -t ./configs -o ./src/api -n api-alephium.ts -p https://raw.githubusercontent.com/alephium/alephium/
|
|
93
|
+
"update-schema:alephium": "npx swagger-typescript-api --disable-throw-on-error -t ./configs -o ./src/api -n api-alephium.ts -p https://raw.githubusercontent.com/alephium/alephium/v${npm_package_config_alephium_version}/api/src/main/resources/openapi.json",
|
|
94
94
|
"update-schema:explorer": "npx swagger-typescript-api --disable-throw-on-error -t ./configs -o ./src/api -n api-explorer.ts -p https://raw.githubusercontent.com/alephium/explorer-backend/v${npm_package_config_explorer_backend_version}/app/src/main/resources/explorer-backend-openapi.json",
|
|
95
95
|
"check-versions": "node scripts/check-versions.js ${npm_package_config_alephium_version} ${npm_package_config_explorer_backend_version}"
|
|
96
96
|
}
|
package/src/api/api-alephium.ts
CHANGED
|
@@ -1306,7 +1306,7 @@ export class HttpClient<SecurityDataType = unknown> {
|
|
|
1306
1306
|
|
|
1307
1307
|
/**
|
|
1308
1308
|
* @title Alephium API
|
|
1309
|
-
* @version 2.
|
|
1309
|
+
* @version 2.14.0
|
|
1310
1310
|
* @baseUrl ../
|
|
1311
1311
|
*/
|
|
1312
1312
|
export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
|
package/src/api/node-provider.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
requestWithLog
|
|
29
29
|
} from './types'
|
|
30
30
|
import { Api as NodeApi, CallContractFailed, CallContractSucceeded } from './api-alephium'
|
|
31
|
-
import { tryGetCallResult } from '../contract'
|
|
32
31
|
import {
|
|
33
32
|
HexString,
|
|
34
33
|
addressFromContractId,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
isHexString,
|
|
39
38
|
toNonNegativeBigInt
|
|
40
39
|
} from '../utils'
|
|
40
|
+
import * as node from '../api/api-alephium'
|
|
41
41
|
|
|
42
42
|
function initializeNodeApi(baseUrl: string, apiKey?: string, customFetch?: typeof fetch): NodeApi<string> {
|
|
43
43
|
const nodeApi = new NodeApi<string>({
|
|
@@ -253,3 +253,10 @@ export class NodeProvider implements NodeProviderApis {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
+
|
|
257
|
+
export function tryGetCallResult(result: node.CallContractResult): node.CallContractSucceeded {
|
|
258
|
+
if (result.type === 'CallContractFailed') {
|
|
259
|
+
throw new Error(`Failed to call contract, error: ${(result as node.CallContractFailed).error}`)
|
|
260
|
+
}
|
|
261
|
+
return result as node.CallContractSucceeded
|
|
262
|
+
}
|
package/src/api/utils.ts
CHANGED
|
@@ -17,7 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import 'cross-fetch/polyfill'
|
|
20
|
-
import
|
|
20
|
+
import * as node from '../api/api-alephium'
|
|
21
21
|
|
|
22
22
|
export function convertHttpResponse<T>(response: { status: number; data: T; error?: { detail: string } }): T {
|
|
23
23
|
if (response.error) {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2018 - 2022 The Alephium Authors
|
|
3
|
+
This file is part of the alephium project.
|
|
4
|
+
|
|
5
|
+
The library is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
The library is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Lesser General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Subscription, SubscribeOptions } from '../utils/subscription'
|
|
20
|
+
import * as node from '../api/api-alephium'
|
|
21
|
+
import { NodeProvider } from '../api'
|
|
22
|
+
import * as web3 from '../global'
|
|
23
|
+
|
|
24
|
+
export type ReorgCallback = (orphanBlocks: node.BlockEntry[], newBlocks: node.BlockEntry[]) => Promise<void> | void
|
|
25
|
+
|
|
26
|
+
export interface BlockSubscribeOptions extends SubscribeOptions<node.BlockEntry> {
|
|
27
|
+
reorgCallback?: ReorgCallback
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export abstract class BlockSubscriptionBase extends Subscription<node.BlockEntry> {
|
|
31
|
+
abstract readonly reorgCallback?: ReorgCallback
|
|
32
|
+
abstract readonly fromGroup: number
|
|
33
|
+
abstract readonly toGroup: number
|
|
34
|
+
|
|
35
|
+
abstract getHashesAtHeight(height: number): Promise<string[]>
|
|
36
|
+
abstract getBlockByHash(hash: string): Promise<node.BlockEntry>
|
|
37
|
+
|
|
38
|
+
protected getParentHash(block: node.BlockEntry): string {
|
|
39
|
+
const index = Math.floor(block.deps.length / 2) + this.toGroup
|
|
40
|
+
return block.deps[index]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected async handleReorg(blockHash: string, blockHeight: number) {
|
|
44
|
+
console.info(`reorg occur, hash: ${blockHash}, height: ${blockHeight}`)
|
|
45
|
+
if (this.reorgCallback === undefined) return
|
|
46
|
+
|
|
47
|
+
const orphans: string[] = []
|
|
48
|
+
const newHashes: string[] = []
|
|
49
|
+
let fromHash = blockHash
|
|
50
|
+
let fromHeight = blockHeight
|
|
51
|
+
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 {
|
|
61
|
+
break
|
|
62
|
+
}
|
|
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)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const newBlocks: node.BlockEntry[] = []
|
|
72
|
+
for (const hash of newHashes.reverse()) {
|
|
73
|
+
const block = await this.getBlockByHash(hash)
|
|
74
|
+
newBlocks.push(block)
|
|
75
|
+
}
|
|
76
|
+
console.info(`orphan hashes: ${orphanBlocks.map((b) => b.hash)}, new hashes: ${newBlocks.map((b) => b.hash)}`)
|
|
77
|
+
await this.reorgCallback(orphanBlocks, newBlocks)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class BlockSubscription extends BlockSubscriptionBase {
|
|
82
|
+
readonly nodeProvider: NodeProvider
|
|
83
|
+
readonly fromGroup: number
|
|
84
|
+
readonly toGroup: number
|
|
85
|
+
readonly reorgCallback?: ReorgCallback
|
|
86
|
+
private currentBlockHeight: number
|
|
87
|
+
private parentBlockHash: string | undefined
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
options: BlockSubscribeOptions,
|
|
91
|
+
fromGroup: number,
|
|
92
|
+
toGroup: number,
|
|
93
|
+
fromBlockHeight: number,
|
|
94
|
+
nodeProvider: NodeProvider | undefined = undefined
|
|
95
|
+
) {
|
|
96
|
+
super(options)
|
|
97
|
+
this.nodeProvider = nodeProvider ?? web3.getCurrentNodeProvider()
|
|
98
|
+
this.fromGroup = fromGroup
|
|
99
|
+
this.toGroup = toGroup
|
|
100
|
+
this.reorgCallback = options.reorgCallback
|
|
101
|
+
this.currentBlockHeight = fromBlockHeight
|
|
102
|
+
this.parentBlockHash = undefined
|
|
103
|
+
}
|
|
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
|
+
})
|
|
111
|
+
return result.headers
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override async getBlockByHash(hash: string): Promise<node.BlockEntry> {
|
|
115
|
+
return await this.nodeProvider.blockflow.getBlockflowBlocksBlockHash(hash)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override async polling(): Promise<void> {
|
|
119
|
+
try {
|
|
120
|
+
const chainInfo = await this.nodeProvider.blockflow.getBlockflowChainInfo({
|
|
121
|
+
fromGroup: this.fromGroup,
|
|
122
|
+
toGroup: this.toGroup
|
|
123
|
+
})
|
|
124
|
+
|
|
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]
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
await this.errorCallback(err, this)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2018 - 2022 The Alephium Authors
|
|
3
|
+
This file is part of the alephium project.
|
|
4
|
+
|
|
5
|
+
The library is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
The library is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Lesser General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export { ReorgCallback, BlockSubscribeOptions, BlockSubscription } from './block'
|
|
@@ -24,7 +24,7 @@ import { Codec } from './codec'
|
|
|
24
24
|
import { Token, tokensCodec } from './token-codec'
|
|
25
25
|
import { ContractOutput as ApiContractOutput } from '../api/api-alephium'
|
|
26
26
|
import { blakeHash, createHint } from './hash'
|
|
27
|
-
import { binToHex, bs58 } from '
|
|
27
|
+
import { binToHex, bs58 } from '../utils'
|
|
28
28
|
import { signedIntCodec } from './signed-int-codec'
|
|
29
29
|
import { lockupScriptCodec } from './lockup-script-codec'
|
|
30
30
|
|
package/src/codec/instr-codec.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { compactUnsignedIntCodec, compactSignedIntCodec, DecodedCompactInt } fro
|
|
|
22
22
|
import { ByteString, byteStringCodec } from './bytestring-codec'
|
|
23
23
|
import { LockupScript, lockupScriptCodec } from './lockup-script-codec'
|
|
24
24
|
import { Codec } from './codec'
|
|
25
|
+
import { signedIntCodec } from './signed-int-codec'
|
|
25
26
|
|
|
26
27
|
const byteStringArrayCodec = new ArrayCodec(byteStringCodec)
|
|
27
28
|
|
|
@@ -192,6 +193,7 @@ export const AddModN: Instr = { code: 0x88, value: {} }
|
|
|
192
193
|
export const U256ToString: Instr = { code: 0x89, value: {} }
|
|
193
194
|
export const I256ToString: Instr = { code: 0x8a, value: {} }
|
|
194
195
|
export const BoolToString: Instr = { code: 0x8b, value: {} }
|
|
196
|
+
export const GroupOfAddress: Instr = { code: 0x8c, value: {} }
|
|
195
197
|
export const LoadMutField = (index: number): Instr => ({ code: 0xa0, value: { index } })
|
|
196
198
|
export const StoreMutField = (index: number): Instr => ({ code: 0xa1, value: { index } })
|
|
197
199
|
export const ApproveAlph: Instr = { code: 0xa2, value: {} }
|
|
@@ -246,6 +248,12 @@ export const CreateMapEntry: (immFields: number, mutFields: number) => Instr = (
|
|
|
246
248
|
immFields: number,
|
|
247
249
|
mutFields: number
|
|
248
250
|
) => ({ code: 0xd2, value: { immFields, mutFields } })
|
|
251
|
+
export const MethodSelector: (selector: number) => Instr = (selector: number) => {
|
|
252
|
+
return { code: 0xd3, value: { index: selector } }
|
|
253
|
+
}
|
|
254
|
+
export const CallExternalBySelector: (selector: number) => Instr = (selector: number) => {
|
|
255
|
+
return { code: 0xd4, value: { index: selector } }
|
|
256
|
+
}
|
|
249
257
|
|
|
250
258
|
export class InstrCodec implements Codec<Instr> {
|
|
251
259
|
parser = Parser.start()
|
|
@@ -393,6 +401,7 @@ export class InstrCodec implements Codec<Instr> {
|
|
|
393
401
|
[U256ToString.code]: Parser.start(),
|
|
394
402
|
[I256ToString.code]: Parser.start(),
|
|
395
403
|
[BoolToString.code]: Parser.start(),
|
|
404
|
+
[GroupOfAddress.code]: Parser.start(),
|
|
396
405
|
0xa0: Parser.start().uint8('index'),
|
|
397
406
|
0xa1: Parser.start().uint8('index'),
|
|
398
407
|
[ApproveAlph.code]: Parser.start(),
|
|
@@ -443,7 +452,9 @@ export class InstrCodec implements Codec<Instr> {
|
|
|
443
452
|
[LoadImmFieldByIndex.code]: Parser.start(),
|
|
444
453
|
[PayGasFee.code]: Parser.start(),
|
|
445
454
|
[MinimalContractDeposit.code]: Parser.start(),
|
|
446
|
-
0xd2: Parser.start().uint8('immFields').uint8('mutFields')
|
|
455
|
+
0xd2: Parser.start().uint8('immFields').uint8('mutFields'),
|
|
456
|
+
0xd3: Parser.start().int32('index'),
|
|
457
|
+
0xd4: Parser.start().int32('index')
|
|
447
458
|
}
|
|
448
459
|
})
|
|
449
460
|
|
|
@@ -465,6 +476,8 @@ export class InstrCodec implements Codec<Instr> {
|
|
|
465
476
|
} else if (instr.code === 0xd2) {
|
|
466
477
|
const value = instrValue as CreateMapEntryValue
|
|
467
478
|
result.push(value.immFields, value.mutFields)
|
|
479
|
+
} else if (instr.code === 0xd3 || instr.code === 0xd4) {
|
|
480
|
+
result.push(...signedIntCodec.encode((instrValue as InstrValueWithIndex).index))
|
|
468
481
|
}
|
|
469
482
|
|
|
470
483
|
return Buffer.from(result)
|
|
@@ -17,7 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
*/
|
|
18
18
|
import { Buffer } from 'buffer/'
|
|
19
19
|
import { Parser } from 'binary-parser'
|
|
20
|
-
import { DecodedCompactInt,
|
|
20
|
+
import { DecodedCompactInt, compactSignedIntCodec } from './compact-int-codec'
|
|
21
21
|
import { Codec } from './codec'
|
|
22
22
|
import { ArrayCodec, DecodedArray } from './array-codec'
|
|
23
23
|
|
|
@@ -41,7 +41,7 @@ const publicKeyHashCodec = new PublicKeyHashCodec()
|
|
|
41
41
|
const publicKeyHashesCodec = new ArrayCodec(publicKeyHashCodec)
|
|
42
42
|
const multiSigParser = Parser.start()
|
|
43
43
|
.nest('publicKeyHashes', { type: publicKeyHashesCodec.parser })
|
|
44
|
-
.nest('m', { type:
|
|
44
|
+
.nest('m', { type: compactSignedIntCodec.parser })
|
|
45
45
|
export interface MultiSig {
|
|
46
46
|
publicKeyHashes: DecodedArray<PublicKeyHash>
|
|
47
47
|
m: DecodedCompactInt
|
|
@@ -79,7 +79,7 @@ export class LockupScriptCodec implements Codec<LockupScript> {
|
|
|
79
79
|
result.push(...(input.script as PublicKeyHash).publicKeyHash)
|
|
80
80
|
} else if (input.scriptType === 1) {
|
|
81
81
|
result.push(...publicKeyHashesCodec.encode((input.script as MultiSig).publicKeyHashes.value))
|
|
82
|
-
result.push(...
|
|
82
|
+
result.push(...compactSignedIntCodec.encode((input.script as MultiSig).m))
|
|
83
83
|
} else if (input.scriptType === 2) {
|
|
84
84
|
result.push(...(input.script as P2SH).scriptHash)
|
|
85
85
|
} else if (input.scriptType === 3) {
|
|
@@ -33,13 +33,51 @@ export interface DecodedMethod {
|
|
|
33
33
|
|
|
34
34
|
export interface Method {
|
|
35
35
|
isPublic: boolean
|
|
36
|
-
|
|
36
|
+
usePreapprovedAssets: boolean
|
|
37
|
+
useContractAssets: boolean
|
|
38
|
+
usePayToContractOnly: boolean
|
|
37
39
|
argsLength: number
|
|
38
40
|
localsLength: number
|
|
39
41
|
returnLength: number
|
|
40
42
|
instrs: Instr[]
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
function decodeAssetModifier(encoded: number): {
|
|
46
|
+
usePreapprovedAssets: boolean
|
|
47
|
+
useContractAssets: boolean
|
|
48
|
+
usePayToContractOnly: boolean
|
|
49
|
+
} {
|
|
50
|
+
const usePayToContractOnly = (encoded & 4) !== 0
|
|
51
|
+
switch (encoded & 3) {
|
|
52
|
+
case 0:
|
|
53
|
+
return { usePayToContractOnly, usePreapprovedAssets: false, useContractAssets: false }
|
|
54
|
+
case 1:
|
|
55
|
+
return { usePayToContractOnly, usePreapprovedAssets: true, useContractAssets: true }
|
|
56
|
+
case 2:
|
|
57
|
+
return { usePayToContractOnly, usePreapprovedAssets: false, useContractAssets: true }
|
|
58
|
+
case 3:
|
|
59
|
+
return { usePayToContractOnly, usePreapprovedAssets: true, useContractAssets: false }
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`Invalid asset modifier: ${encoded}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function encodeAssetModifier(arg: {
|
|
66
|
+
usePreapprovedAssets: boolean
|
|
67
|
+
useContractAssets: boolean
|
|
68
|
+
usePayToContractOnly: boolean
|
|
69
|
+
}): number {
|
|
70
|
+
const encoded =
|
|
71
|
+
!arg.usePreapprovedAssets && !arg.useContractAssets
|
|
72
|
+
? 0
|
|
73
|
+
: arg.usePreapprovedAssets && arg.useContractAssets
|
|
74
|
+
? 1
|
|
75
|
+
: !arg.usePreapprovedAssets && arg.useContractAssets
|
|
76
|
+
? 2
|
|
77
|
+
: 3
|
|
78
|
+
return encoded | (arg.usePayToContractOnly ? 4 : 0)
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
export class MethodCodec implements Codec<DecodedMethod> {
|
|
44
82
|
parser = Parser.start()
|
|
45
83
|
.uint8('isPublic')
|
|
@@ -73,7 +111,7 @@ export class MethodCodec implements Codec<DecodedMethod> {
|
|
|
73
111
|
static toMethod(decodedMethod: DecodedMethod): Method {
|
|
74
112
|
return {
|
|
75
113
|
isPublic: decodedMethod.isPublic === 1,
|
|
76
|
-
|
|
114
|
+
...decodeAssetModifier(decodedMethod.assetModifier),
|
|
77
115
|
argsLength: compactUnsignedIntCodec.toU32(decodedMethod.argsLength),
|
|
78
116
|
localsLength: compactUnsignedIntCodec.toU32(decodedMethod.localsLength),
|
|
79
117
|
returnLength: compactUnsignedIntCodec.toU32(decodedMethod.returnLength),
|
|
@@ -84,7 +122,7 @@ export class MethodCodec implements Codec<DecodedMethod> {
|
|
|
84
122
|
static fromMethod(method: Method): DecodedMethod {
|
|
85
123
|
return {
|
|
86
124
|
isPublic: method.isPublic ? 1 : 0,
|
|
87
|
-
assetModifier: method
|
|
125
|
+
assetModifier: encodeAssetModifier(method),
|
|
88
126
|
argsLength: compactUnsignedIntCodec.fromU32(method.argsLength),
|
|
89
127
|
localsLength: compactUnsignedIntCodec.fromU32(method.localsLength),
|
|
90
128
|
returnLength: compactUnsignedIntCodec.fromU32(method.returnLength),
|