@twin.org/dlt-iota 0.0.2-next.8 → 0.0.3-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.
Files changed (52) hide show
  1. package/README.md +1 -81
  2. package/dist/es/index.js +19 -0
  3. package/dist/es/index.js.map +1 -0
  4. package/dist/es/iota.js +508 -0
  5. package/dist/es/iota.js.map +1 -0
  6. package/dist/es/iotaSmartContractUtils.js +413 -0
  7. package/dist/es/iotaSmartContractUtils.js.map +1 -0
  8. package/dist/es/models/IAdminCapFields.js +4 -0
  9. package/dist/es/models/IAdminCapFields.js.map +1 -0
  10. package/dist/es/models/IContractData.js +4 -0
  11. package/dist/es/models/IContractData.js.map +1 -0
  12. package/dist/es/models/IGasReservationResult.js +2 -0
  13. package/dist/es/models/IGasReservationResult.js.map +1 -0
  14. package/dist/es/models/IGasStationConfig.js +4 -0
  15. package/dist/es/models/IGasStationConfig.js.map +1 -0
  16. package/dist/es/models/IGasStationExecuteResponse.js +4 -0
  17. package/dist/es/models/IGasStationExecuteResponse.js.map +1 -0
  18. package/dist/es/models/IGasStationReserveGasResponse.js +2 -0
  19. package/dist/es/models/IGasStationReserveGasResponse.js.map +1 -0
  20. package/dist/es/models/IGasStationReserveGasResult.js +2 -0
  21. package/dist/es/models/IGasStationReserveGasResult.js.map +1 -0
  22. package/dist/es/models/IIotaConfig.js +2 -0
  23. package/dist/es/models/IIotaConfig.js.map +1 -0
  24. package/dist/es/models/IIotaDryRun.js +2 -0
  25. package/dist/es/models/IIotaDryRun.js.map +1 -0
  26. package/dist/es/models/IIotaResponseOptions.js +2 -0
  27. package/dist/es/models/IIotaResponseOptions.js.map +1 -0
  28. package/dist/es/models/IMigrationStateFields.js +4 -0
  29. package/dist/es/models/IMigrationStateFields.js.map +1 -0
  30. package/dist/es/models/ISmartContractDeployments.js +2 -0
  31. package/dist/es/models/ISmartContractDeployments.js.map +1 -0
  32. package/dist/es/models/ISmartContractObject.js +4 -0
  33. package/dist/es/models/ISmartContractObject.js.map +1 -0
  34. package/dist/es/models/networkTypes.js +21 -0
  35. package/dist/es/models/networkTypes.js.map +1 -0
  36. package/dist/types/index.d.ts +16 -16
  37. package/dist/types/iota.d.ts +8 -4
  38. package/dist/types/iotaSmartContractUtils.d.ts +2 -2
  39. package/dist/types/models/IContractData.d.ts +4 -0
  40. package/dist/types/models/IGasStationReserveGasResponse.d.ts +1 -1
  41. package/dist/types/models/IIotaConfig.d.ts +1 -1
  42. package/dist/types/models/ISmartContractDeployments.d.ts +2 -2
  43. package/docs/changelog.md +51 -0
  44. package/docs/reference/classes/Iota.md +11 -3
  45. package/docs/reference/classes/IotaSmartContractUtils.md +3 -3
  46. package/docs/reference/interfaces/IContractData.md +8 -0
  47. package/docs/reference/interfaces/IGasStationExecuteResponse.md +1 -1
  48. package/docs/reference/interfaces/IGasStationReserveGasResponse.md +1 -1
  49. package/locales/en.json +6 -2
  50. package/package.json +21 -12
  51. package/dist/cjs/index.cjs +0 -946
  52. package/dist/esm/index.mjs +0 -942
package/README.md CHANGED
@@ -12,90 +12,10 @@ npm install @twin.org/dlt-iota
12
12
 
13
13
  The tests developed are functional tests and need an instance of the IOTA Gas Station and Redis up and running.
14
14
 
15
- ### Quick Setup (Recommended)
16
-
17
15
  The simplest way to set up the testing environment using our unified container:
18
16
 
19
17
  ```shell
20
- # Start the unified container with Redis + Gas Station
21
- docker run -d --name twin-gas-station-test \
22
- -p 6379:6379 -p 9527:9527 -p 9184:9184 \
23
- twinfoundation/twin-gas-station-test:latest
24
-
25
- # Wait a moment for services to start, then verify
26
- docker exec twin-gas-station-test redis-cli ping # Should return: PONG
27
- curl http://localhost:9527/ # Should return: OK
28
-
29
- # Services are ready - you can now run tests
30
- npm run test
31
-
32
- # When finished, cleanup
33
- docker stop twin-gas-station-test && docker rm twin-gas-station-test
34
- ```
35
-
36
- That's it! The unified container includes:
37
-
38
- - Redis server (port 6379)
39
- - IOTA Gas Station (port 9527, metrics 9184)
40
- - Pre-configured with test keypair and settings
41
- - Health checks and proper startup sequencing
42
-
43
- ### Alternative Setup Methods
44
-
45
- For advanced users who prefer alternative setups:
46
-
47
- #### Option A: Standalone Docker Images
48
-
49
- If you prefer to run services separately:
50
-
51
- ```shell
52
- # Pull the required Docker images
53
- docker pull redis:7-alpine
54
- docker pull iotaledger/gas-station:latest
55
-
56
- # Start Redis
57
- docker run -d --name gas-station-redis -p 6379:6379 redis:7-alpine
58
-
59
- # Create gas station config file
60
- cat > gas-station-config.yaml << EOF
61
- signer-config:
62
- local:
63
- keypair: AKT1Ghtd+yNbI9fFCQin3FpiGx8xoUdJMe7iAhoFUm4f
64
- rpc-host-ip: 0.0.0.0
65
- rpc-port: 9527
66
- metrics-port: 9184
67
- storage-config:
68
- redis:
69
- redis_url: "redis://127.0.0.1:6379"
70
- fullnode-url: "https://api.testnet.iota.cafe"
71
- coin-init-config:
72
- target-init-balance: 100000000
73
- refresh-interval-sec: 86400
74
- daily-gas-usage-cap: 1500000000000
75
- access-controller:
76
- access-policy: disabled
77
- EOF
78
-
79
- # Start IOTA Gas Station
80
- docker run -d --name gas-station \
81
- -p 9527:9527 -p 9184:9184 \
82
- -v $(pwd)/gas-station-config.yaml:/config/config.yaml \
83
- --network host \
84
- -e GAS_STATION_AUTH=qEyCL6d9BKKFl/tfDGAKeGFkhUlf7FkqiGV7Xw4JUsI= \
85
- iotaledger/gas-station:latest \
86
- --config-path /config/config.yaml
87
- ```
88
-
89
- #### Option B: Docker Compose
90
-
91
- Using the IOTA gas station repository:
92
-
93
- ```shell
94
- # Clone and setup gas station repository
95
- git clone https://github.com/iotaledger/gas-station.git
96
- cd gas-station/docker
97
- ../target/debug/tool generate-sample-config --docker-compose --config-path config.yaml --network testnet
98
- GAS_STATION_AUTH=qEyCL6d9BKKFl/tfDGAKeGFkhUlf7FkqiGV7Xw4JUsI= docker-compose up -d
18
+ docker run -d --name twin-gas-station-test -p 9527:9527 -p 6379:6379 -p 9184:9184 twinfoundation/twin-gas-station-test:latest
99
19
  ```
100
20
 
101
21
  ## Examples
@@ -0,0 +1,19 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./iota.js";
4
+ export * from "./iotaSmartContractUtils.js";
5
+ export * from "./models/IAdminCapFields.js";
6
+ export * from "./models/IContractData.js";
7
+ export * from "./models/IGasReservationResult.js";
8
+ export * from "./models/IGasStationConfig.js";
9
+ export * from "./models/IGasStationExecuteResponse.js";
10
+ export * from "./models/IGasStationReserveGasResponse.js";
11
+ export * from "./models/IGasStationReserveGasResult.js";
12
+ export * from "./models/IIotaConfig.js";
13
+ export * from "./models/IIotaDryRun.js";
14
+ export * from "./models/IIotaResponseOptions.js";
15
+ export * from "./models/IMigrationStateFields.js";
16
+ export * from "./models/ISmartContractDeployments.js";
17
+ export * from "./models/ISmartContractObject.js";
18
+ export * from "./models/networkTypes.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,508 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { IotaClient } from "@iota/iota-sdk/client";
4
+ import { Ed25519Keypair } from "@iota/iota-sdk/keypairs/ed25519";
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";
8
+ import { FetchHelper, HttpMethod } from "@twin.org/web";
9
+ /**
10
+ * Class for performing operations on IOTA.
11
+ */
12
+ export class Iota {
13
+ /**
14
+ * Default name for the mnemonic secret.
15
+ */
16
+ static DEFAULT_MNEMONIC_SECRET_NAME = "mnemonic";
17
+ /**
18
+ * Default name for the seed secret.
19
+ */
20
+ static DEFAULT_SEED_SECRET_NAME = "seed";
21
+ /**
22
+ * Default coin type.
23
+ */
24
+ static DEFAULT_COIN_TYPE = 4218;
25
+ /**
26
+ * Default scan range.
27
+ */
28
+ static DEFAULT_SCAN_RANGE = 1000;
29
+ /**
30
+ * Default inclusion timeout.
31
+ */
32
+ static DEFAULT_INCLUSION_TIMEOUT = 60;
33
+ /**
34
+ * Runtime name for the class.
35
+ */
36
+ static CLASS_NAME = "Iota";
37
+ /**
38
+ * Create a new IOTA client.
39
+ * @param config The configuration.
40
+ * @returns The client instance.
41
+ */
42
+ static createClient(config) {
43
+ Guards.object(Iota.CLASS_NAME, "config", config);
44
+ Guards.object(Iota.CLASS_NAME, "config.clientOptions", config.clientOptions);
45
+ Guards.string(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
46
+ return new IotaClient(config.clientOptions);
47
+ }
48
+ /**
49
+ * Create configuration using defaults where necessary.
50
+ * @param config The configuration to populate.
51
+ */
52
+ static populateConfig(config) {
53
+ Guards.object(Iota.CLASS_NAME, "config.clientOptions", config.clientOptions);
54
+ config.vaultMnemonicId ??= Iota.DEFAULT_MNEMONIC_SECRET_NAME;
55
+ config.vaultSeedId ??= Iota.DEFAULT_SEED_SECRET_NAME;
56
+ config.coinType ??= Iota.DEFAULT_COIN_TYPE;
57
+ config.inclusionTimeoutSeconds ??= Iota.DEFAULT_INCLUSION_TIMEOUT;
58
+ }
59
+ /**
60
+ * Get addresses for the identity.
61
+ * @param seed The seed to use for generating addresses.
62
+ * @param coinType The coin type to use.
63
+ * @param accountIndex The account index to get the addresses for.
64
+ * @param startAddressIndex The start index for the addresses.
65
+ * @param count The number of addresses to generate.
66
+ * @param isInternal Whether the addresses are internal.
67
+ * @returns The list of addresses.
68
+ */
69
+ static getAddresses(seed, coinType, accountIndex, startAddressIndex, count, isInternal) {
70
+ Guards.integer(Iota.CLASS_NAME, "coinType", coinType);
71
+ Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
72
+ Guards.integer(Iota.CLASS_NAME, "startAddressIndex", startAddressIndex);
73
+ Guards.integer(Iota.CLASS_NAME, "count", count);
74
+ const addresses = [];
75
+ for (let i = startAddressIndex; i < startAddressIndex + count; i++) {
76
+ // Derive the keypair using the seed
77
+ const keyPair = Bip44.keyPair(seed, KeyType.Ed25519, coinType ?? Iota.DEFAULT_COIN_TYPE, accountIndex, isInternal ?? false, i);
78
+ const keypair = Ed25519Keypair.fromSecretKey(keyPair.privateKey);
79
+ addresses.push(keypair.getPublicKey().toIotaAddress());
80
+ }
81
+ return addresses;
82
+ }
83
+ /**
84
+ * Get a key pair for the specified index.
85
+ * @param seed The seed to use for generating the key pair.
86
+ * @param coinType The coin type to use.
87
+ * @param accountIndex The account index to get the key pair for.
88
+ * @param addressIndex The address index to get the key pair for.
89
+ * @param isInternal Whether the address is internal.
90
+ * @returns The key pair containing private key and public key.
91
+ */
92
+ static getKeyPair(seed, coinType, accountIndex, addressIndex, isInternal) {
93
+ Guards.integer(Iota.CLASS_NAME, "coinType", coinType);
94
+ Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
95
+ Guards.integer(Iota.CLASS_NAME, "addressIndex", addressIndex);
96
+ const keyPair = Bip44.keyPair(seed, KeyType.Ed25519, coinType ?? Iota.DEFAULT_COIN_TYPE, accountIndex, isInternal ?? false, addressIndex);
97
+ return keyPair;
98
+ }
99
+ /**
100
+ * Prepare and post a transaction.
101
+ * @param config The configuration.
102
+ * @param vaultConnector The vault connector.
103
+ * @param logging The logging component.
104
+ * @param identity The identity of the user to access the vault keys.
105
+ * @param client The client instance.
106
+ * @param source The source address.
107
+ * @param amount The amount to transfer.
108
+ * @param recipient The recipient address.
109
+ * @param options The transaction options.
110
+ * @returns The transaction result.
111
+ */
112
+ static async prepareAndPostValueTransaction(config, vaultConnector, logging, identity, client, source, amount, recipient, options) {
113
+ try {
114
+ const txb = new Transaction();
115
+ const [coin] = txb.splitCoins(txb.gas, [txb.pure.u64(amount)]);
116
+ txb.transferObjects([coin], txb.pure.address(recipient));
117
+ // Check if gas station configuration is present
118
+ if (Is.object(config.gasStation)) {
119
+ return await Iota.prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, source, txb);
120
+ }
121
+ const result = await Iota.prepareAndPostTransaction(config, vaultConnector, logging, identity, client, source, txb, options);
122
+ return result;
123
+ }
124
+ catch (error) {
125
+ throw new GeneralError(Iota.CLASS_NAME, "valueTransactionFailed", undefined, Iota.extractPayloadError(error));
126
+ }
127
+ }
128
+ /**
129
+ * Prepare and post a transaction.
130
+ * @param config The configuration.
131
+ * @param vaultConnector The vault connector.
132
+ * @param logging The logging component.
133
+ * @param identity The identity of the user to access the vault keys.
134
+ * @param client The client instance.
135
+ * @param owner The owner of the address.
136
+ * @param transaction The transaction to execute.
137
+ * @param options The transaction options.
138
+ * @returns The transaction response.
139
+ */
140
+ static async prepareAndPostTransaction(config, vaultConnector, logging, identity, client, owner, transaction, options) {
141
+ // Check if gas station configuration is present
142
+ if (Is.object(config.gasStation)) {
143
+ return Iota.prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, owner, transaction, options);
144
+ }
145
+ // Traditional transaction flow
146
+ // Dry run the transaction if cost logging is enabled to get the gas and storage costs
147
+ if (Is.stringValue(options?.dryRunLabel)) {
148
+ await Iota.dryRunTransaction(client, logging, transaction, owner, options.dryRunLabel);
149
+ }
150
+ const seed = await Iota.getSeed(config, vaultConnector, identity);
151
+ const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
152
+ const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
153
+ try {
154
+ const response = await client.signAndExecuteTransaction({
155
+ transaction,
156
+ signer: keypair,
157
+ requestType: "WaitForLocalExecution",
158
+ options: {
159
+ showEffects: options?.showEffects ?? true,
160
+ showEvents: options?.showEvents ?? true,
161
+ showObjectChanges: options?.showObjectChanges ?? true
162
+ }
163
+ });
164
+ if (options?.waitForConfirmation ?? true) {
165
+ // Wait for transaction to be indexed and available over API
166
+ const confirmedTransaction = await Iota.waitForTransactionConfirmation(client, response.digest, config, {
167
+ showEffects: options?.showEffects ?? true,
168
+ showEvents: options?.showEvents ?? true,
169
+ showObjectChanges: options?.showObjectChanges ?? true
170
+ });
171
+ return confirmedTransaction;
172
+ }
173
+ return response;
174
+ }
175
+ catch (error) {
176
+ throw new GeneralError(Iota.CLASS_NAME, "transactionFailed", undefined, Iota.extractPayloadError(error));
177
+ }
178
+ }
179
+ /**
180
+ * Get the seed from the vault.
181
+ * @param config The configuration to use.
182
+ * @param vaultConnector The vault connector to use.
183
+ * @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
+ * @param address The address to find.
201
+ * @returns The address key pair.
202
+ * @throws Error if the address is not found.
203
+ */
204
+ static findAddress(maxScanRange, coinType, seed, address) {
205
+ for (let i = 0; i < maxScanRange; i++) {
206
+ const addressKeyPair = Bip44.address(seed, KeyType.Ed25519, coinType, 0, false, i);
207
+ if (addressKeyPair.address === address) {
208
+ return addressKeyPair;
209
+ }
210
+ }
211
+ throw new GeneralError(Iota.CLASS_NAME, "addressNotFound", { address });
212
+ }
213
+ /**
214
+ * Extract error from SDK payload.
215
+ * Errors from the IOTA SDK are usually not JSON strings but objects.
216
+ * @param error The error to extract.
217
+ * @returns The extracted error.
218
+ */
219
+ static extractPayloadError(error) {
220
+ if (Is.object(error)) {
221
+ if (!Is.empty(error.inner)) {
222
+ error.inner = Iota.extractPayloadError(error.inner);
223
+ }
224
+ if (!Is.empty(error.cause)) {
225
+ error.cause = Iota.extractPayloadError(error.cause);
226
+ }
227
+ if (error.code === "InsufficientGas") {
228
+ return new GeneralError(Iota.CLASS_NAME, "insufficientFunds");
229
+ }
230
+ else if (error.message?.startsWith("ErrorObject")) {
231
+ const msg = /message: "(.*)"/.exec(error.message);
232
+ if (msg && msg.length > 1) {
233
+ error = msg[1];
234
+ }
235
+ }
236
+ }
237
+ const baseError = BaseError.fromError(error);
238
+ if (baseError.name === "Base" && !Is.stringValue(baseError.source)) {
239
+ baseError.name = "IOTA";
240
+ baseError.source = Iota.CLASS_NAME;
241
+ }
242
+ return baseError;
243
+ }
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
+ /**
263
+ * Check if the package exists on the network.
264
+ * @param client The client to use.
265
+ * @param packageId The package ID to check.
266
+ * @returns True if the package exists, false otherwise.
267
+ */
268
+ static async packageExistsOnNetwork(client, packageId) {
269
+ try {
270
+ const packageObject = await client.getObject({
271
+ id: packageId,
272
+ options: {
273
+ showType: true
274
+ }
275
+ });
276
+ if ("error" in packageObject) {
277
+ if (packageObject?.error?.code === "notExists") {
278
+ return false;
279
+ }
280
+ throw new GeneralError(Iota.CLASS_NAME, "packageObjectError", {
281
+ packageId,
282
+ error: packageObject.error
283
+ });
284
+ }
285
+ return true;
286
+ }
287
+ catch (error) {
288
+ throw new GeneralError(Iota.CLASS_NAME, "packageNotFoundOnNetwork", {
289
+ packageId
290
+ }, Iota.extractPayloadError(error));
291
+ }
292
+ }
293
+ /**
294
+ * Dry run a transaction and log the results.
295
+ * @param client The IOTA client.
296
+ * @param logging The logging component.
297
+ * @param txb The transaction to dry run.
298
+ * @param sender The sender address.
299
+ * @param operation The operation to log.
300
+ * @returns void.
301
+ */
302
+ static async dryRunTransaction(client, logging, txb, sender, operation) {
303
+ try {
304
+ txb.setSender(sender);
305
+ const builtTx = await txb.build({
306
+ client,
307
+ onlyTransactionKind: false
308
+ });
309
+ const dryRunResult = await client.dryRunTransactionBlock({
310
+ transactionBlock: builtTx
311
+ });
312
+ if (dryRunResult.effects.status?.status !== "success") {
313
+ throw new GeneralError(Iota.CLASS_NAME, "dryRunFailed", {
314
+ error: dryRunResult.effects?.status?.error
315
+ });
316
+ }
317
+ const result = {
318
+ status: dryRunResult.effects.status.status,
319
+ costs: {
320
+ computationCost: dryRunResult.effects.gasUsed.computationCost,
321
+ computationCostBurned: dryRunResult.effects.gasUsed.computationCostBurned,
322
+ storageCost: dryRunResult.effects.gasUsed.storageCost,
323
+ storageRebate: dryRunResult.effects.gasUsed.storageRebate,
324
+ nonRefundableStorageFee: dryRunResult.effects.gasUsed.nonRefundableStorageFee
325
+ },
326
+ events: dryRunResult.events ?? [],
327
+ balanceChanges: dryRunResult.balanceChanges ?? [],
328
+ objectChanges: dryRunResult.objectChanges ?? []
329
+ };
330
+ await logging?.log({
331
+ level: "info",
332
+ source: Iota.CLASS_NAME,
333
+ ts: Date.now(),
334
+ message: "transactionCosts",
335
+ data: {
336
+ operation,
337
+ cost: JSON.stringify(result)
338
+ }
339
+ });
340
+ return result;
341
+ }
342
+ catch (error) {
343
+ if (BaseError.isErrorName(error, GeneralError.CLASS_NAME)) {
344
+ throw error;
345
+ }
346
+ throw new GeneralError(Iota.CLASS_NAME, "dryRunFailed", undefined, Iota.extractPayloadError(error));
347
+ }
348
+ }
349
+ /**
350
+ * Wait for a transaction to be indexed and available over the API.
351
+ * @param client The IOTA client instance.
352
+ * @param digest The digest of the transaction to wait for.
353
+ * @param config The IOTA configuration.
354
+ * @param options Additional options for the transaction query.
355
+ * @param options.showEffects Whether to show effects.
356
+ * @param options.showEvents Whether to show events.
357
+ * @param options.showObjectChanges Whether to show object changes.
358
+ * @returns The confirmed transaction response.
359
+ */
360
+ static async waitForTransactionConfirmation(client, digest, config, options) {
361
+ const timeoutMs = (config.inclusionTimeoutSeconds ?? Iota.DEFAULT_INCLUSION_TIMEOUT) * 1000;
362
+ return client.waitForTransaction({
363
+ digest,
364
+ timeout: timeoutMs,
365
+ options: {
366
+ showEffects: options?.showEffects ?? true,
367
+ showEvents: options?.showEvents ?? true,
368
+ showObjectChanges: options?.showObjectChanges ?? true
369
+ }
370
+ });
371
+ }
372
+ /**
373
+ * Check if the error is an abort error.
374
+ * @param error The error to check.
375
+ * @param code The error code to check for.
376
+ * @returns True if the error is an abort error, false otherwise.
377
+ */
378
+ static isAbortError(error, code) {
379
+ const err = BaseError.fromError(error);
380
+ if (Is.stringValue(err.properties?.error) && err.properties.error.startsWith("MoveAbort")) {
381
+ if (Is.number(code)) {
382
+ return err.properties.error.includes(code.toString());
383
+ }
384
+ return true;
385
+ }
386
+ return false;
387
+ }
388
+ /**
389
+ * Prepare and post a transaction using gas station sponsoring.
390
+ * @param config The configuration.
391
+ * @param vaultConnector The vault connector.
392
+ * @param identity The identity of the user to access the vault keys.
393
+ * @param client The client instance.
394
+ * @param owner The owner of the address.
395
+ * @param transaction The transaction to execute.
396
+ * @param options Response options including confirmation behavior.
397
+ * @returns The transaction response.
398
+ */
399
+ static async prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, owner, transaction, options) {
400
+ Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
401
+ try {
402
+ // Reserve gas from the gas station
403
+ const gasBudget = config.gasBudget ?? 50000000;
404
+ const gasReservation = await Iota.reserveGas(config, gasBudget);
405
+ // Set transaction parameters for sponsoring
406
+ transaction.setSender(owner);
407
+ transaction.setGasOwner(gasReservation.sponsorAddress);
408
+ transaction.setGasPayment(gasReservation.gasCoins);
409
+ transaction.setGasBudget(gasBudget);
410
+ // Build and sign transaction
411
+ const unsignedTxBytes = await transaction.build({ client });
412
+ // Sign the transaction with the user's private key
413
+ const seed = await Iota.getSeed(config, vaultConnector, identity);
414
+ const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
415
+ const keypair = Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
416
+ const signature = await keypair.signTransaction(unsignedTxBytes);
417
+ return await Iota.executeAndConfirmGasStationTransaction(config, client, gasReservation.reservationId, unsignedTxBytes, signature.signature, options);
418
+ }
419
+ catch (error) {
420
+ throw new GeneralError(Iota.CLASS_NAME, "gasStationTransactionFailed", undefined, Iota.extractPayloadError(error));
421
+ }
422
+ }
423
+ /**
424
+ * Reserve gas from the gas station.
425
+ * @param config The configuration containing gas station settings.
426
+ * @param gasBudget The gas budget to reserve.
427
+ * @returns The gas reservation result.
428
+ */
429
+ static async reserveGas(config, gasBudget) {
430
+ Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
431
+ const requestData = {
432
+ // eslint-disable-next-line camelcase
433
+ gas_budget: gasBudget,
434
+ // eslint-disable-next-line camelcase
435
+ reserve_duration_secs: 30
436
+ };
437
+ const baseUrl = StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
438
+ const result = await FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/reserve_gas`, HttpMethod.POST, requestData, {
439
+ headers: {
440
+ Authorization: `Bearer ${config.gasStation.gasStationAuthToken}`
441
+ }
442
+ });
443
+ const apiResponse = result.result;
444
+ return {
445
+ sponsorAddress: apiResponse.sponsor_address,
446
+ reservationId: apiResponse.reservation_id,
447
+ gasCoins: apiResponse.gas_coins
448
+ };
449
+ }
450
+ /**
451
+ * Execute a sponsored transaction through the gas station.
452
+ * @param config The configuration containing gas station settings.
453
+ * @param reservationId The reservation ID from gas reservation.
454
+ * @param transactionBytes The unsigned transaction bytes.
455
+ * @param userSignature The user's signature.
456
+ * @returns The transaction response.
457
+ */
458
+ static async executeGasStationTransaction(config, reservationId, transactionBytes, userSignature) {
459
+ Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
460
+ const requestData = {
461
+ // eslint-disable-next-line camelcase
462
+ reservation_id: reservationId,
463
+ // eslint-disable-next-line camelcase
464
+ tx_bytes: Converter.bytesToBase64(transactionBytes),
465
+ // eslint-disable-next-line camelcase
466
+ user_sig: userSignature
467
+ };
468
+ const baseUrl = StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
469
+ const result = await FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/execute_tx`, HttpMethod.POST, requestData, {
470
+ headers: {
471
+ Authorization: `Bearer ${config.gasStation.gasStationAuthToken}`
472
+ }
473
+ });
474
+ const effectsData = result.effects;
475
+ // Match IotaTransactionBlockResponse format
476
+ return {
477
+ digest: effectsData.transactionDigest,
478
+ effects: effectsData,
479
+ events: [],
480
+ objectChanges: [],
481
+ confirmedLocalExecution: true
482
+ };
483
+ }
484
+ /**
485
+ * Execute and confirm a gas station transaction.
486
+ * @param config The configuration containing gas station settings.
487
+ * @param client The IOTA client for confirmation.
488
+ * @param reservationId The reservation ID from gas reservation.
489
+ * @param transactionBytes The unsigned transaction bytes.
490
+ * @param userSignature The user's signature.
491
+ * @param options Response options including confirmation behavior.
492
+ * @returns The transaction response (confirmed if waitForConfirmation is true).
493
+ */
494
+ static async executeAndConfirmGasStationTransaction(config, client, reservationId, transactionBytes, userSignature, options) {
495
+ Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
496
+ const response = await Iota.executeGasStationTransaction(config, reservationId, transactionBytes, userSignature);
497
+ if (options?.waitForConfirmation ?? true) {
498
+ const confirmedTransaction = await Iota.waitForTransactionConfirmation(client, response.digest, config, {
499
+ showEffects: options?.showEffects ?? true,
500
+ showEvents: options?.showEvents ?? true,
501
+ showObjectChanges: options?.showObjectChanges ?? true
502
+ });
503
+ return confirmedTransaction;
504
+ }
505
+ return response;
506
+ }
507
+ }
508
+ //# sourceMappingURL=iota.js.map