@twin.org/wallet-connector-entity-storage 0.0.1-next.3

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.
@@ -0,0 +1,308 @@
1
+ import { property, entity, LogicalOperator, ComparisonOperator, EntitySchemaFactory, EntitySchemaHelper } from '@twin.org/entity';
2
+ import { Guards, Is, Coerce, GeneralError } from '@twin.org/core';
3
+ import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
4
+ import { Bip39, Bip44, KeyType } from '@twin.org/crypto';
5
+ import { VaultConnectorFactory } from '@twin.org/vault-models';
6
+ import { FaucetConnectorFactory } from '@twin.org/wallet-models';
7
+
8
+ // Copyright 2024 IOTA Stiftung.
9
+ // SPDX-License-Identifier: Apache-2.0.
10
+ /**
11
+ * Class describing a wallet address.
12
+ */
13
+ let WalletAddress = class WalletAddress {
14
+ /**
15
+ * The address in the wallet.
16
+ */
17
+ address;
18
+ /**
19
+ * The identity of the owner.
20
+ */
21
+ identity;
22
+ /**
23
+ * The balance of the wallet as bigint.
24
+ */
25
+ balance;
26
+ };
27
+ __decorate([
28
+ property({ type: "string", isPrimary: true }),
29
+ __metadata("design:type", String)
30
+ ], WalletAddress.prototype, "address", void 0);
31
+ __decorate([
32
+ property({ type: "string" }),
33
+ __metadata("design:type", String)
34
+ ], WalletAddress.prototype, "identity", void 0);
35
+ __decorate([
36
+ property({ type: "string" }),
37
+ __metadata("design:type", String)
38
+ ], WalletAddress.prototype, "balance", void 0);
39
+ WalletAddress = __decorate([
40
+ entity()
41
+ ], WalletAddress);
42
+
43
+ // Copyright 2024 IOTA Stiftung.
44
+ // SPDX-License-Identifier: Apache-2.0.
45
+ /**
46
+ * Class for performing faucet operations using entity storage.
47
+ */
48
+ class EntityStorageFaucetConnector {
49
+ /**
50
+ * The namespace supported by the wallet connector.
51
+ */
52
+ static NAMESPACE = "entity-storage";
53
+ /**
54
+ * Runtime name for the class.
55
+ */
56
+ CLASS_NAME = "EntityStorageFaucetConnector";
57
+ /**
58
+ * The entity storage for wallets.
59
+ * @internal
60
+ */
61
+ _walletAddressEntityStorage;
62
+ /**
63
+ * Create a new instance of EntityStorageFaucetConnector.
64
+ * @param options The options for the wallet connector.
65
+ * @param options.walletAddressEntityStorageType The entity storage type for wallet addresses, defaults to "wallet-address".
66
+ */
67
+ constructor(options) {
68
+ this._walletAddressEntityStorage = EntityStorageConnectorFactory.get(options?.walletAddressEntityStorageType ?? "wallet-address");
69
+ }
70
+ /**
71
+ * Fund the wallet from the faucet.
72
+ * @param identity The identity of the user to access the vault keys.
73
+ * @param address The bech32 encoded address of the address to fund.
74
+ * @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
75
+ * @returns The amount added to the address by the faucet.
76
+ */
77
+ async fundAddress(identity, address, timeoutInSeconds = 60) {
78
+ Guards.stringValue(this.CLASS_NAME, "identity", identity);
79
+ Guards.stringValue(this.CLASS_NAME, "address", address);
80
+ let walletAddress = await this._walletAddressEntityStorage.get(address);
81
+ if (Is.empty(walletAddress)) {
82
+ walletAddress = {
83
+ balance: "0",
84
+ identity,
85
+ address
86
+ };
87
+ }
88
+ const maxFundAmount = 1000000000n;
89
+ const balance = Coerce.bigint(walletAddress.balance) ?? 0n;
90
+ walletAddress.balance = (balance + maxFundAmount).toString();
91
+ await this._walletAddressEntityStorage.set(walletAddress);
92
+ return maxFundAmount;
93
+ }
94
+ }
95
+
96
+ // Copyright 2024 IOTA Stiftung.
97
+ // SPDX-License-Identifier: Apache-2.0.
98
+ /**
99
+ * Class for performing wallet operations using in-memory storage.
100
+ */
101
+ class EntityStorageWalletConnector {
102
+ /**
103
+ * The namespace supported by the wallet connector.
104
+ */
105
+ static NAMESPACE = "entity-storage";
106
+ /**
107
+ * Default name for the mnemonic secret.
108
+ * @internal
109
+ */
110
+ static _DEFAULT_MNEMONIC_SECRET_NAME = "mnemonic";
111
+ /**
112
+ * Default coin type.
113
+ * @internal
114
+ */
115
+ static _DEFAULT_COIN_TYPE = 9999;
116
+ /**
117
+ * Default bech32 hrp.
118
+ * @internal
119
+ */
120
+ static _DEFAULT_BECH32_HRP = "ent";
121
+ /**
122
+ * Runtime name for the class.
123
+ */
124
+ CLASS_NAME = "EntityStorageWalletConnector";
125
+ /**
126
+ * The vault for the mnemonic.
127
+ * @internal
128
+ */
129
+ _vaultConnector;
130
+ /**
131
+ * The faucet.
132
+ * @internal
133
+ */
134
+ _faucetConnector;
135
+ /**
136
+ * The entity storage for wallets.
137
+ * @internal
138
+ */
139
+ _walletAddressEntityStorage;
140
+ /**
141
+ * The configuration to use for tangle operations.
142
+ * @internal
143
+ */
144
+ _config;
145
+ /**
146
+ * Create a new instance of EntityStorageWalletConnector.
147
+ * @param options The options for the wallet connector.
148
+ * @param options.vaultConnectorType Vault connector to use for wallet secrets, defaults to "vault".
149
+ * @param options.faucetConnectorType Optional faucet for requesting funds, defaults to "faucet".
150
+ * @param options.walletAddressEntityStorageType The entity storage for wallets, defaults to "wallet-address".
151
+ * @param options.config The configuration to use.
152
+ */
153
+ constructor(options) {
154
+ this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
155
+ this._faucetConnector = FaucetConnectorFactory.getIfExists(options?.faucetConnectorType ?? "faucet");
156
+ this._walletAddressEntityStorage = EntityStorageConnectorFactory.get(options?.walletAddressEntityStorageType ?? "wallet-address");
157
+ this._config = options?.config ?? {};
158
+ this._config.coinType ??= EntityStorageWalletConnector._DEFAULT_COIN_TYPE;
159
+ this._config.bech32Hrp ??= EntityStorageWalletConnector._DEFAULT_BECH32_HRP;
160
+ }
161
+ /**
162
+ * Create a new wallet.
163
+ * @param identity The identity of the user to access the vault keys.
164
+ * @returns Nothing.
165
+ */
166
+ async create(identity) {
167
+ Guards.stringValue(this.CLASS_NAME, "identity", identity);
168
+ const mnemonic = Bip39.randomMnemonic();
169
+ await this._vaultConnector.setSecret(this.buildMnemonicKey(identity), mnemonic);
170
+ }
171
+ /**
172
+ * Get the addresses for the requested range.
173
+ * @param identity The identity of the user to access the vault keys.
174
+ * @param accountIndex The account index to get the addresses for.
175
+ * @param startAddressIndex The start index for the addresses.
176
+ * @param count The number of addresses to generate.
177
+ * @returns The list of addresses.
178
+ */
179
+ async getAddresses(identity, accountIndex, startAddressIndex, count) {
180
+ Guards.stringValue(this.CLASS_NAME, "identity", identity);
181
+ Guards.integer(this.CLASS_NAME, "startAddressIndex", startAddressIndex);
182
+ Guards.integer(this.CLASS_NAME, "count", count);
183
+ const mnemonic = await this._vaultConnector.getSecret(this.buildMnemonicKey(identity));
184
+ const seed = Bip39.mnemonicToSeed(mnemonic);
185
+ const keyPairs = [];
186
+ for (let i = startAddressIndex; i < startAddressIndex + count; i++) {
187
+ const addressKeyPair = Bip44.addressBech32(seed, KeyType.Ed25519, this._config.bech32Hrp ?? EntityStorageWalletConnector._DEFAULT_BECH32_HRP, this._config.coinType ?? EntityStorageWalletConnector._DEFAULT_COIN_TYPE, accountIndex, false, i);
188
+ keyPairs.push(addressKeyPair.address);
189
+ }
190
+ return keyPairs;
191
+ }
192
+ /**
193
+ * Get the balance for an address in a wallet.
194
+ * @param identity The identity of the user to access the vault keys.
195
+ * @param address The bech32 encoded address.
196
+ * @returns The balance of the wallet address.
197
+ */
198
+ async getBalance(identity, address) {
199
+ Guards.stringValue(this.CLASS_NAME, "address", address);
200
+ const walletAddress = await this._walletAddressEntityStorage.get(address);
201
+ return Coerce.bigint(walletAddress?.balance) ?? 0n;
202
+ }
203
+ /**
204
+ * Ensure the balance for an address in a wallet.
205
+ * @param identity The identity of the user to access the vault keys.
206
+ * @param address The bech32 encoded address.
207
+ * @param ensureBalance The balance to ensure on the address.
208
+ * @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
209
+ * @returns True if the balance has been ensured.
210
+ */
211
+ async ensureBalance(identity, address, ensureBalance, timeoutInSeconds) {
212
+ Guards.stringValue(this.CLASS_NAME, "identity", identity);
213
+ Guards.stringValue(this.CLASS_NAME, "address", address);
214
+ Guards.bigint(this.CLASS_NAME, "ensureBalance", ensureBalance);
215
+ if (this._faucetConnector) {
216
+ let retryCount = 10;
217
+ let currentBalance = await this.getBalance(identity, address);
218
+ while (currentBalance < ensureBalance && retryCount > 0) {
219
+ const addedBalance = await this._faucetConnector.fundAddress(identity, address, timeoutInSeconds);
220
+ if (addedBalance === 0n) {
221
+ // The balance has not increased, so return.
222
+ return false;
223
+ }
224
+ currentBalance += addedBalance;
225
+ if (currentBalance < ensureBalance) {
226
+ // The balance has increased but is still not enough, wait and try again.
227
+ await new Promise(resolve => setTimeout(resolve, 100));
228
+ retryCount--;
229
+ }
230
+ }
231
+ return currentBalance >= ensureBalance;
232
+ }
233
+ return false;
234
+ }
235
+ /**
236
+ * Transfer funds to an address.
237
+ * @param identity The identity of the user to access the vault keys.
238
+ * @param addressSource The bech32 encoded address to send the funds from.
239
+ * @param addressDest The bech32 encoded address to send the funds to.
240
+ * @param amount The amount to transfer.
241
+ * @returns An identifier for the transfer if there was one.
242
+ */
243
+ async transfer(identity, addressSource, addressDest, amount) {
244
+ Guards.stringValue(this.CLASS_NAME, "identity", identity);
245
+ Guards.stringValue(this.CLASS_NAME, "addressSource", addressSource);
246
+ Guards.stringValue(this.CLASS_NAME, "addressDest", addressDest);
247
+ Guards.bigint(this.CLASS_NAME, "amount", amount);
248
+ const walletAddresses = await this._walletAddressEntityStorage.query({
249
+ logicalOperator: LogicalOperator.And,
250
+ conditions: [
251
+ {
252
+ property: "identity",
253
+ comparison: ComparisonOperator.Equals,
254
+ value: identity
255
+ },
256
+ {
257
+ property: "address",
258
+ comparison: ComparisonOperator.Equals,
259
+ value: addressSource
260
+ }
261
+ ]
262
+ });
263
+ let walletAddress;
264
+ let balance = 0n;
265
+ if (walletAddresses.entities.length > 0) {
266
+ walletAddress = walletAddresses.entities[0];
267
+ balance = BigInt(walletAddress.balance);
268
+ walletAddress.balance = (BigInt(walletAddress.balance) - amount).toString();
269
+ }
270
+ if (balance < amount) {
271
+ throw new GeneralError(this.CLASS_NAME, "insufficientFunds");
272
+ }
273
+ if (!Is.empty(walletAddress)) {
274
+ await this._walletAddressEntityStorage.set(walletAddress);
275
+ let destWalletAddress = await this._walletAddressEntityStorage.get(addressDest);
276
+ if (Is.empty(destWalletAddress)) {
277
+ destWalletAddress = {
278
+ balance: "0",
279
+ identity: "",
280
+ address: addressDest
281
+ };
282
+ }
283
+ destWalletAddress.balance = (BigInt(destWalletAddress.balance) + amount).toString();
284
+ await this._walletAddressEntityStorage.set(destWalletAddress);
285
+ }
286
+ return undefined;
287
+ }
288
+ /**
289
+ * Build the key name to access the mnemonic in the vault.
290
+ * @param identity The identity of the user to access the vault keys.
291
+ * @returns The vault key.
292
+ * @internal
293
+ */
294
+ buildMnemonicKey(identity) {
295
+ return `${identity}/${this._config.vaultMnemonicId ?? EntityStorageWalletConnector._DEFAULT_MNEMONIC_SECRET_NAME}`;
296
+ }
297
+ }
298
+
299
+ // Copyright 2024 IOTA Stiftung.
300
+ // SPDX-License-Identifier: Apache-2.0.
301
+ /**
302
+ * Initialize the schema for the wallet entity storage connector.
303
+ */
304
+ function initSchema() {
305
+ EntitySchemaFactory.register("WalletAddress", () => EntitySchemaHelper.getSchema(WalletAddress));
306
+ }
307
+
308
+ export { EntityStorageFaucetConnector, EntityStorageWalletConnector, WalletAddress, initSchema };
Binary file
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Class describing a wallet address.
3
+ */
4
+ export declare class WalletAddress {
5
+ /**
6
+ * The address in the wallet.
7
+ */
8
+ address: string;
9
+ /**
10
+ * The identity of the owner.
11
+ */
12
+ identity: string;
13
+ /**
14
+ * The balance of the wallet as bigint.
15
+ */
16
+ balance: string;
17
+ }
@@ -0,0 +1,30 @@
1
+ import type { IFaucetConnector } from "@twin.org/wallet-models";
2
+ /**
3
+ * Class for performing faucet operations using entity storage.
4
+ */
5
+ export declare class EntityStorageFaucetConnector implements IFaucetConnector {
6
+ /**
7
+ * The namespace supported by the wallet connector.
8
+ */
9
+ static readonly NAMESPACE: string;
10
+ /**
11
+ * Runtime name for the class.
12
+ */
13
+ readonly CLASS_NAME: string;
14
+ /**
15
+ * Create a new instance of EntityStorageFaucetConnector.
16
+ * @param options The options for the wallet connector.
17
+ * @param options.walletAddressEntityStorageType The entity storage type for wallet addresses, defaults to "wallet-address".
18
+ */
19
+ constructor(options?: {
20
+ walletAddressEntityStorageType?: string;
21
+ });
22
+ /**
23
+ * Fund the wallet from the faucet.
24
+ * @param identity The identity of the user to access the vault keys.
25
+ * @param address The bech32 encoded address of the address to fund.
26
+ * @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
27
+ * @returns The amount added to the address by the faucet.
28
+ */
29
+ fundAddress(identity: string, address: string, timeoutInSeconds?: number): Promise<bigint>;
30
+ }
@@ -0,0 +1,69 @@
1
+ import { type IWalletConnector } from "@twin.org/wallet-models";
2
+ import type { IEntityStorageWalletConnectorConfig } from "./models/IEntityStorageWalletConnectorConfig";
3
+ /**
4
+ * Class for performing wallet operations using in-memory storage.
5
+ */
6
+ export declare class EntityStorageWalletConnector implements IWalletConnector {
7
+ /**
8
+ * The namespace supported by the wallet connector.
9
+ */
10
+ static readonly NAMESPACE: string;
11
+ /**
12
+ * Runtime name for the class.
13
+ */
14
+ readonly CLASS_NAME: string;
15
+ /**
16
+ * Create a new instance of EntityStorageWalletConnector.
17
+ * @param options The options for the wallet connector.
18
+ * @param options.vaultConnectorType Vault connector to use for wallet secrets, defaults to "vault".
19
+ * @param options.faucetConnectorType Optional faucet for requesting funds, defaults to "faucet".
20
+ * @param options.walletAddressEntityStorageType The entity storage for wallets, defaults to "wallet-address".
21
+ * @param options.config The configuration to use.
22
+ */
23
+ constructor(options?: {
24
+ vaultConnectorType?: string;
25
+ faucetConnectorType?: string;
26
+ walletAddressEntityStorageType?: string;
27
+ config?: IEntityStorageWalletConnectorConfig;
28
+ });
29
+ /**
30
+ * Create a new wallet.
31
+ * @param identity The identity of the user to access the vault keys.
32
+ * @returns Nothing.
33
+ */
34
+ create(identity: string): Promise<void>;
35
+ /**
36
+ * Get the addresses for the requested range.
37
+ * @param identity The identity of the user to access the vault keys.
38
+ * @param accountIndex The account index to get the addresses for.
39
+ * @param startAddressIndex The start index for the addresses.
40
+ * @param count The number of addresses to generate.
41
+ * @returns The list of addresses.
42
+ */
43
+ getAddresses(identity: string, accountIndex: number, startAddressIndex: number, count: number): Promise<string[]>;
44
+ /**
45
+ * Get the balance for an address in a wallet.
46
+ * @param identity The identity of the user to access the vault keys.
47
+ * @param address The bech32 encoded address.
48
+ * @returns The balance of the wallet address.
49
+ */
50
+ getBalance(identity: string, address: string): Promise<bigint>;
51
+ /**
52
+ * Ensure the balance for an address in a wallet.
53
+ * @param identity The identity of the user to access the vault keys.
54
+ * @param address The bech32 encoded address.
55
+ * @param ensureBalance The balance to ensure on the address.
56
+ * @param timeoutInSeconds The timeout in seconds to wait for the funding to complete.
57
+ * @returns True if the balance has been ensured.
58
+ */
59
+ ensureBalance(identity: string, address: string, ensureBalance: bigint, timeoutInSeconds?: number): Promise<boolean>;
60
+ /**
61
+ * Transfer funds to an address.
62
+ * @param identity The identity of the user to access the vault keys.
63
+ * @param addressSource The bech32 encoded address to send the funds from.
64
+ * @param addressDest The bech32 encoded address to send the funds to.
65
+ * @param amount The amount to transfer.
66
+ * @returns An identifier for the transfer if there was one.
67
+ */
68
+ transfer(identity: string, addressSource: string, addressDest: string, amount: bigint): Promise<string | undefined>;
69
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./entities/walletAddress";
2
+ export * from "./entityStorageFaucetConnector";
3
+ export * from "./entityStorageWalletConnector";
4
+ export * from "./models/IEntityStorageWalletConnectorConfig";
5
+ export * from "./schema";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Configuration for the Entity Storage Wallet Connector.
3
+ */
4
+ export interface IEntityStorageWalletConnectorConfig {
5
+ /**
6
+ * The id of the entry in the vault containing the mnemonic.
7
+ * @default mnemonic
8
+ */
9
+ vaultMnemonicId?: string;
10
+ /**
11
+ * The coin type.
12
+ * @default 9999
13
+ */
14
+ coinType?: number;
15
+ /**
16
+ * The bech32 human readable part for the addresses.
17
+ * @default ent
18
+ */
19
+ bech32Hrp?: string;
20
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Initialize the schema for the wallet entity storage connector.
3
+ */
4
+ export declare function initSchema(): void;
@@ -0,0 +1,5 @@
1
+ # @twin.org/wallet-connector-entity-storage - Changelog
2
+
3
+ ## v0.0.1-next.3
4
+
5
+ - Initial Release
@@ -0,0 +1 @@
1
+ # @twin.org/wallet-connector-entity-storage - Examples
@@ -0,0 +1,81 @@
1
+ # Class: EntityStorageFaucetConnector
2
+
3
+ Class for performing faucet operations using entity storage.
4
+
5
+ ## Implements
6
+
7
+ - `IFaucetConnector`
8
+
9
+ ## Constructors
10
+
11
+ ### new EntityStorageFaucetConnector()
12
+
13
+ > **new EntityStorageFaucetConnector**(`options`?): [`EntityStorageFaucetConnector`](EntityStorageFaucetConnector.md)
14
+
15
+ Create a new instance of EntityStorageFaucetConnector.
16
+
17
+ #### Parameters
18
+
19
+ • **options?**
20
+
21
+ The options for the wallet connector.
22
+
23
+ • **options.walletAddressEntityStorageType?**: `string`
24
+
25
+ The entity storage type for wallet addresses, defaults to "wallet-address".
26
+
27
+ #### Returns
28
+
29
+ [`EntityStorageFaucetConnector`](EntityStorageFaucetConnector.md)
30
+
31
+ ## Properties
32
+
33
+ ### NAMESPACE
34
+
35
+ > `readonly` `static` **NAMESPACE**: `string` = `"entity-storage"`
36
+
37
+ The namespace supported by the wallet connector.
38
+
39
+ ***
40
+
41
+ ### CLASS\_NAME
42
+
43
+ > `readonly` **CLASS\_NAME**: `string`
44
+
45
+ Runtime name for the class.
46
+
47
+ #### Implementation of
48
+
49
+ `IFaucetConnector.CLASS_NAME`
50
+
51
+ ## Methods
52
+
53
+ ### fundAddress()
54
+
55
+ > **fundAddress**(`identity`, `address`, `timeoutInSeconds`): `Promise`\<`bigint`\>
56
+
57
+ Fund the wallet from the faucet.
58
+
59
+ #### Parameters
60
+
61
+ • **identity**: `string`
62
+
63
+ The identity of the user to access the vault keys.
64
+
65
+ • **address**: `string`
66
+
67
+ The bech32 encoded address of the address to fund.
68
+
69
+ • **timeoutInSeconds**: `number` = `60`
70
+
71
+ The timeout in seconds to wait for the funding to complete.
72
+
73
+ #### Returns
74
+
75
+ `Promise`\<`bigint`\>
76
+
77
+ The amount added to the address by the faucet.
78
+
79
+ #### Implementation of
80
+
81
+ `IFaucetConnector.fundAddress`