@twin.org/dlt-iota 0.0.3-next.9 → 0.9.0-next.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/es/index.js +7 -3
- package/dist/es/index.js.map +1 -1
- package/dist/es/iota.js +356 -93
- package/dist/es/iota.js.map +1 -1
- package/dist/es/iotaIdentityUtils.js +2 -4
- package/dist/es/iotaIdentityUtils.js.map +1 -1
- package/dist/es/iotaSmartContractUtils.js +31 -27
- package/dist/es/iotaSmartContractUtils.js.map +1 -1
- package/dist/es/models/IAdminCapFields.js.map +1 -1
- package/dist/es/models/IGasStationExecuteResponse.js.map +1 -1
- package/dist/es/models/IIotaConfig.js.map +1 -1
- package/dist/es/models/IIotaControllerCapInfo.js.map +1 -1
- package/dist/es/models/IIotaResponseOptions.js.map +1 -1
- package/dist/es/models/IMigrationStateFields.js.map +1 -1
- package/dist/es/models/ISmartContractObject.js.map +1 -1
- package/dist/es/models/ITransactionSigner.js +2 -0
- package/dist/es/models/ITransactionSigner.js.map +1 -0
- package/dist/es/vaultJwkStorage.js +71 -0
- package/dist/es/vaultJwkStorage.js.map +1 -0
- package/dist/es/vaultJwtSigner.js +49 -0
- package/dist/es/vaultJwtSigner.js.map +1 -0
- package/dist/es/vaultSigner.js +60 -0
- package/dist/es/vaultSigner.js.map +1 -0
- package/dist/es/vaultTransactionSigner.js +74 -0
- package/dist/es/vaultTransactionSigner.js.map +1 -0
- package/dist/types/index.d.ts +7 -3
- package/dist/types/iota.d.ts +82 -53
- package/dist/types/iotaIdentityUtils.d.ts +2 -4
- package/dist/types/iotaSmartContractUtils.d.ts +18 -17
- package/dist/types/models/IAdminCapFields.d.ts +2 -2
- package/dist/types/models/IGasStationExecuteResponse.d.ts +1 -3
- package/dist/types/models/IIotaConfig.d.ts +5 -0
- package/dist/types/models/IIotaControllerCapInfo.d.ts +3 -6
- package/dist/types/models/IIotaResponseOptions.d.ts +1 -1
- package/dist/types/models/IMigrationStateFields.d.ts +2 -2
- package/dist/types/models/ISmartContractObject.d.ts +2 -2
- package/dist/types/models/ITransactionSigner.d.ts +27 -0
- package/dist/types/vaultJwkStorage.d.ts +50 -0
- package/dist/types/vaultJwtSigner.d.ts +26 -0
- package/dist/types/vaultSigner.d.ts +32 -0
- package/dist/types/vaultTransactionSigner.d.ts +39 -0
- package/docs/changelog.md +88 -0
- package/docs/examples.md +6 -13
- package/docs/reference/classes/Iota.md +325 -189
- package/docs/reference/classes/IotaIdentityUtils.md +2 -4
- package/docs/reference/classes/IotaSmartContractUtils.md +57 -40
- package/docs/reference/classes/VaultJwtSigner.md +71 -0
- package/docs/reference/classes/VaultSigner.md +106 -0
- package/docs/reference/classes/VaultTransactionSigner.md +122 -0
- package/docs/reference/index.md +4 -0
- package/docs/reference/interfaces/IAdminCapFields.md +2 -2
- package/docs/reference/interfaces/IGasStationExecuteResponse.md +1 -3
- package/docs/reference/interfaces/IIotaConfig.md +14 -0
- package/docs/reference/interfaces/IIotaControllerCapInfo.md +3 -6
- package/docs/reference/interfaces/IIotaResponseOptions.md +1 -1
- package/docs/reference/interfaces/IMigrationStateFields.md +2 -2
- package/docs/reference/interfaces/ISmartContractObject.md +2 -2
- package/docs/reference/interfaces/ITransactionSigner.md +67 -0
- package/locales/en.json +7 -1
- package/package.json +12 -12
package/dist/es/index.js
CHANGED
|
@@ -11,14 +11,18 @@ export * from "./models/IGasStationExecuteResponse.js";
|
|
|
11
11
|
export * from "./models/IGasStationReserveGasResponse.js";
|
|
12
12
|
export * from "./models/IGasStationReserveGasResult.js";
|
|
13
13
|
export * from "./models/IIotaClient.js";
|
|
14
|
-
export * from "./models/IIotaControllerCapInfo.js";
|
|
15
|
-
export * from "./models/IIotaTransaction.js";
|
|
16
|
-
export * from "./models/IIotaTransactionBlockResponse.js";
|
|
17
14
|
export * from "./models/IIotaConfig.js";
|
|
15
|
+
export * from "./models/IIotaControllerCapInfo.js";
|
|
18
16
|
export * from "./models/IIotaDryRun.js";
|
|
19
17
|
export * from "./models/IIotaResponseOptions.js";
|
|
18
|
+
export * from "./models/IIotaTransaction.js";
|
|
19
|
+
export * from "./models/IIotaTransactionBlockResponse.js";
|
|
20
|
+
export * from "./models/ITransactionSigner.js";
|
|
20
21
|
export * from "./models/IMigrationStateFields.js";
|
|
21
22
|
export * from "./models/ISmartContractDeployments.js";
|
|
22
23
|
export * from "./models/ISmartContractObject.js";
|
|
23
24
|
export * from "./models/networkTypes.js";
|
|
25
|
+
export * from "./vaultJwtSigner.js";
|
|
26
|
+
export * from "./vaultSigner.js";
|
|
27
|
+
export * from "./vaultTransactionSigner.js";
|
|
24
28
|
//# sourceMappingURL=index.js.map
|
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,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,
|
|
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,yBAAyB,CAAC;AACxC,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,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/IIotaConfig.js\";\nexport * from \"./models/IIotaControllerCapInfo.js\";\nexport * from \"./models/IIotaDryRun.js\";\nexport * from \"./models/IIotaResponseOptions.js\";\nexport * from \"./models/IIotaTransaction.js\";\nexport * from \"./models/IIotaTransactionBlockResponse.js\";\nexport * from \"./models/ITransactionSigner.js\";\nexport * from \"./models/IMigrationStateFields.js\";\nexport * from \"./models/ISmartContractDeployments.js\";\nexport * from \"./models/ISmartContractObject.js\";\nexport * from \"./models/networkTypes.js\";\nexport * from \"./vaultJwtSigner.js\";\nexport * from \"./vaultSigner.js\";\nexport * from \"./vaultTransactionSigner.js\";\n"]}
|
package/dist/es/iota.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
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 {
|
|
4
|
+
import { requestIotaFromFaucetV0 } from "@iota/iota-sdk/faucet";
|
|
5
5
|
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";
|
|
6
|
+
import { BaseError, Coerce, Converter, GeneralError, Guards, Is, StringHelper } from "@twin.org/core";
|
|
7
|
+
import { Bip39, Bip44, Blake2b, KeyType } from "@twin.org/crypto";
|
|
8
|
+
import { VaultConnectorHelper, VaultKeyType } from "@twin.org/vault-models";
|
|
8
9
|
import { FetchHelper, HttpMethod } from "@twin.org/web";
|
|
10
|
+
import { VaultSigner } from "./vaultSigner.js";
|
|
11
|
+
import { VaultTransactionSigner } from "./vaultTransactionSigner.js";
|
|
9
12
|
/**
|
|
10
13
|
* Class for performing operations on IOTA.
|
|
11
14
|
*/
|
|
@@ -31,11 +34,26 @@ export class Iota {
|
|
|
31
34
|
* @internal
|
|
32
35
|
*/
|
|
33
36
|
static _DEFAULT_SCAN_RANGE = 1000;
|
|
37
|
+
/**
|
|
38
|
+
* Default pre-calculation chunk size.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
static _PRE_CALC_CHUNK_SIZE = 25;
|
|
34
42
|
/**
|
|
35
43
|
* Default inclusion timeout.
|
|
36
44
|
* @internal
|
|
37
45
|
*/
|
|
38
46
|
static _DEFAULT_INCLUSION_TIMEOUT = 60;
|
|
47
|
+
/**
|
|
48
|
+
* Default gas budget for all transactions (including sponsored and direct).
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
static _DEFAULT_GAS_BUDGET = 50000000;
|
|
52
|
+
/**
|
|
53
|
+
* Default gas reservation duration.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
static _DEFAULT_GAS_RESERVATION_DURATION = 60;
|
|
39
57
|
/**
|
|
40
58
|
* Create a new IOTA client.
|
|
41
59
|
* @param config The configuration.
|
|
@@ -58,45 +76,107 @@ export class Iota {
|
|
|
58
76
|
config.coinType ??= Iota.DEFAULT_COIN_TYPE;
|
|
59
77
|
config.inclusionTimeoutSeconds ??= Iota._DEFAULT_INCLUSION_TIMEOUT;
|
|
60
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Store a mnemonic in the vault, derive and store the seed, and pre-cache the first keypair chunk.
|
|
81
|
+
* @param vaultConnector The vault connector.
|
|
82
|
+
* @param config The configuration.
|
|
83
|
+
* @param identity The identity of the user to access the vault keys.
|
|
84
|
+
* @param mnemonic The mnemonic to store, if undefined a new one will be generated and returned.
|
|
85
|
+
* @param accountIndex The account index to pre-cache.
|
|
86
|
+
* @returns The mnemonic that was stored.
|
|
87
|
+
*/
|
|
88
|
+
static async storeMnemonic(vaultConnector, config, identity, mnemonic, accountIndex) {
|
|
89
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
90
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
91
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
92
|
+
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
93
|
+
const mnemonicToStore = mnemonic ?? Bip39.randomMnemonic();
|
|
94
|
+
await vaultConnector.setSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId), mnemonicToStore);
|
|
95
|
+
const seed = Bip39.mnemonicToSeed(mnemonicToStore);
|
|
96
|
+
await vaultConnector.setSecret(Iota.buildSeedKey(identity, config.vaultSeedId), Converter.bytesToBase64(seed));
|
|
97
|
+
await Iota.getPublicKeys(vaultConnector, config, identity, accountIndex, false, 0, async () => seed);
|
|
98
|
+
return mnemonicToStore;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Derive an address from a public key.
|
|
102
|
+
* @param publicKey The public key to derive the address from.
|
|
103
|
+
* @returns The derived address.
|
|
104
|
+
*/
|
|
105
|
+
static publicKeyToAddress(publicKey) {
|
|
106
|
+
return Converter.bytesToHex(Blake2b.sum256(publicKey), true);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get address for the identity.
|
|
110
|
+
* @param vaultConnector The vault connector.
|
|
111
|
+
* @param config The configuration.
|
|
112
|
+
* @param identity The identity of the user to access the vault keys.
|
|
113
|
+
* @param accountIndex The account index to get the addresses for.
|
|
114
|
+
* @param startAddressIndex The start index for the addresses.
|
|
115
|
+
* @param isInternal Whether the addresses are internal.
|
|
116
|
+
* @returns The address.
|
|
117
|
+
*/
|
|
118
|
+
static async getAddress(vaultConnector, config, identity, accountIndex, startAddressIndex, isInternal) {
|
|
119
|
+
const addresses = await Iota.getAddresses(vaultConnector, config, identity, accountIndex, startAddressIndex, 1, isInternal);
|
|
120
|
+
return addresses[0];
|
|
121
|
+
}
|
|
61
122
|
/**
|
|
62
123
|
* Get addresses for the identity.
|
|
63
|
-
* @param
|
|
64
|
-
* @param
|
|
124
|
+
* @param vaultConnector The vault connector.
|
|
125
|
+
* @param config The configuration.
|
|
126
|
+
* @param identity The identity of the user to access the vault keys.
|
|
65
127
|
* @param accountIndex The account index to get the addresses for.
|
|
66
128
|
* @param startAddressIndex The start index for the addresses.
|
|
67
129
|
* @param count The number of addresses to generate.
|
|
68
130
|
* @param isInternal Whether the addresses are internal.
|
|
69
131
|
* @returns The list of addresses.
|
|
70
132
|
*/
|
|
71
|
-
static getAddresses(
|
|
72
|
-
Guards.
|
|
133
|
+
static async getAddresses(vaultConnector, config, identity, accountIndex, startAddressIndex, count, isInternal) {
|
|
134
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
135
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
136
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
73
137
|
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
74
138
|
Guards.integer(Iota.CLASS_NAME, "startAddressIndex", startAddressIndex);
|
|
75
139
|
Guards.integer(Iota.CLASS_NAME, "count", count);
|
|
76
140
|
const addresses = [];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
141
|
+
const internal = isInternal ?? false;
|
|
142
|
+
let currentIndex = startAddressIndex;
|
|
143
|
+
let cachedSeed;
|
|
144
|
+
const seedProvider = async () => {
|
|
145
|
+
cachedSeed ??= await Iota.getSeed(vaultConnector, config, identity);
|
|
146
|
+
return cachedSeed;
|
|
147
|
+
};
|
|
148
|
+
while (addresses.length < count) {
|
|
149
|
+
const chunkStart = Math.floor(currentIndex / Iota._PRE_CALC_CHUNK_SIZE) * Iota._PRE_CALC_CHUNK_SIZE;
|
|
150
|
+
const publicKeys = await Iota.getPublicKeys(vaultConnector, config, identity, accountIndex, internal, chunkStart, seedProvider);
|
|
151
|
+
const chunk = publicKeys.map((pk) => Iota.publicKeyToAddress(Converter.base64ToBytes(pk)));
|
|
152
|
+
const offsetInChunk = currentIndex - chunkStart;
|
|
153
|
+
const remaining = count - addresses.length;
|
|
154
|
+
addresses.push(...chunk.slice(offsetInChunk, offsetInChunk + remaining));
|
|
155
|
+
currentIndex = chunkStart + Iota._PRE_CALC_CHUNK_SIZE;
|
|
82
156
|
}
|
|
83
157
|
return addresses;
|
|
84
158
|
}
|
|
85
159
|
/**
|
|
86
|
-
* Get a
|
|
87
|
-
* @param
|
|
88
|
-
* @param
|
|
89
|
-
* @param
|
|
90
|
-
* @param
|
|
91
|
-
* @param
|
|
92
|
-
* @returns
|
|
160
|
+
* Get a vault-backed transaction signer for the given identity and key indices.
|
|
161
|
+
* @param vaultConnector The vault connector.
|
|
162
|
+
* @param config The configuration.
|
|
163
|
+
* @param identity The identity of the user to access the vault keys.
|
|
164
|
+
* @param accountIndex The account index.
|
|
165
|
+
* @param addressIndex The address index within the account.
|
|
166
|
+
* @returns A VaultTransactionSigner for the specified key.
|
|
93
167
|
*/
|
|
94
|
-
static
|
|
95
|
-
Guards.
|
|
168
|
+
static async getTransactionSigner(vaultConnector, config, identity, accountIndex, addressIndex) {
|
|
169
|
+
Guards.object(Iota.CLASS_NAME, "vaultConnector", vaultConnector);
|
|
170
|
+
Guards.object(Iota.CLASS_NAME, "config", config);
|
|
171
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
96
172
|
Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
97
173
|
Guards.integer(Iota.CLASS_NAME, "addressIndex", addressIndex);
|
|
98
|
-
const
|
|
99
|
-
|
|
174
|
+
const publicKeys = await Iota.getPublicKeys(vaultConnector, config, identity, accountIndex, false, addressIndex, async () => Iota.getSeed(vaultConnector, config, identity));
|
|
175
|
+
const chunkStart = addressIndex - (addressIndex % Iota._PRE_CALC_CHUNK_SIZE);
|
|
176
|
+
const offsetInChunk = addressIndex - chunkStart;
|
|
177
|
+
const publicKey = Converter.base64ToBytes(publicKeys[offsetInChunk]);
|
|
178
|
+
const keyName = Iota.buildAddressKeyName(identity, accountIndex, false, addressIndex);
|
|
179
|
+
return new VaultTransactionSigner(vaultConnector, keyName, publicKey);
|
|
100
180
|
}
|
|
101
181
|
/**
|
|
102
182
|
* Create a new transaction instance.
|
|
@@ -156,13 +236,12 @@ export class Iota {
|
|
|
156
236
|
if (Is.stringValue(options?.dryRunLabel)) {
|
|
157
237
|
await Iota.dryRunTransaction(client, logging, transaction, owner, options.dryRunLabel);
|
|
158
238
|
}
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
239
|
+
const { keyName, publicKey } = await Iota.ensureOwnerKey(vaultConnector, config, identity, owner);
|
|
240
|
+
const signer = new VaultSigner(vaultConnector, keyName, publicKey);
|
|
162
241
|
try {
|
|
163
242
|
const response = await client.signAndExecuteTransaction({
|
|
164
243
|
transaction,
|
|
165
|
-
signer
|
|
244
|
+
signer,
|
|
166
245
|
requestType: "WaitForLocalExecution",
|
|
167
246
|
options: {
|
|
168
247
|
showEffects: options?.showEffects ?? true,
|
|
@@ -185,40 +264,6 @@ export class Iota {
|
|
|
185
264
|
throw new GeneralError(Iota.CLASS_NAME, "transactionFailed", undefined, Iota.extractPayloadError(error));
|
|
186
265
|
}
|
|
187
266
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Get the seed from the vault.
|
|
190
|
-
* @param config The configuration to use.
|
|
191
|
-
* @param vaultConnector The vault connector to use.
|
|
192
|
-
* @param identity The identity of the user to access the vault keys.
|
|
193
|
-
* @returns The seed.
|
|
194
|
-
*/
|
|
195
|
-
static async getSeed(config, vaultConnector, identity) {
|
|
196
|
-
try {
|
|
197
|
-
const seedBase64 = await vaultConnector.getSecret(Iota.buildSeedKey(identity, config.vaultSeedId));
|
|
198
|
-
return Converter.base64ToBytes(seedBase64);
|
|
199
|
-
}
|
|
200
|
-
catch { }
|
|
201
|
-
const mnemonic = await vaultConnector.getSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId));
|
|
202
|
-
return Bip39.mnemonicToSeed(mnemonic);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Find the address in the seed.
|
|
206
|
-
* @param maxScanRange The maximum range to scan.
|
|
207
|
-
* @param coinType The coin type to use.
|
|
208
|
-
* @param seed The seed to use.
|
|
209
|
-
* @param address The address to find.
|
|
210
|
-
* @returns The address key pair.
|
|
211
|
-
* @throws Error if the address is not found.
|
|
212
|
-
*/
|
|
213
|
-
static findAddress(maxScanRange, coinType, seed, address) {
|
|
214
|
-
for (let i = 0; i < maxScanRange; i++) {
|
|
215
|
-
const addressKeyPair = Bip44.address(seed, KeyType.Ed25519, coinType, 0, false, i);
|
|
216
|
-
if (addressKeyPair.address === address) {
|
|
217
|
-
return addressKeyPair;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
throw new GeneralError(Iota.CLASS_NAME, "addressNotFound", { address });
|
|
221
|
-
}
|
|
222
267
|
/**
|
|
223
268
|
* Extract error from SDK payload.
|
|
224
269
|
* Errors from the IOTA SDK are usually not JSON strings but objects.
|
|
@@ -250,24 +295,6 @@ export class Iota {
|
|
|
250
295
|
}
|
|
251
296
|
return baseError;
|
|
252
297
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Get the key for storing the mnemonic.
|
|
255
|
-
* @param identity The identity to use.
|
|
256
|
-
* @param vaultMnemonicId The mnemonic ID to use.
|
|
257
|
-
* @returns The mnemonic key.
|
|
258
|
-
*/
|
|
259
|
-
static buildMnemonicKey(identity, vaultMnemonicId) {
|
|
260
|
-
return `${identity}/${vaultMnemonicId ?? Iota.DEFAULT_MNEMONIC_SECRET_NAME}`;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Get the key for storing the seed.
|
|
264
|
-
* @param identity The identity to use.
|
|
265
|
-
* @param vaultSeedId The seed ID to use.
|
|
266
|
-
* @returns The seed key.
|
|
267
|
-
*/
|
|
268
|
-
static buildSeedKey(identity, vaultSeedId) {
|
|
269
|
-
return `${identity}/${vaultSeedId ?? Iota.DEFAULT_SEED_SECRET_NAME}`;
|
|
270
|
-
}
|
|
271
298
|
/**
|
|
272
299
|
* Check if the package exists on the network.
|
|
273
300
|
* @param client The client to use.
|
|
@@ -306,7 +333,7 @@ export class Iota {
|
|
|
306
333
|
* @param txb The transaction to dry run.
|
|
307
334
|
* @param sender The sender address.
|
|
308
335
|
* @param operation The operation to log.
|
|
309
|
-
* @returns
|
|
336
|
+
* @returns The dry run result including status, costs, events, and object changes.
|
|
310
337
|
*/
|
|
311
338
|
static async dryRunTransaction(client, logging, txb, sender, operation) {
|
|
312
339
|
try {
|
|
@@ -387,11 +414,25 @@ export class Iota {
|
|
|
387
414
|
static isAbortError(error, code) {
|
|
388
415
|
const err = BaseError.fromError(error);
|
|
389
416
|
if (Is.stringValue(err.properties?.error)) {
|
|
390
|
-
const abortCodeMatch = /abort
|
|
417
|
+
const abortCodeMatch = /abort code\s*:\s*(\d+)/i.exec(err.properties.error);
|
|
391
418
|
return abortCodeMatch?.[1] === code.toString();
|
|
392
419
|
}
|
|
393
420
|
return false;
|
|
394
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Extracts the abort code from a transaction result if the transaction was aborted.
|
|
424
|
+
* @param response The transaction result to extract the abort code from.
|
|
425
|
+
* @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.
|
|
426
|
+
*/
|
|
427
|
+
static extractAbortCode(response) {
|
|
428
|
+
if (response.effects?.status?.status === "failure" &&
|
|
429
|
+
Is.stringValue(response.effects.status.error)) {
|
|
430
|
+
const match = /abort code: (\d+)/.exec(response.effects.status.error);
|
|
431
|
+
if (match) {
|
|
432
|
+
return Coerce.integer(match[1]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
395
436
|
/**
|
|
396
437
|
* Prepare and post a transaction using gas station sponsoring.
|
|
397
438
|
* @param config The configuration.
|
|
@@ -407,20 +448,17 @@ export class Iota {
|
|
|
407
448
|
Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
408
449
|
try {
|
|
409
450
|
// Reserve gas from the gas station
|
|
410
|
-
const
|
|
411
|
-
const gasReservation = await Iota.reserveGas(config, gasBudget);
|
|
451
|
+
const gasReservation = await Iota.reserveGas(config);
|
|
412
452
|
// Set transaction parameters for sponsoring
|
|
413
453
|
transaction.setSender(owner);
|
|
414
454
|
transaction.setGasOwner(gasReservation.sponsorAddress);
|
|
415
455
|
transaction.setGasPayment(gasReservation.gasCoins);
|
|
416
|
-
transaction.setGasBudget(gasBudget);
|
|
456
|
+
transaction.setGasBudget(config.gasBudget ?? Iota._DEFAULT_GAS_BUDGET);
|
|
417
457
|
// Build and sign transaction
|
|
418
458
|
const unsignedTxBytes = await transaction.build({ client });
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
423
|
-
const signature = await keypair.signTransaction(unsignedTxBytes);
|
|
459
|
+
const { keyName, publicKey } = await Iota.ensureOwnerKey(vaultConnector, config, identity, owner);
|
|
460
|
+
const signer = new VaultSigner(vaultConnector, keyName, publicKey);
|
|
461
|
+
const signature = await signer.signTransaction(unsignedTxBytes);
|
|
424
462
|
return await Iota.executeAndConfirmGasStationTransaction(config, client, gasReservation.reservationId, unsignedTxBytes, signature.signature, options);
|
|
425
463
|
}
|
|
426
464
|
catch (error) {
|
|
@@ -430,16 +468,15 @@ export class Iota {
|
|
|
430
468
|
/**
|
|
431
469
|
* Reserve gas from the gas station.
|
|
432
470
|
* @param config The configuration containing gas station settings.
|
|
433
|
-
* @param gasBudget The gas budget to reserve.
|
|
434
471
|
* @returns The gas reservation result.
|
|
435
472
|
*/
|
|
436
|
-
static async reserveGas(config
|
|
473
|
+
static async reserveGas(config) {
|
|
437
474
|
Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
438
475
|
const requestData = {
|
|
439
476
|
// eslint-disable-next-line camelcase
|
|
440
|
-
gas_budget: gasBudget,
|
|
477
|
+
gas_budget: config.gasBudget ?? Iota._DEFAULT_GAS_BUDGET,
|
|
441
478
|
// eslint-disable-next-line camelcase
|
|
442
|
-
reserve_duration_secs:
|
|
479
|
+
reserve_duration_secs: config.gasReservationDuration ?? Iota._DEFAULT_GAS_RESERVATION_DURATION
|
|
443
480
|
};
|
|
444
481
|
const baseUrl = StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
|
|
445
482
|
const result = await FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/reserve_gas`, HttpMethod.POST, requestData, {
|
|
@@ -511,5 +548,231 @@ export class Iota {
|
|
|
511
548
|
}
|
|
512
549
|
return response;
|
|
513
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Fund an address with IOTA from the faucet.
|
|
553
|
+
* @param config The configuration containing endpoint information.
|
|
554
|
+
* @param faucetUrl The URL of the faucet to request funds from.
|
|
555
|
+
* @param identity The identity of the user to access the vault keys.
|
|
556
|
+
* @param address The address to fund.
|
|
557
|
+
* @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
|
|
558
|
+
* @returns The amount funded.
|
|
559
|
+
*/
|
|
560
|
+
static async fundAddress(config, faucetUrl, identity, address, timeoutInSeconds = 60) {
|
|
561
|
+
Guards.objectValue(Iota.CLASS_NAME, "config", config);
|
|
562
|
+
Guards.stringValue(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
563
|
+
Guards.stringValue(Iota.CLASS_NAME, "faucetUrl", faucetUrl);
|
|
564
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
565
|
+
Guards.stringValue(Iota.CLASS_NAME, "address", address);
|
|
566
|
+
try {
|
|
567
|
+
const initialBalance = await Iota.getBalance(config, address);
|
|
568
|
+
const response = await requestIotaFromFaucetV0({
|
|
569
|
+
host: faucetUrl,
|
|
570
|
+
recipient: address
|
|
571
|
+
});
|
|
572
|
+
if (response?.error) {
|
|
573
|
+
throw new GeneralError(Iota.CLASS_NAME, "fundingFailed", undefined, response.error);
|
|
574
|
+
}
|
|
575
|
+
// Poll for balance change
|
|
576
|
+
const numTries = Math.ceil(timeoutInSeconds / 5);
|
|
577
|
+
for (let i = 0; i < numTries; i++) {
|
|
578
|
+
const newBalance = await Iota.getBalance(config, address);
|
|
579
|
+
if (newBalance > initialBalance) {
|
|
580
|
+
return newBalance - initialBalance;
|
|
581
|
+
}
|
|
582
|
+
if (i < numTries - 1) {
|
|
583
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
const payloadError = Iota.extractPayloadError(error);
|
|
589
|
+
if (payloadError.message.includes("Too many requests from this client have been sent to the faucet")) {
|
|
590
|
+
throw new GeneralError(Iota.CLASS_NAME, "faucetRateLimit", undefined, Iota.extractPayloadError(error));
|
|
591
|
+
}
|
|
592
|
+
throw new GeneralError(Iota.CLASS_NAME, "fundingFailed", undefined, Iota.extractPayloadError(error));
|
|
593
|
+
}
|
|
594
|
+
return 0n;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Ensure the balance for the given address is at least the given amount.
|
|
598
|
+
* @param config The configuration containing endpoint information.
|
|
599
|
+
* @param faucetUrl The URL of the faucet to request funds from.
|
|
600
|
+
* @param identity The identity of the user to access the vault keys.
|
|
601
|
+
* @param address The address to ensure the balance for.
|
|
602
|
+
* @param ensureBalance The minimum balance to ensure.
|
|
603
|
+
* @param timeoutInSeconds Optional timeout in seconds, defaults to 10 seconds.
|
|
604
|
+
* @returns True if the balance is at least the given amount, false otherwise.
|
|
605
|
+
*/
|
|
606
|
+
static async ensureBalance(config, faucetUrl, identity, address, ensureBalance, timeoutInSeconds) {
|
|
607
|
+
Guards.objectValue(Iota.CLASS_NAME, "config", config);
|
|
608
|
+
Guards.stringValue(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
609
|
+
Guards.stringValue(Iota.CLASS_NAME, "identity", identity);
|
|
610
|
+
Guards.stringValue(Iota.CLASS_NAME, "address", address);
|
|
611
|
+
Guards.bigint(Iota.CLASS_NAME, "ensureBalance", ensureBalance);
|
|
612
|
+
let currentBalance = await Iota.getBalance(config, address);
|
|
613
|
+
if (Is.stringValue(faucetUrl)) {
|
|
614
|
+
let retryCount = 10;
|
|
615
|
+
while (currentBalance < ensureBalance && retryCount > 0) {
|
|
616
|
+
const addedBalance = await Iota.fundAddress(config, faucetUrl, identity, address, timeoutInSeconds);
|
|
617
|
+
if (addedBalance === 0n) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
currentBalance += addedBalance;
|
|
621
|
+
if (currentBalance < ensureBalance) {
|
|
622
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
623
|
+
retryCount--;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return currentBalance >= ensureBalance;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Get the balance for the given address.
|
|
631
|
+
* @param config The configuration containing endpoint information.
|
|
632
|
+
* @param address The address to get the balance for.
|
|
633
|
+
* @returns The balance of the given address.
|
|
634
|
+
*/
|
|
635
|
+
static async getBalance(config, address) {
|
|
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, "address", address);
|
|
639
|
+
const client = Iota.createClient(config);
|
|
640
|
+
const balance = await client.getBalance({
|
|
641
|
+
owner: address
|
|
642
|
+
});
|
|
643
|
+
return BigInt(balance.totalBalance);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Create a transaction instance from the given bytes.
|
|
647
|
+
* @param bytes The transaction bytes to create the transaction from.
|
|
648
|
+
* @returns The transaction instance created from the given bytes.
|
|
649
|
+
*/
|
|
650
|
+
static transactionFromBytes(bytes) {
|
|
651
|
+
return Transaction.from(bytes);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Find the vault key name and public key for the given owner address.
|
|
655
|
+
* Keys are individually registered in the vault by buildKeyPairRange, so no addKey is needed here.
|
|
656
|
+
* @param vaultConnector The vault connector to use.
|
|
657
|
+
* @param config The configuration to use.
|
|
658
|
+
* @param identity The identity of the user to access the vault keys.
|
|
659
|
+
* @param owner The owner address whose key should be located.
|
|
660
|
+
* @param accountIndex The account index to search.
|
|
661
|
+
* @returns The vault key name and the public key bytes.
|
|
662
|
+
* @internal
|
|
663
|
+
*/
|
|
664
|
+
static async ensureOwnerKey(vaultConnector, config, identity, owner, accountIndex = 0) {
|
|
665
|
+
const scanRange = config.maxAddressScanRange ?? Iota._DEFAULT_SCAN_RANGE;
|
|
666
|
+
let cachedSeed;
|
|
667
|
+
const seedProvider = async () => {
|
|
668
|
+
cachedSeed ??= await Iota.getSeed(vaultConnector, config, identity);
|
|
669
|
+
return cachedSeed;
|
|
670
|
+
};
|
|
671
|
+
for (let chunkStart = 0; chunkStart < scanRange; chunkStart += Iota._PRE_CALC_CHUNK_SIZE) {
|
|
672
|
+
const publicKeys = await Iota.getPublicKeys(vaultConnector, config, identity, accountIndex, false, chunkStart, seedProvider);
|
|
673
|
+
const matchIndex = publicKeys.findIndex((pk) => Iota.publicKeyToAddress(Converter.base64ToBytes(pk)) === owner);
|
|
674
|
+
if (matchIndex >= 0) {
|
|
675
|
+
const addressIndex = chunkStart + matchIndex;
|
|
676
|
+
return {
|
|
677
|
+
keyName: Iota.buildAddressKeyName(identity, accountIndex, false, addressIndex),
|
|
678
|
+
publicKey: Converter.base64ToBytes(publicKeys[matchIndex])
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
throw new GeneralError(Iota.CLASS_NAME, "addressNotFound", { address: owner });
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Get the seed from the vault, deriving it from the mnemonic if necessary.
|
|
686
|
+
* @param vaultConnector The vault connector to use.
|
|
687
|
+
* @param config The configuration to use.
|
|
688
|
+
* @param identity The identity of the user to access the vault keys.
|
|
689
|
+
* @returns The seed bytes.
|
|
690
|
+
* @internal
|
|
691
|
+
*/
|
|
692
|
+
static async getSeed(vaultConnector, config, identity) {
|
|
693
|
+
const seedKey = Iota.buildSeedKey(identity, config.vaultSeedId);
|
|
694
|
+
try {
|
|
695
|
+
const seedBase64 = await vaultConnector.getSecret(seedKey);
|
|
696
|
+
return Converter.base64ToBytes(seedBase64);
|
|
697
|
+
}
|
|
698
|
+
catch { }
|
|
699
|
+
const mnemonic = await vaultConnector.getSecret(Iota.buildMnemonicKey(identity, config.vaultMnemonicId));
|
|
700
|
+
// If the seed is not found but the mnemonic exists, derive the seed and store it for future use
|
|
701
|
+
const seed = Bip39.mnemonicToSeed(mnemonic);
|
|
702
|
+
await vaultConnector.setSecret(seedKey, Converter.bytesToBase64(seed));
|
|
703
|
+
return seed;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Get the key for storing the mnemonic.
|
|
707
|
+
* @param identity The identity to use.
|
|
708
|
+
* @param vaultMnemonicId The mnemonic ID to use.
|
|
709
|
+
* @returns The mnemonic key.
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
static buildMnemonicKey(identity, vaultMnemonicId) {
|
|
713
|
+
return VaultConnectorHelper.buildKeyName(identity, vaultMnemonicId ?? Iota.DEFAULT_MNEMONIC_SECRET_NAME);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get the key for storing the seed.
|
|
717
|
+
* @param identity The identity to use.
|
|
718
|
+
* @param vaultSeedId The seed ID to use.
|
|
719
|
+
* @returns The seed key.
|
|
720
|
+
* @internal
|
|
721
|
+
*/
|
|
722
|
+
static buildSeedKey(identity, vaultSeedId) {
|
|
723
|
+
return VaultConnectorHelper.buildKeyName(identity, vaultSeedId ?? Iota.DEFAULT_SEED_SECRET_NAME);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Ensure a range of BIP44-derived keys are registered as individual vault keys and return their public keys.
|
|
727
|
+
* If the first key of the range already exists the range is considered registered and only public keys are derived.
|
|
728
|
+
* If not registered, all keys are derived and added to the vault before returning the public keys.
|
|
729
|
+
* @param vaultConnector The vault connector to use.
|
|
730
|
+
* @param config The configuration to use.
|
|
731
|
+
* @param identity The identity of the user to access the vault keys.
|
|
732
|
+
* @param accountIndex The account index.
|
|
733
|
+
* @param internal Whether the addresses are internal or external.
|
|
734
|
+
* @param addressIndex Any address index within the desired chunk; aligned internally.
|
|
735
|
+
* @param seedProvider Callback invoked at most once per call to supply the seed when a chunk is not yet registered.
|
|
736
|
+
* @returns The base64-encoded public keys for each address in the range.
|
|
737
|
+
* @internal
|
|
738
|
+
*/
|
|
739
|
+
static async getPublicKeys(vaultConnector, config, identity, accountIndex, internal, addressIndex, seedProvider) {
|
|
740
|
+
const chunkStart = addressIndex - (addressIndex % Iota._PRE_CALC_CHUNK_SIZE);
|
|
741
|
+
const firstKeyName = Iota.buildAddressKeyName(identity, accountIndex, internal, chunkStart);
|
|
742
|
+
const publicKeys = [];
|
|
743
|
+
if (await vaultConnector.keyExists(firstKeyName)) {
|
|
744
|
+
for (let i = chunkStart; i < chunkStart + Iota._PRE_CALC_CHUNK_SIZE; i++) {
|
|
745
|
+
const keyName = Iota.buildAddressKeyName(identity, accountIndex, internal, i);
|
|
746
|
+
const keyData = await vaultConnector.getKey(keyName, "public");
|
|
747
|
+
if (!keyData.publicKey) {
|
|
748
|
+
throw new GeneralError(Iota.CLASS_NAME, "missingPublicKey", { keyName });
|
|
749
|
+
}
|
|
750
|
+
publicKeys.push(Converter.bytesToBase64(keyData.publicKey));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
const seed = await seedProvider();
|
|
755
|
+
const coinType = config.coinType ?? Iota.DEFAULT_COIN_TYPE;
|
|
756
|
+
for (let i = chunkStart; i < chunkStart + Iota._PRE_CALC_CHUNK_SIZE; i++) {
|
|
757
|
+
const keyName = Iota.buildAddressKeyName(identity, accountIndex, internal, i);
|
|
758
|
+
const keyPair = Bip44.keyPair(seed, KeyType.Ed25519, coinType, accountIndex, internal, i);
|
|
759
|
+
await vaultConnector.addKey(keyName, VaultKeyType.Ed25519, keyPair.privateKey, keyPair.publicKey);
|
|
760
|
+
publicKeys.push(Converter.bytesToBase64(keyPair.publicKey));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return publicKeys;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Build the vault key name for a specific derived address.
|
|
767
|
+
* @param identity The identity to use.
|
|
768
|
+
* @param accountIndex The account index.
|
|
769
|
+
* @param internal Whether the address is internal or external.
|
|
770
|
+
* @param addressIndex The address index.
|
|
771
|
+
* @returns The vault key name.
|
|
772
|
+
* @internal
|
|
773
|
+
*/
|
|
774
|
+
static buildAddressKeyName(identity, accountIndex, internal, addressIndex) {
|
|
775
|
+
return VaultConnectorHelper.buildKeyName(identity, "account", accountIndex.toString(), internal ? "1" : "0", `${addressIndex}`);
|
|
776
|
+
}
|
|
514
777
|
}
|
|
515
778
|
//# sourceMappingURL=iota.js.map
|