@twin.org/dlt-iota 0.0.3-next.1 → 0.0.3-next.11
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/README.md +4 -6
- package/dist/es/index.js +5 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/iota.js +336 -81
- package/dist/es/iota.js.map +1 -1
- package/dist/es/iotaIdentityUtils.js +84 -0
- package/dist/es/iotaIdentityUtils.js.map +1 -0
- package/dist/es/iotaSmartContractUtils.js +29 -26
- package/dist/es/iotaSmartContractUtils.js.map +1 -1
- package/dist/es/models/IIotaClient.js +2 -0
- package/dist/es/models/IIotaClient.js.map +1 -0
- package/dist/es/models/IIotaConfig.js.map +1 -1
- package/dist/es/models/IIotaControllerCapInfo.js +4 -0
- package/dist/es/models/IIotaControllerCapInfo.js.map +1 -0
- package/dist/es/models/IIotaTransaction.js +2 -0
- package/dist/es/models/IIotaTransaction.js.map +1 -0
- package/dist/es/models/IIotaTransactionBlockResponse.js +2 -0
- package/dist/es/models/IIotaTransactionBlockResponse.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/iota.d.ts +99 -57
- package/dist/types/iotaIdentityUtils.d.ts +23 -0
- package/dist/types/iotaSmartContractUtils.d.ts +18 -16
- package/dist/types/models/IIotaClient.d.ts +5 -0
- package/dist/types/models/IIotaConfig.d.ts +5 -0
- package/dist/types/models/IIotaControllerCapInfo.d.ts +16 -0
- package/dist/types/models/IIotaTransaction.d.ts +5 -0
- package/dist/types/models/IIotaTransactionBlockResponse.d.ts +5 -0
- package/docs/changelog.md +185 -83
- package/docs/examples.md +413 -1
- package/docs/reference/classes/Iota.md +358 -172
- package/docs/reference/classes/IotaIdentityUtils.md +65 -0
- package/docs/reference/classes/IotaSmartContractUtils.md +70 -52
- package/docs/reference/index.md +5 -0
- package/docs/reference/interfaces/IAdminCapFields.md +1 -1
- package/docs/reference/interfaces/IContractData.md +10 -10
- package/docs/reference/interfaces/IGasReservationResult.md +3 -3
- package/docs/reference/interfaces/IGasStationConfig.md +2 -2
- package/docs/reference/interfaces/IGasStationExecuteResponse.md +3 -3
- package/docs/reference/interfaces/IGasStationReserveGasResponse.md +3 -3
- package/docs/reference/interfaces/IGasStationReserveGasResult.md +3 -3
- package/docs/reference/interfaces/IIotaConfig.md +32 -18
- package/docs/reference/interfaces/IIotaControllerCapInfo.md +22 -0
- package/docs/reference/interfaces/IIotaDryRun.md +5 -5
- package/docs/reference/interfaces/IIotaResponseOptions.md +4 -4
- package/docs/reference/interfaces/IMigrationStateFields.md +2 -2
- package/docs/reference/interfaces/ISmartContractObject.md +2 -2
- package/docs/reference/type-aliases/IIotaClient.md +5 -0
- package/docs/reference/type-aliases/IIotaTransaction.md +5 -0
- package/docs/reference/type-aliases/IIotaTransactionBlockResponse.md +5 -0
- package/docs/reference/variables/NetworkTypes.md +3 -3
- package/locales/en.json +9 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TWIN DLT IOTA
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package provides utilities for integrating applications with IOTA distributed ledger capabilities, including client construction, transaction submission, sponsored transaction support, and smart contract migration helpers. It is intended for services that need a reliable and repeatable approach to ledger operations without rebuilding common primitives.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,14 +8,12 @@ DLT helpers for use with IOTA.
|
|
|
8
8
|
npm install @twin.org/dlt-iota
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Docker
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
The simplest way to set up the testing environment using our unified container:
|
|
13
|
+
To perform testing of this component it may be necessary to launch a local instance of the gas station to communicate with.
|
|
16
14
|
|
|
17
15
|
```shell
|
|
18
|
-
docker run -d --name twin-gas-station-test -p
|
|
16
|
+
docker run -d --name twin-gas-station-test -p 6379:6379 -p 9527:9527 -p 9184:9184 -e IOTA_NODE_URL="https://api.testnet.iota.cafe" -e GAS_STATION_AUTH="qEyCL6d9BKKFl/tfDGAKeGFkhUlf7FkqiGV7Xw4JUsI=" twinfoundation/twin-gas-station-test:latest
|
|
19
17
|
```
|
|
20
18
|
|
|
21
19
|
## Examples
|
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
export * from "./iota.js";
|
|
4
|
+
export * from "./iotaIdentityUtils.js";
|
|
4
5
|
export * from "./iotaSmartContractUtils.js";
|
|
5
6
|
export * from "./models/IAdminCapFields.js";
|
|
6
7
|
export * from "./models/IContractData.js";
|
|
@@ -9,6 +10,10 @@ export * from "./models/IGasStationConfig.js";
|
|
|
9
10
|
export * from "./models/IGasStationExecuteResponse.js";
|
|
10
11
|
export * from "./models/IGasStationReserveGasResponse.js";
|
|
11
12
|
export * from "./models/IGasStationReserveGasResult.js";
|
|
13
|
+
export * from "./models/IIotaClient.js";
|
|
14
|
+
export * from "./models/IIotaControllerCapInfo.js";
|
|
15
|
+
export * from "./models/IIotaTransaction.js";
|
|
16
|
+
export * from "./models/IIotaTransactionBlockResponse.js";
|
|
12
17
|
export * from "./models/IIotaConfig.js";
|
|
13
18
|
export * from "./models/IIotaDryRun.js";
|
|
14
19
|
export * from "./models/IIotaResponseOptions.js";
|
package/dist/es/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,yCAAyC,CAAC;AACxD,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./iota.js\";\nexport * from \"./iotaSmartContractUtils.js\";\nexport * from \"./models/IAdminCapFields.js\";\nexport * from \"./models/IContractData.js\";\nexport * from \"./models/IGasReservationResult.js\";\nexport * from \"./models/IGasStationConfig.js\";\nexport * from \"./models/IGasStationExecuteResponse.js\";\nexport * from \"./models/IGasStationReserveGasResponse.js\";\nexport * from \"./models/IGasStationReserveGasResult.js\";\nexport * from \"./models/IIotaConfig.js\";\nexport * from \"./models/IIotaDryRun.js\";\nexport * from \"./models/IIotaResponseOptions.js\";\nexport * from \"./models/IMigrationStateFields.js\";\nexport * from \"./models/ISmartContractDeployments.js\";\nexport * from \"./models/ISmartContractObject.js\";\nexport * from \"./models/networkTypes.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,yCAAyC,CAAC;AACxD,cAAc,yBAAyB,CAAC;AACxC,cAAc,oCAAoC,CAAC;AACnD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./iota.js\";\nexport * from \"./iotaIdentityUtils.js\";\nexport * from \"./iotaSmartContractUtils.js\";\nexport * from \"./models/IAdminCapFields.js\";\nexport * from \"./models/IContractData.js\";\nexport * from \"./models/IGasReservationResult.js\";\nexport * from \"./models/IGasStationConfig.js\";\nexport * from \"./models/IGasStationExecuteResponse.js\";\nexport * from \"./models/IGasStationReserveGasResponse.js\";\nexport * from \"./models/IGasStationReserveGasResult.js\";\nexport * from \"./models/IIotaClient.js\";\nexport * from \"./models/IIotaControllerCapInfo.js\";\nexport * from \"./models/IIotaTransaction.js\";\nexport * from \"./models/IIotaTransactionBlockResponse.js\";\nexport * from \"./models/IIotaConfig.js\";\nexport * from \"./models/IIotaDryRun.js\";\nexport * from \"./models/IIotaResponseOptions.js\";\nexport * from \"./models/IMigrationStateFields.js\";\nexport * from \"./models/ISmartContractDeployments.js\";\nexport * from \"./models/ISmartContractObject.js\";\nexport * from \"./models/networkTypes.js\";\n"]}
|
package/dist/es/iota.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
import { IotaClient } from "@iota/iota-sdk/client";
|
|
4
|
+
import { requestIotaFromFaucetV0 } from "@iota/iota-sdk/faucet";
|
|
4
5
|
import { Ed25519Keypair } from "@iota/iota-sdk/keypairs/ed25519";
|
|
5
6
|
import { Transaction } from "@iota/iota-sdk/transactions";
|
|
6
|
-
import { BaseError, Converter, GeneralError, Guards, Is, StringHelper } from "@twin.org/core";
|
|
7
|
-
import { Bip39, Bip44, KeyType } from "@twin.org/crypto";
|
|
7
|
+
import { BaseError, Coerce, Converter, GeneralError, Guards, Is, StringHelper } from "@twin.org/core";
|
|
8
|
+
import { Bip39, Bip44, Blake2b, KeyType } from "@twin.org/crypto";
|
|
8
9
|
import { FetchHelper, HttpMethod } from "@twin.org/web";
|
|
9
10
|
/**
|
|
10
11
|
* Class for performing operations on IOTA.
|
|
@@ -22,18 +23,35 @@ export class Iota {
|
|
|
22
23
|
* Default coin type.
|
|
23
24
|
*/
|
|
24
25
|
static DEFAULT_COIN_TYPE = 4218;
|
|
26
|
+
/**
|
|
27
|
+
* Runtime name for the class.
|
|
28
|
+
*/
|
|
29
|
+
static CLASS_NAME = "Iota";
|
|
25
30
|
/**
|
|
26
31
|
* Default scan range.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
static _DEFAULT_SCAN_RANGE = 1000;
|
|
35
|
+
/**
|
|
36
|
+
* Default pre-calculation chunk size.
|
|
37
|
+
* @internal
|
|
27
38
|
*/
|
|
28
|
-
static
|
|
39
|
+
static _PRE_CALC_CHUNK_SIZE = 25;
|
|
29
40
|
/**
|
|
30
41
|
* Default inclusion timeout.
|
|
42
|
+
* @internal
|
|
31
43
|
*/
|
|
32
|
-
static
|
|
44
|
+
static _DEFAULT_INCLUSION_TIMEOUT = 60;
|
|
33
45
|
/**
|
|
34
|
-
*
|
|
46
|
+
* Default gas budget for all transactions (including sponsored and direct).
|
|
47
|
+
* @internal
|
|
35
48
|
*/
|
|
36
|
-
static
|
|
49
|
+
static _DEFAULT_GAS_BUDGET = 50000000;
|
|
50
|
+
/**
|
|
51
|
+
* Default gas reservation duration.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
static _DEFAULT_GAS_RESERVATION_DURATION = 60;
|
|
37
55
|
/**
|
|
38
56
|
* Create a new IOTA client.
|
|
39
57
|
* @param config The configuration.
|
|
@@ -54,47 +72,115 @@ export class Iota {
|
|
|
54
72
|
config.vaultMnemonicId ??= Iota.DEFAULT_MNEMONIC_SECRET_NAME;
|
|
55
73
|
config.vaultSeedId ??= Iota.DEFAULT_SEED_SECRET_NAME;
|
|
56
74
|
config.coinType ??= Iota.DEFAULT_COIN_TYPE;
|
|
57
|
-
config.inclusionTimeoutSeconds ??= Iota.
|
|
75
|
+
config.inclusionTimeoutSeconds ??= Iota._DEFAULT_INCLUSION_TIMEOUT;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Store a mnemonic in the vault, derive and store the seed, and pre-cache the first keypair chunk.
|
|
79
|
+
* @param vaultConnector The vault connector.
|
|
80
|
+
* @param config The configuration.
|
|
81
|
+
* @param identity The identity of the user to access the vault keys.
|
|
82
|
+
* @param mnemonic The mnemonic to store, if undefined a new one will be generated and returned.
|
|
83
|
+
* @param accountIndex The account index to pre-cache.
|
|
84
|
+
* @returns The mnemonic that was stored.
|
|
85
|
+
*/
|
|
86
|
+
static async storeMnemonic(vaultConnector, config, identity, mnemonic, accountIndex) {
|
|
87
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
88
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
89
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
90
|
+
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
91
|
+
const mnemonicToStore = mnemonic ?? Bip39.randomMnemonic();
|
|
92
|
+
await vaultConnector.setSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId), mnemonicToStore);
|
|
93
|
+
const seed = Bip39.mnemonicToSeed(mnemonicToStore);
|
|
94
|
+
await vaultConnector.setSecret(Iota.buildSeedKey(identity, config.vaultSeedId), Converter.bytesToBase64(seed));
|
|
95
|
+
await Iota.buildKeyPairRange(vaultConnector, config, identity, accountIndex, false, 0);
|
|
96
|
+
return mnemonicToStore;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Derive an address from a public key.
|
|
100
|
+
* @param publicKey The public key to derive the address from.
|
|
101
|
+
* @returns The derived address.
|
|
102
|
+
*/
|
|
103
|
+
static publicKeyToAddress(publicKey) {
|
|
104
|
+
return Converter.bytesToHex(Blake2b.sum256(publicKey), true);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get address for the identity.
|
|
108
|
+
* @param vaultConnector The vault connector.
|
|
109
|
+
* @param config The configuration.
|
|
110
|
+
* @param identity The identity of the user to access the vault keys.
|
|
111
|
+
* @param accountIndex The account index to get the addresses for.
|
|
112
|
+
* @param startAddressIndex The start index for the addresses.
|
|
113
|
+
* @param isInternal Whether the addresses are internal.
|
|
114
|
+
* @returns The address.
|
|
115
|
+
*/
|
|
116
|
+
static async getAddress(vaultConnector, config, identity, accountIndex, startAddressIndex, isInternal) {
|
|
117
|
+
const addresses = await Iota.getAddresses(vaultConnector, config, identity, accountIndex, startAddressIndex, 1, isInternal);
|
|
118
|
+
return addresses[0];
|
|
58
119
|
}
|
|
59
120
|
/**
|
|
60
121
|
* Get addresses for the identity.
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
122
|
+
* @param vaultConnector The vault connector.
|
|
123
|
+
* @param config The configuration.
|
|
124
|
+
* @param identity The identity of the user to access the vault keys.
|
|
63
125
|
* @param accountIndex The account index to get the addresses for.
|
|
64
126
|
* @param startAddressIndex The start index for the addresses.
|
|
65
127
|
* @param count The number of addresses to generate.
|
|
66
128
|
* @param isInternal Whether the addresses are internal.
|
|
67
129
|
* @returns The list of addresses.
|
|
68
130
|
*/
|
|
69
|
-
static getAddresses(
|
|
70
|
-
Guards.
|
|
131
|
+
static async getAddresses(vaultConnector, config, identity, accountIndex, startAddressIndex, count, isInternal) {
|
|
132
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
133
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
134
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
71
135
|
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
72
136
|
Guards.integer(Iota.CLASS_NAME, "startAddressIndex", startAddressIndex);
|
|
73
137
|
Guards.integer(Iota.CLASS_NAME, "count", count);
|
|
74
138
|
const addresses = [];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
139
|
+
const internal = isInternal ?? false;
|
|
140
|
+
let currentIndex = startAddressIndex;
|
|
141
|
+
while (addresses.length < count) {
|
|
142
|
+
const chunkStart = Math.floor(currentIndex / Iota._PRE_CALC_CHUNK_SIZE) * Iota._PRE_CALC_CHUNK_SIZE;
|
|
143
|
+
const keyPairs = await Iota.buildKeyPairRange(vaultConnector, config, identity, accountIndex, internal, chunkStart);
|
|
144
|
+
const chunk = keyPairs.map(kp => Iota.publicKeyToAddress(Converter.base64ToBytes(kp.publicKey)));
|
|
145
|
+
const offsetInChunk = currentIndex - chunkStart;
|
|
146
|
+
const remaining = count - addresses.length;
|
|
147
|
+
addresses.push(...chunk.slice(offsetInChunk, offsetInChunk + remaining));
|
|
148
|
+
currentIndex = chunkStart + Iota._PRE_CALC_CHUNK_SIZE;
|
|
80
149
|
}
|
|
81
150
|
return addresses;
|
|
82
151
|
}
|
|
83
152
|
/**
|
|
84
153
|
* Get a key pair for the specified index.
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
154
|
+
* @param vaultConnector The vault connector.
|
|
155
|
+
* @param config The configuration.
|
|
156
|
+
* @param identity The identity of the user to access the vault keys.
|
|
87
157
|
* @param accountIndex The account index to get the key pair for.
|
|
88
158
|
* @param addressIndex The address index to get the key pair for.
|
|
89
159
|
* @param isInternal Whether the address is internal.
|
|
90
160
|
* @returns The key pair containing private key and public key.
|
|
91
161
|
*/
|
|
92
|
-
static getKeyPair(
|
|
93
|
-
Guards.
|
|
162
|
+
static async getKeyPair(vaultConnector, config, identity, accountIndex, addressIndex, isInternal) {
|
|
163
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
164
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
165
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
94
166
|
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
95
167
|
Guards.integer(Iota.CLASS_NAME, "addressIndex", addressIndex);
|
|
96
|
-
const
|
|
97
|
-
|
|
168
|
+
const internal = isInternal ?? false;
|
|
169
|
+
const chunkStart = Math.floor(addressIndex / Iota._PRE_CALC_CHUNK_SIZE) * Iota._PRE_CALC_CHUNK_SIZE;
|
|
170
|
+
const keyPairs = await Iota.buildKeyPairRange(vaultConnector, config, identity, accountIndex, internal, chunkStart);
|
|
171
|
+
const offsetInChunk = addressIndex - chunkStart;
|
|
172
|
+
const entry = keyPairs[offsetInChunk];
|
|
173
|
+
return {
|
|
174
|
+
privateKey: Converter.base64ToBytes(entry.privateKey),
|
|
175
|
+
publicKey: Converter.base64ToBytes(entry.publicKey)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a new transaction instance.
|
|
180
|
+
* @returns A new transaction instance.
|
|
181
|
+
*/
|
|
182
|
+
static createTransaction() {
|
|
183
|
+
return new Transaction();
|
|
98
184
|
}
|
|
99
185
|
/**
|
|
100
186
|
* Prepare and post a transaction.
|
|
@@ -147,8 +233,7 @@ export class Iota {
|
|
|
147
233
|
if (Is.stringValue(options?.dryRunLabel)) {
|
|
148
234
|
await Iota.dryRunTransaction(client, logging, transaction, owner, options.dryRunLabel);
|
|
149
235
|
}
|
|
150
|
-
const
|
|
151
|
-
const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
|
|
236
|
+
const addressKeyPair = await Iota.findAddress(vaultConnector, config, identity, owner, 0);
|
|
152
237
|
const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
153
238
|
try {
|
|
154
239
|
const response = await client.signAndExecuteTransaction({
|
|
@@ -177,35 +262,32 @@ export class Iota {
|
|
|
177
262
|
}
|
|
178
263
|
}
|
|
179
264
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @param config The configuration to use.
|
|
265
|
+
* Find the address in the seed.
|
|
182
266
|
* @param vaultConnector The vault connector to use.
|
|
267
|
+
* @param config The configuration to use.
|
|
183
268
|
* @param identity The identity of the user to access the vault keys.
|
|
184
|
-
* @returns The seed.
|
|
185
|
-
*/
|
|
186
|
-
static async getSeed(config, vaultConnector, identity) {
|
|
187
|
-
try {
|
|
188
|
-
const seedBase64 = await vaultConnector.getSecret(Iota.buildSeedKey(identity, config.vaultSeedId));
|
|
189
|
-
return Converter.base64ToBytes(seedBase64);
|
|
190
|
-
}
|
|
191
|
-
catch { }
|
|
192
|
-
const mnemonic = await vaultConnector.getSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId));
|
|
193
|
-
return Bip39.mnemonicToSeed(mnemonic);
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Find the address in the seed.
|
|
197
|
-
* @param maxScanRange The maximum range to scan.
|
|
198
|
-
* @param coinType The coin type to use.
|
|
199
|
-
* @param seed The seed to use.
|
|
200
269
|
* @param address The address to find.
|
|
270
|
+
* @param accountIndex The account index to search.
|
|
271
|
+
* @param isInternal Whether to search internal addresses.
|
|
272
|
+
* @param startScanIndex The address index to start scanning from.
|
|
273
|
+
* @param maxScanRange The maximum range to scan.
|
|
201
274
|
* @returns The address key pair.
|
|
202
275
|
* @throws Error if the address is not found.
|
|
203
276
|
*/
|
|
204
|
-
static findAddress(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
277
|
+
static async findAddress(vaultConnector, config, identity, address, accountIndex, isInternal, startScanIndex, maxScanRange) {
|
|
278
|
+
const internal = isInternal ?? false;
|
|
279
|
+
const startIndex = startScanIndex ?? 0;
|
|
280
|
+
const scanRange = maxScanRange ?? config.maxAddressScanRange ?? Iota._DEFAULT_SCAN_RANGE;
|
|
281
|
+
for (let chunkStart = startIndex; chunkStart < startIndex + scanRange; chunkStart += Iota._PRE_CALC_CHUNK_SIZE) {
|
|
282
|
+
const keyPairs = await Iota.buildKeyPairRange(vaultConnector, config, identity, accountIndex, internal, chunkStart);
|
|
283
|
+
const offsetInChunk = keyPairs.findIndex(kp => Iota.publicKeyToAddress(Converter.base64ToBytes(kp.publicKey)) === address);
|
|
284
|
+
if (offsetInChunk !== -1) {
|
|
285
|
+
const entry = keyPairs[offsetInChunk];
|
|
286
|
+
return {
|
|
287
|
+
address,
|
|
288
|
+
privateKey: Converter.base64ToBytes(entry.privateKey),
|
|
289
|
+
publicKey: Converter.base64ToBytes(entry.publicKey)
|
|
290
|
+
};
|
|
209
291
|
}
|
|
210
292
|
}
|
|
211
293
|
throw new GeneralError(Iota.CLASS_NAME, "addressNotFound", { address });
|
|
@@ -241,24 +323,6 @@ export class Iota {
|
|
|
241
323
|
}
|
|
242
324
|
return baseError;
|
|
243
325
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Get the key for storing the mnemonic.
|
|
246
|
-
* @param identity The identity to use.
|
|
247
|
-
* @param vaultMnemonicId The mnemonic ID to use.
|
|
248
|
-
* @returns The mnemonic key.
|
|
249
|
-
*/
|
|
250
|
-
static buildMnemonicKey(identity, vaultMnemonicId) {
|
|
251
|
-
return `${identity}/${vaultMnemonicId ?? Iota.DEFAULT_MNEMONIC_SECRET_NAME}`;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Get the key for storing the seed.
|
|
255
|
-
* @param identity The identity to use.
|
|
256
|
-
* @param vaultSeedId The seed ID to use.
|
|
257
|
-
* @returns The seed key.
|
|
258
|
-
*/
|
|
259
|
-
static buildSeedKey(identity, vaultSeedId) {
|
|
260
|
-
return `${identity}/${vaultSeedId ?? Iota.DEFAULT_SEED_SECRET_NAME}`;
|
|
261
|
-
}
|
|
262
326
|
/**
|
|
263
327
|
* Check if the package exists on the network.
|
|
264
328
|
* @param client The client to use.
|
|
@@ -358,7 +422,7 @@ export class Iota {
|
|
|
358
422
|
* @returns The confirmed transaction response.
|
|
359
423
|
*/
|
|
360
424
|
static async waitForTransactionConfirmation(client, digest, config, options) {
|
|
361
|
-
const timeoutMs = (config.inclusionTimeoutSeconds ?? Iota.
|
|
425
|
+
const timeoutMs = (config.inclusionTimeoutSeconds ?? Iota._DEFAULT_INCLUSION_TIMEOUT) * 1000;
|
|
362
426
|
return client.waitForTransaction({
|
|
363
427
|
digest,
|
|
364
428
|
timeout: timeoutMs,
|
|
@@ -370,21 +434,33 @@ export class Iota {
|
|
|
370
434
|
});
|
|
371
435
|
}
|
|
372
436
|
/**
|
|
373
|
-
* Check if the error is an abort error.
|
|
437
|
+
* Check if the error is an abort error with a specific code.
|
|
374
438
|
* @param error The error to check.
|
|
375
439
|
* @param code The error code to check for.
|
|
376
440
|
* @returns True if the error is an abort error, false otherwise.
|
|
377
441
|
*/
|
|
378
442
|
static isAbortError(error, code) {
|
|
379
443
|
const err = BaseError.fromError(error);
|
|
380
|
-
if (Is.stringValue(err.properties?.error)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
return true;
|
|
444
|
+
if (Is.stringValue(err.properties?.error)) {
|
|
445
|
+
const abortCodeMatch = /abort code\s*:\s*(\d+)/i.exec(err.properties.error);
|
|
446
|
+
return abortCodeMatch?.[1] === code.toString();
|
|
385
447
|
}
|
|
386
448
|
return false;
|
|
387
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Extracts the abort code from a transaction result if the transaction was aborted.
|
|
452
|
+
* @param response The transaction result to extract the abort code from.
|
|
453
|
+
* @returns The abort code if the transaction was aborted, or undefined if it was not an abort error or the code could not be extracted.
|
|
454
|
+
*/
|
|
455
|
+
static extractAbortCode(response) {
|
|
456
|
+
if (response.effects?.status?.status === "failure" &&
|
|
457
|
+
Is.stringValue(response.effects.status.error)) {
|
|
458
|
+
const match = /abort code: (\d+)/.exec(response.effects.status.error);
|
|
459
|
+
if (match) {
|
|
460
|
+
return Coerce.integer(match[1]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
388
464
|
/**
|
|
389
465
|
* Prepare and post a transaction using gas station sponsoring.
|
|
390
466
|
* @param config The configuration.
|
|
@@ -400,18 +476,16 @@ export class Iota {
|
|
|
400
476
|
Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
401
477
|
try {
|
|
402
478
|
// Reserve gas from the gas station
|
|
403
|
-
const
|
|
404
|
-
const gasReservation = await Iota.reserveGas(config, gasBudget);
|
|
479
|
+
const gasReservation = await Iota.reserveGas(config);
|
|
405
480
|
// Set transaction parameters for sponsoring
|
|
406
481
|
transaction.setSender(owner);
|
|
407
482
|
transaction.setGasOwner(gasReservation.sponsorAddress);
|
|
408
483
|
transaction.setGasPayment(gasReservation.gasCoins);
|
|
409
|
-
transaction.setGasBudget(gasBudget);
|
|
484
|
+
transaction.setGasBudget(config.gasBudget ?? Iota._DEFAULT_GAS_BUDGET);
|
|
410
485
|
// Build and sign transaction
|
|
411
486
|
const unsignedTxBytes = await transaction.build({ client });
|
|
412
487
|
// Sign the transaction with the user's private key
|
|
413
|
-
const
|
|
414
|
-
const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
|
|
488
|
+
const addressKeyPair = await Iota.findAddress(vaultConnector, config, identity, owner, 0);
|
|
415
489
|
const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
416
490
|
const signature = await keypair.signTransaction(unsignedTxBytes);
|
|
417
491
|
return await Iota.executeAndConfirmGasStationTransaction(config, client, gasReservation.reservationId, unsignedTxBytes, signature.signature, options);
|
|
@@ -423,16 +497,15 @@ export class Iota {
|
|
|
423
497
|
/**
|
|
424
498
|
* Reserve gas from the gas station.
|
|
425
499
|
* @param config The configuration containing gas station settings.
|
|
426
|
-
* @param gasBudget The gas budget to reserve.
|
|
427
500
|
* @returns The gas reservation result.
|
|
428
501
|
*/
|
|
429
|
-
static async reserveGas(config
|
|
502
|
+
static async reserveGas(config) {
|
|
430
503
|
Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
431
504
|
const requestData = {
|
|
432
505
|
// eslint-disable-next-line camelcase
|
|
433
|
-
gas_budget: gasBudget,
|
|
506
|
+
gas_budget: config.gasBudget ?? Iota._DEFAULT_GAS_BUDGET,
|
|
434
507
|
// eslint-disable-next-line camelcase
|
|
435
|
-
reserve_duration_secs:
|
|
508
|
+
reserve_duration_secs: config.gasReservationDuration ?? Iota._DEFAULT_GAS_RESERVATION_DURATION
|
|
436
509
|
};
|
|
437
510
|
const baseUrl = StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
|
|
438
511
|
const result = await FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/reserve_gas`, HttpMethod.POST, requestData, {
|
|
@@ -504,5 +577,187 @@ export class Iota {
|
|
|
504
577
|
}
|
|
505
578
|
return response;
|
|
506
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* Fund an address with IOTA from the faucet.
|
|
582
|
+
* @param config The configuration containing endpoint information.
|
|
583
|
+
* @param faucetUrl The URL of the faucet to request funds from.
|
|
584
|
+
* @param identity The identity of the user to access the vault keys.
|
|
585
|
+
* @param address The address to fund.
|
|
586
|
+
* @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
|
|
587
|
+
* @returns The amount funded.
|
|
588
|
+
*/
|
|
589
|
+
static async fundAddress(config, faucetUrl, identity, address, timeoutInSeconds = 60) {
|
|
590
|
+
Guards.objectValue(Iota.CLASS_NAME, "config", config);
|
|
591
|
+
Guards.stringValue(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
592
|
+
Guards.stringValue(Iota.CLASS_NAME, "faucetUrl", faucetUrl);
|
|
593
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
594
|
+
Guards.stringValue(Iota.CLASS_NAME, "address", address);
|
|
595
|
+
try {
|
|
596
|
+
const initialBalance = await Iota.getBalance(config, address);
|
|
597
|
+
const response = await requestIotaFromFaucetV0({
|
|
598
|
+
host: faucetUrl,
|
|
599
|
+
recipient: address
|
|
600
|
+
});
|
|
601
|
+
if (response?.error) {
|
|
602
|
+
throw new GeneralError(Iota.CLASS_NAME, "fundingFailed", undefined, response.error);
|
|
603
|
+
}
|
|
604
|
+
// Poll for balance change
|
|
605
|
+
const numTries = Math.ceil(timeoutInSeconds / 5);
|
|
606
|
+
for (let i = 0; i < numTries; i++) {
|
|
607
|
+
const newBalance = await Iota.getBalance(config, address);
|
|
608
|
+
if (newBalance > initialBalance) {
|
|
609
|
+
return newBalance - initialBalance;
|
|
610
|
+
}
|
|
611
|
+
if (i < numTries - 1) {
|
|
612
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
const payloadError = Iota.extractPayloadError(error);
|
|
618
|
+
if (payloadError.message.includes("Too many requests from this client have been sent to the faucet")) {
|
|
619
|
+
throw new GeneralError(Iota.CLASS_NAME, "faucetRateLimit", undefined, Iota.extractPayloadError(error));
|
|
620
|
+
}
|
|
621
|
+
throw new GeneralError(Iota.CLASS_NAME, "fundingFailed", undefined, Iota.extractPayloadError(error));
|
|
622
|
+
}
|
|
623
|
+
return 0n;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Ensure the balance for the given address is at least the given amount.
|
|
627
|
+
* @param config The configuration containing endpoint information.
|
|
628
|
+
* @param faucetUrl The URL of the faucet to request funds from.
|
|
629
|
+
* @param identity The identity of the user to access the vault keys.
|
|
630
|
+
* @param address The address to ensure the balance for.
|
|
631
|
+
* @param ensureBalance The minimum balance to ensure.
|
|
632
|
+
* @param timeoutInSeconds Optional timeout in seconds, defaults to 10 seconds.
|
|
633
|
+
* @returns True if the balance is at least the given amount, false otherwise.
|
|
634
|
+
*/
|
|
635
|
+
static async ensureBalance(config, faucetUrl, identity, address, ensureBalance, timeoutInSeconds) {
|
|
636
|
+
Guards.objectValue(Iota.CLASS_NAME, "config", config);
|
|
637
|
+
Guards.stringValue(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
638
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
639
|
+
Guards.stringValue(Iota.CLASS_NAME, "address", address);
|
|
640
|
+
Guards.bigint(Iota.CLASS_NAME, "ensureBalance", ensureBalance);
|
|
641
|
+
let currentBalance = await Iota.getBalance(config, address);
|
|
642
|
+
if (Is.stringValue(faucetUrl)) {
|
|
643
|
+
let retryCount = 10;
|
|
644
|
+
while (currentBalance < ensureBalance && retryCount > 0) {
|
|
645
|
+
const addedBalance = await Iota.fundAddress(config, faucetUrl, identity, address, timeoutInSeconds);
|
|
646
|
+
if (addedBalance === 0n) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
currentBalance += addedBalance;
|
|
650
|
+
if (currentBalance < ensureBalance) {
|
|
651
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
652
|
+
retryCount--;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return currentBalance >= ensureBalance;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Get the balance for the given address.
|
|
660
|
+
* @param config The configuration containing endpoint information.
|
|
661
|
+
* @param address The address to get the balance for.
|
|
662
|
+
* @returns The balance of the given address.
|
|
663
|
+
*/
|
|
664
|
+
static async getBalance(config, address) {
|
|
665
|
+
Guards.objectValue(Iota.CLASS_NAME, "config", config);
|
|
666
|
+
Guards.stringValue(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
667
|
+
Guards.stringValue(Iota.CLASS_NAME, "address", address);
|
|
668
|
+
const client = Iota.createClient(config);
|
|
669
|
+
const balance = await client.getBalance({
|
|
670
|
+
owner: address
|
|
671
|
+
});
|
|
672
|
+
return BigInt(balance.totalBalance);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get the seed from the vault.
|
|
676
|
+
* @param vaultConnector The vault connector to use.
|
|
677
|
+
* @param config The configuration to use.
|
|
678
|
+
* @param identity The identity of the user to access the vault keys.
|
|
679
|
+
* @returns The seed.
|
|
680
|
+
* @internal
|
|
681
|
+
*/
|
|
682
|
+
static async getSeed(vaultConnector, config, identity) {
|
|
683
|
+
const seedKey = Iota.buildSeedKey(identity, config.vaultSeedId);
|
|
684
|
+
try {
|
|
685
|
+
const seedBase64 = await vaultConnector.getSecret(seedKey);
|
|
686
|
+
return Converter.base64ToBytes(seedBase64);
|
|
687
|
+
}
|
|
688
|
+
catch { }
|
|
689
|
+
const mnemonic = await vaultConnector.getSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId));
|
|
690
|
+
// If the seed is not found but the mnemonic exists, derive the seed and store it for future use
|
|
691
|
+
const seed = Bip39.mnemonicToSeed(mnemonic);
|
|
692
|
+
await vaultConnector.setSecret(seedKey, Converter.bytesToBase64(seed));
|
|
693
|
+
return seed;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get the key for storing the mnemonic.
|
|
697
|
+
* @param identity The identity to use.
|
|
698
|
+
* @param vaultMnemonicId The mnemonic ID to use.
|
|
699
|
+
* @returns The mnemonic key.
|
|
700
|
+
* @internal
|
|
701
|
+
*/
|
|
702
|
+
static buildMnemonicKey(identity, vaultMnemonicId) {
|
|
703
|
+
return `${identity}/${vaultMnemonicId ?? Iota.DEFAULT_MNEMONIC_SECRET_NAME}`;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Get the key for storing the seed.
|
|
707
|
+
* @param identity The identity to use.
|
|
708
|
+
* @param vaultSeedId The seed ID to use.
|
|
709
|
+
* @returns The seed key.
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
static buildSeedKey(identity, vaultSeedId) {
|
|
713
|
+
return `${identity}/${vaultSeedId ?? Iota.DEFAULT_SEED_SECRET_NAME}`;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Build a chunk of key pairs from vault cache or derived from seed.
|
|
717
|
+
* @param vaultConnector The vault connector to use.
|
|
718
|
+
* @param config The configuration to use.
|
|
719
|
+
* @param identity The identity of the user to access the vault keys.
|
|
720
|
+
* @param accountIndex The account index.
|
|
721
|
+
* @param internal Whether the addresses are internal or external.
|
|
722
|
+
* @param addressIndex The starting address index.
|
|
723
|
+
* @returns The key pairs for the chunk.
|
|
724
|
+
* @internal
|
|
725
|
+
*/
|
|
726
|
+
static async buildKeyPairRange(vaultConnector, config, identity, accountIndex, internal, addressIndex) {
|
|
727
|
+
const keyPairChunkKey = Iota.buildKeyPairChunkKey(identity, accountIndex, internal, addressIndex);
|
|
728
|
+
let keyPairChunk;
|
|
729
|
+
try {
|
|
730
|
+
keyPairChunk =
|
|
731
|
+
await vaultConnector.getSecret(keyPairChunkKey);
|
|
732
|
+
}
|
|
733
|
+
catch { }
|
|
734
|
+
if (Is.empty(keyPairChunk)) {
|
|
735
|
+
const startChunkIndex = addressIndex % Iota._PRE_CALC_CHUNK_SIZE;
|
|
736
|
+
const endChunkIndex = startChunkIndex + Iota._PRE_CALC_CHUNK_SIZE;
|
|
737
|
+
const seed = await Iota.getSeed(vaultConnector, config, identity);
|
|
738
|
+
keyPairChunk = [];
|
|
739
|
+
for (let i = startChunkIndex; i < endChunkIndex; i++) {
|
|
740
|
+
const keyPair = Bip44.keyPair(seed, KeyType.Ed25519, config.coinType ?? Iota.DEFAULT_COIN_TYPE, accountIndex, internal, i);
|
|
741
|
+
keyPairChunk.push({
|
|
742
|
+
privateKey: Converter.bytesToBase64(keyPair.privateKey),
|
|
743
|
+
publicKey: Converter.bytesToBase64(keyPair.publicKey)
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
await vaultConnector.setSecret(keyPairChunkKey, keyPairChunk);
|
|
747
|
+
}
|
|
748
|
+
return keyPairChunk;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Get the key for storing a keypair chunk.
|
|
752
|
+
* @param identity The identity to use.
|
|
753
|
+
* @param accountIndex The account index.
|
|
754
|
+
* @param internal Whether the address is internal or external.
|
|
755
|
+
* @param addressIndex The address index to determine the chunk.
|
|
756
|
+
* @returns The keypair chunk key.
|
|
757
|
+
* @internal
|
|
758
|
+
*/
|
|
759
|
+
static buildKeyPairChunkKey(identity, accountIndex, internal, addressIndex) {
|
|
760
|
+
return `${identity}/keypair/${accountIndex}/${internal ? "internal" : "external"}/${addressIndex % Iota._PRE_CALC_CHUNK_SIZE}`;
|
|
761
|
+
}
|
|
507
762
|
}
|
|
508
763
|
//# sourceMappingURL=iota.js.map
|