@stellar/typescript-wallet-sdk 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,6 +100,18 @@ export declare class NoAccountSignersError extends Error {
100
100
  export declare class DeviceKeyEqualsMasterKeyError extends Error {
101
101
  constructor();
102
102
  }
103
+ export declare class DuplicateRecoverySignerError extends Error {
104
+ constructor(serverKeys: string[], signerKey: string);
105
+ }
106
+ export declare class RecoverySignerEqualsDeviceKeyError extends Error {
107
+ constructor(serverKey: string, signerKey: string);
108
+ }
109
+ export declare class DuplicateAccountSignerError extends Error {
110
+ constructor(address: string);
111
+ }
112
+ export declare class SignerKeyEqualsMasterKeyError extends Error {
113
+ constructor(address: string);
114
+ }
103
115
  export declare class NoAccountAndNoSponsorError extends Error {
104
116
  constructor();
105
117
  }
@@ -40,8 +40,21 @@ export declare class Recovery extends AccountRecover {
40
40
  * **Warning**: This transaction will lock master key of the account. Make sure you have access to
41
41
  * specified [RecoverableWalletConfig.deviceAddress]
42
42
  *
43
+ * The returned transaction is unsigned — sign it and submit it yourself. It sets signers and
44
+ * thresholds, so it needs signatures meeting the account's current **high** threshold rather
45
+ * than the master key specifically, and the sponsor must sign too when
46
+ * [RecoverableWalletConfig.sponsorAddress] is set. See [RecoverableWallet] for the cases.
47
+ *
48
+ * The signer set returned by the recovery servers is validated before the transaction is built,
49
+ * because undoing the master key lock afterwards takes a further `SetOptions` meeting the
50
+ * account's high threshold, which the collapsed signer set may no longer be able to reach.
51
+ *
43
52
  * This transaction can be sponsored.
44
53
  * @param {RecoverableWalletConfig} config - The configuration for recoverable wallet.
54
+ * @throws {DeviceKeyEqualsMasterKeyError} If the device address is the account address.
55
+ * @throws {DuplicateRecoverySignerError} If two or more recovery servers return the same signer key.
56
+ * @throws {RecoverySignerEqualsDeviceKeyError} If a recovery server returns the device key.
57
+ * @throws {SignerKeyEqualsMasterKeyError} If a recovery server returns the account's own key.
45
58
  * @returns {Promise<RecoverableWallet>} The wallet.
46
59
  */
47
60
  createRecoverableWallet(config: RecoverableWalletConfig): Promise<RecoverableWallet>;
@@ -63,6 +76,9 @@ export declare class Recovery extends AccountRecover {
63
76
  * @param {AccountThreshold} accountThreshold - Low, medium, and high thresholds to set on the account.
64
77
  * @param {AccountKeypair} [sponsorAddress] - Stellar address of the account sponsoring this transaction.
65
78
  * @param {(builder: CommonBuilder) => CommonBuilder} [builderExtra] - Stellar address of the account sponsoring this transaction.
79
+ * @throws {DuplicateAccountSignerError} If the same address appears more than once in [accountSigners].
80
+ * @throws {SignerKeyEqualsMasterKeyError} If [accountSigners] contains [account] itself.
81
+ * @throws {NoAccountAndNoSponsorError} If the account does not exist and no sponsor is given.
66
82
  * @returns {Promise<Transaction>} The built transaction.
67
83
  */
68
84
  registerRecoveryServerSigners(account: AccountKeypair, accountSigners: AccountSigner[], accountThreshold: AccountThreshold, sponsorAddress?: AccountKeypair, builderExtra?: (builder: CommonBuilder) => CommonBuilder): Promise<Transaction>;
@@ -71,9 +87,27 @@ export declare class Recovery extends AccountRecover {
71
87
  * [SEP-30](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md).
72
88
  * @param {AccountKeypair} account - Account being registerd.
73
89
  * @param {RecoveryIdentityMap} identityMap - map of identities to recovery keys.
74
- * @returns {Promise<string[]>} List of recovery signer public keys.
90
+ * @returns {Promise<EnrolledRecoverySigner[]>} Each recovery signer public key paired with the
91
+ * server that returned it. The pairing is kept so that cross-server validation can name the
92
+ * server responsible when two of them return the same key.
75
93
  */
76
94
  private enrollWithRecoveryServer;
95
+ /**
96
+ * Assert that the recovery servers collectively returned a usable signer set.
97
+ *
98
+ * SEP-30 has each server generate its own unique signing key for the account,
99
+ * so the same key coming back from two servers is a protocol violation, not a
100
+ * judgement call. Left unchecked it halves the recovery signer set while the
101
+ * thresholds sized for the full set are written anyway, which is only
102
+ * discovered when the device is lost and recovery is attempted.
103
+ * @private
104
+ * @param {EnrolledRecoverySigner[]} enrolled - Signer keys paired with the servers that returned them.
105
+ * @param {string} deviceAddress - Address being added as the device signer.
106
+ * @throws {DuplicateRecoverySignerError} If two or more servers returned the same key.
107
+ * @throws {RecoverySignerEqualsDeviceKeyError} If a server returned the device key.
108
+ * @returns {void}
109
+ */
110
+ private validateEnrolledSigners;
77
111
  private getLatestRecoverySigner;
78
112
  private register;
79
113
  }
@@ -22,6 +22,33 @@ export type RecoverableWalletConfig = {
22
22
  sponsorAddress?: AccountKeypair;
23
23
  builderExtra?: (builder: CommonBuilder) => CommonBuilder;
24
24
  };
25
+ /**
26
+ * The result of registering an account with SEP-30 recovery servers.
27
+ * @property {Transaction} transaction - The **unsigned** registration transaction. The SDK builds
28
+ * it but neither signs nor submits it.
29
+ *
30
+ * It sets signers and thresholds, which Stellar authorizes against the account's **high**
31
+ * threshold — not against the master key specifically:
32
+ *
33
+ * - For an account that already exists, whichever of its current signers together reach `high`
34
+ * can authorize it. The master key is neither automatically sufficient (it may carry less
35
+ * weight than `high`) nor necessarily required (an account whose master key is already locked
36
+ * is enrolled by its other signers alone).
37
+ * - For an account created within this same transaction, i.e. the sponsored path where the
38
+ * account does not exist yet, the new account's own key suffices: a new account starts with
39
+ * master weight 1 and zeroed thresholds.
40
+ * - When `sponsorAddress` is supplied the sponsor must sign as well, being the source of the
41
+ * `beginSponsoringFutureReserves` operation and of `createAccount`.
42
+ *
43
+ * For a newly funded account whose master key is still its only signer, that is just
44
+ * `transaction.sign(accountKp.keypair)` followed by `stellar.submitTransaction(transaction)`.
45
+ *
46
+ * Once submitted the master key is locked, so inspect the transaction before signing if you want
47
+ * to verify the signer set it installs.
48
+ * @property {string[]} signers - Public keys of the recovery server signers added to the account,
49
+ * one per configured server. These are the addresses to pass to `signWithRecoveryServers` when
50
+ * recovering the account later.
51
+ */
25
52
  export type RecoverableWallet = {
26
53
  transaction: Transaction;
27
54
  signers: string[];
@@ -41,6 +68,30 @@ export type AccountThreshold = {
41
68
  medium: number;
42
69
  high: number;
43
70
  };
71
+ /**
72
+ * Weights assigned to the device signer and to each recovery server signer.
73
+ *
74
+ * These values only mean something relative to {@link AccountThreshold}. Together they decide
75
+ * three separate properties, and it is possible to satisfy some and silently lose the others:
76
+ *
77
+ * 1. The device can operate the account on its own — `device >= high`.
78
+ * 2. The recovery servers can recover the account without the device —
79
+ * `recoveryServer * serverCount >= high`.
80
+ * 3. No single recovery server can act alone — `recoveryServer < low`. This is a choice, not a
81
+ * protocol guarantee: SEP-30 supports both postures. Two or more servers weighted this way
82
+ * means no individual server controls the account, but the spec equally supports a
83
+ * single-server custodial setup where one deliberately does.
84
+ *
85
+ * With two servers, `{ device: 10, recoveryServer: 5 }` against thresholds `{ low: 10, medium: 10,
86
+ * high: 10 }` satisfies all three: the device alone reaches `high`, the two servers together reach
87
+ * `high`, and one server alone reaches nothing.
88
+ *
89
+ * Note that whether the device can act alone is decided by `device` against `high`, per property 1
90
+ * above — not by how `device` compares to `recoveryServer`. A device weighing less than a single
91
+ * recovery server still operates the account independently as long as it meets `high`.
92
+ * @property {number} device - Weight of the device signer, which replaces the master key.
93
+ * @property {number} recoveryServer - Weight given to each recovery server signer.
94
+ */
44
95
  export type SignerWeight = {
45
96
  device: number;
46
97
  recoveryServer: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellar/typescript-wallet-sdk",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "engines": {
5
5
  "node": ">=22.12.0"
6
6
  },
@@ -274,6 +274,57 @@ export class DeviceKeyEqualsMasterKeyError extends Error {
274
274
  }
275
275
  }
276
276
 
277
+ export class DuplicateRecoverySignerError extends Error {
278
+ constructor(serverKeys: string[], signerKey: string) {
279
+ super(
280
+ `Recovery servers [${serverKeys.join(
281
+ ", ",
282
+ )}] all returned the signer key ${signerKey}. SEP-30 requires each ` +
283
+ `server to generate a unique signing key, so this is either one ` +
284
+ `endpoint configured under several keys in the recovery server map, ` +
285
+ `or a server returning a key it does not control. Registering this ` +
286
+ `set would install one signer where several were intended, so the ` +
287
+ `recovery servers would together carry less weight than the ` +
288
+ `thresholds written alongside them were sized for, and may no longer ` +
289
+ `reach the account's high threshold once the device is lost`,
290
+ );
291
+ Object.setPrototypeOf(this, DuplicateRecoverySignerError.prototype);
292
+ }
293
+ }
294
+
295
+ export class RecoverySignerEqualsDeviceKeyError extends Error {
296
+ constructor(serverKey: string, signerKey: string) {
297
+ super(
298
+ `Recovery server '${serverKey}' returned the device key ${signerKey} ` +
299
+ `as its signer. The recovery signer must differ from the device key, ` +
300
+ `otherwise the device and the server collapse into a single signer`,
301
+ );
302
+ Object.setPrototypeOf(this, RecoverySignerEqualsDeviceKeyError.prototype);
303
+ }
304
+ }
305
+
306
+ export class DuplicateAccountSignerError extends Error {
307
+ constructor(address: string) {
308
+ super(
309
+ `Account signer ${address} is listed more than once. Stellar stores ` +
310
+ `signers keyed by public key and SetOptions overwrites rather than ` +
311
+ `accumulates, so repeated entries would install one signer carrying ` +
312
+ `one weight, not several`,
313
+ );
314
+ Object.setPrototypeOf(this, DuplicateAccountSignerError.prototype);
315
+ }
316
+ }
317
+
318
+ export class SignerKeyEqualsMasterKeyError extends Error {
319
+ constructor(address: string) {
320
+ super(
321
+ `Account signer ${address} is the account's own master key, which ` +
322
+ `cannot be added as a signer of itself`,
323
+ );
324
+ Object.setPrototypeOf(this, SignerKeyEqualsMasterKeyError.prototype);
325
+ }
326
+ }
327
+
277
328
  export class NoAccountAndNoSponsorError extends Error {
278
329
  constructor() {
279
330
  super(`Account does not exist and is not sponsored`);
@@ -21,10 +21,14 @@ import { AccountRecover } from "./AccountRecover";
21
21
  import { Sep10 } from "../Auth";
22
22
  import {
23
23
  DeviceKeyEqualsMasterKeyError,
24
+ DuplicateAccountSignerError,
25
+ DuplicateRecoverySignerError,
24
26
  NoAccountAndNoSponsorError,
25
27
  NoAccountSignersError,
26
28
  RecoveryIdentityNotFoundError,
29
+ RecoverySignerEqualsDeviceKeyError,
27
30
  ServerRequestFailedError,
31
+ SignerKeyEqualsMasterKeyError,
28
32
  } from "../Exceptions";
29
33
  import {
30
34
  AccountKeypair,
@@ -44,6 +48,50 @@ type RecoveryParams = {
44
48
  servers: RecoveryServerMap;
45
49
  };
46
50
 
51
+ /** A signer key paired with the recovery server that returned it. */
52
+ type EnrolledRecoverySigner = {
53
+ serverKey: RecoveryServerKey;
54
+ signerKey: string;
55
+ };
56
+
57
+ /**
58
+ * Assert that a signer set can be installed as written.
59
+ *
60
+ * Stellar stores account signers as a set keyed by public key, and `SetOptions`
61
+ * is an upsert rather than an append — it overwrites a repeated key instead of
62
+ * summing the weights. A set containing the same address twice therefore
63
+ * installs one signer, while the thresholds chosen for the intended set are
64
+ * written alongside it regardless. Because the same transaction also zeroes the
65
+ * master key, repairing that mismatch takes a further `SetOptions`, itself
66
+ * authorized at the account's high threshold — so it is possible only while the
67
+ * remaining signers can still reach `high`, and impossible once they cannot.
68
+ * @param {string} accountAddress - Address of the account whose master key is being locked.
69
+ * @param {AccountSigner[]} accountSigners - The signer set about to be installed.
70
+ * @throws {DuplicateAccountSignerError} If any address appears more than once.
71
+ * @throws {SignerKeyEqualsMasterKeyError} If any address is the account itself.
72
+ * @returns {void}
73
+ */
74
+ const validateAccountSigners = (
75
+ accountAddress: string,
76
+ accountSigners: AccountSigner[],
77
+ ): void => {
78
+ const seen: { [address: string]: true } = {};
79
+
80
+ accountSigners.forEach(({ address }) => {
81
+ const { publicKey } = address;
82
+
83
+ if (publicKey === accountAddress) {
84
+ throw new SignerKeyEqualsMasterKeyError(publicKey);
85
+ }
86
+
87
+ if (seen[publicKey]) {
88
+ throw new DuplicateAccountSignerError(publicKey);
89
+ }
90
+
91
+ seen[publicKey] = true;
92
+ });
93
+ };
94
+
47
95
  /**
48
96
  * Used for Account Recovery using Sep-30.
49
97
  * @see {@link https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md}
@@ -89,8 +137,21 @@ export class Recovery extends AccountRecover {
89
137
  * **Warning**: This transaction will lock master key of the account. Make sure you have access to
90
138
  * specified [RecoverableWalletConfig.deviceAddress]
91
139
  *
140
+ * The returned transaction is unsigned — sign it and submit it yourself. It sets signers and
141
+ * thresholds, so it needs signatures meeting the account's current **high** threshold rather
142
+ * than the master key specifically, and the sponsor must sign too when
143
+ * [RecoverableWalletConfig.sponsorAddress] is set. See [RecoverableWallet] for the cases.
144
+ *
145
+ * The signer set returned by the recovery servers is validated before the transaction is built,
146
+ * because undoing the master key lock afterwards takes a further `SetOptions` meeting the
147
+ * account's high threshold, which the collapsed signer set may no longer be able to reach.
148
+ *
92
149
  * This transaction can be sponsored.
93
150
  * @param {RecoverableWalletConfig} config - The configuration for recoverable wallet.
151
+ * @throws {DeviceKeyEqualsMasterKeyError} If the device address is the account address.
152
+ * @throws {DuplicateRecoverySignerError} If two or more recovery servers return the same signer key.
153
+ * @throws {RecoverySignerEqualsDeviceKeyError} If a recovery server returns the device key.
154
+ * @throws {SignerKeyEqualsMasterKeyError} If a recovery server returns the account's own key.
94
155
  * @returns {Promise<RecoverableWallet>} The wallet.
95
156
  */
96
157
  async createRecoverableWallet(
@@ -100,11 +161,15 @@ export class Recovery extends AccountRecover {
100
161
  throw new DeviceKeyEqualsMasterKeyError();
101
162
  }
102
163
 
103
- const recoverySigners = await this.enrollWithRecoveryServer(
164
+ const enrolled = await this.enrollWithRecoveryServer(
104
165
  config.accountAddress,
105
166
  config.accountIdentity,
106
167
  );
107
168
 
169
+ this.validateEnrolledSigners(enrolled, config.deviceAddress.publicKey);
170
+
171
+ const recoverySigners = enrolled.map(({ signerKey }) => signerKey);
172
+
108
173
  const accountSigners: AccountSigner[] = recoverySigners.map((rs) => ({
109
174
  address: PublicKeypair.fromPublicKey(rs),
110
175
  weight: config.signerWeight.recoveryServer,
@@ -185,6 +250,9 @@ export class Recovery extends AccountRecover {
185
250
  * @param {AccountThreshold} accountThreshold - Low, medium, and high thresholds to set on the account.
186
251
  * @param {AccountKeypair} [sponsorAddress] - Stellar address of the account sponsoring this transaction.
187
252
  * @param {(builder: CommonBuilder) => CommonBuilder} [builderExtra] - Stellar address of the account sponsoring this transaction.
253
+ * @throws {DuplicateAccountSignerError} If the same address appears more than once in [accountSigners].
254
+ * @throws {SignerKeyEqualsMasterKeyError} If [accountSigners] contains [account] itself.
255
+ * @throws {NoAccountAndNoSponsorError} If the account does not exist and no sponsor is given.
188
256
  * @returns {Promise<Transaction>} The built transaction.
189
257
  */
190
258
  async registerRecoveryServerSigners(
@@ -194,6 +262,8 @@ export class Recovery extends AccountRecover {
194
262
  sponsorAddress?: AccountKeypair,
195
263
  builderExtra?: (builder: CommonBuilder) => CommonBuilder,
196
264
  ): Promise<Transaction> {
265
+ validateAccountSigners(account.publicKey, accountSigners);
266
+
197
267
  let accountInfo = undefined;
198
268
 
199
269
  try {
@@ -253,12 +323,14 @@ export class Recovery extends AccountRecover {
253
323
  * [SEP-30](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md).
254
324
  * @param {AccountKeypair} account - Account being registerd.
255
325
  * @param {RecoveryIdentityMap} identityMap - map of identities to recovery keys.
256
- * @returns {Promise<string[]>} List of recovery signer public keys.
326
+ * @returns {Promise<EnrolledRecoverySigner[]>} Each recovery signer public key paired with the
327
+ * server that returned it. The pairing is kept so that cross-server validation can name the
328
+ * server responsible when two of them return the same key.
257
329
  */
258
330
  private async enrollWithRecoveryServer(
259
331
  account: AccountKeypair,
260
332
  identityMap: RecoveryIdentityMap,
261
- ): Promise<string[]> {
333
+ ): Promise<EnrolledRecoverySigner[]> {
262
334
  return Promise.all(
263
335
  Object.keys(this.servers).map(async (key) => {
264
336
  const server = this.servers[key];
@@ -299,11 +371,55 @@ export class Recovery extends AccountRecover {
299
371
  throw new ServerRequestFailedError(e);
300
372
  }
301
373
 
302
- return this.getLatestRecoverySigner(recoveryAccount.signers);
374
+ return {
375
+ serverKey: key,
376
+ signerKey: this.getLatestRecoverySigner(recoveryAccount.signers),
377
+ };
303
378
  }),
304
379
  );
305
380
  }
306
381
 
382
+ /**
383
+ * Assert that the recovery servers collectively returned a usable signer set.
384
+ *
385
+ * SEP-30 has each server generate its own unique signing key for the account,
386
+ * so the same key coming back from two servers is a protocol violation, not a
387
+ * judgement call. Left unchecked it halves the recovery signer set while the
388
+ * thresholds sized for the full set are written anyway, which is only
389
+ * discovered when the device is lost and recovery is attempted.
390
+ * @private
391
+ * @param {EnrolledRecoverySigner[]} enrolled - Signer keys paired with the servers that returned them.
392
+ * @param {string} deviceAddress - Address being added as the device signer.
393
+ * @throws {DuplicateRecoverySignerError} If two or more servers returned the same key.
394
+ * @throws {RecoverySignerEqualsDeviceKeyError} If a server returned the device key.
395
+ * @returns {void}
396
+ */
397
+ private validateEnrolledSigners(
398
+ enrolled: EnrolledRecoverySigner[],
399
+ deviceAddress: string,
400
+ ): void {
401
+ const serverKeysBySigner: { [signerKey: string]: RecoveryServerKey[] } = {};
402
+
403
+ enrolled.forEach(({ serverKey, signerKey }) => {
404
+ serverKeysBySigner[signerKey] = [
405
+ ...(serverKeysBySigner[signerKey] || []),
406
+ serverKey,
407
+ ];
408
+ });
409
+
410
+ Object.keys(serverKeysBySigner).forEach((signerKey) => {
411
+ const serverKeys = serverKeysBySigner[signerKey];
412
+
413
+ if (serverKeys.length > 1) {
414
+ throw new DuplicateRecoverySignerError(serverKeys, signerKey);
415
+ }
416
+
417
+ if (signerKey === deviceAddress) {
418
+ throw new RecoverySignerEqualsDeviceKeyError(serverKeys[0], signerKey);
419
+ }
420
+ });
421
+ }
422
+
307
423
  private getLatestRecoverySigner(signers: RecoveryAccountSigner[]): string {
308
424
  if (signers.length === 0) {
309
425
  throw new NoAccountSignersError();
@@ -25,6 +25,33 @@ export type RecoverableWalletConfig = {
25
25
  builderExtra?: (builder: CommonBuilder) => CommonBuilder;
26
26
  };
27
27
 
28
+ /**
29
+ * The result of registering an account with SEP-30 recovery servers.
30
+ * @property {Transaction} transaction - The **unsigned** registration transaction. The SDK builds
31
+ * it but neither signs nor submits it.
32
+ *
33
+ * It sets signers and thresholds, which Stellar authorizes against the account's **high**
34
+ * threshold — not against the master key specifically:
35
+ *
36
+ * - For an account that already exists, whichever of its current signers together reach `high`
37
+ * can authorize it. The master key is neither automatically sufficient (it may carry less
38
+ * weight than `high`) nor necessarily required (an account whose master key is already locked
39
+ * is enrolled by its other signers alone).
40
+ * - For an account created within this same transaction, i.e. the sponsored path where the
41
+ * account does not exist yet, the new account's own key suffices: a new account starts with
42
+ * master weight 1 and zeroed thresholds.
43
+ * - When `sponsorAddress` is supplied the sponsor must sign as well, being the source of the
44
+ * `beginSponsoringFutureReserves` operation and of `createAccount`.
45
+ *
46
+ * For a newly funded account whose master key is still its only signer, that is just
47
+ * `transaction.sign(accountKp.keypair)` followed by `stellar.submitTransaction(transaction)`.
48
+ *
49
+ * Once submitted the master key is locked, so inspect the transaction before signing if you want
50
+ * to verify the signer set it installs.
51
+ * @property {string[]} signers - Public keys of the recovery server signers added to the account,
52
+ * one per configured server. These are the addresses to pass to `signWithRecoveryServers` when
53
+ * recovering the account later.
54
+ */
28
55
  export type RecoverableWallet = {
29
56
  transaction: Transaction;
30
57
  signers: string[];
@@ -47,6 +74,30 @@ export type AccountThreshold = {
47
74
  high: number;
48
75
  };
49
76
 
77
+ /**
78
+ * Weights assigned to the device signer and to each recovery server signer.
79
+ *
80
+ * These values only mean something relative to {@link AccountThreshold}. Together they decide
81
+ * three separate properties, and it is possible to satisfy some and silently lose the others:
82
+ *
83
+ * 1. The device can operate the account on its own — `device >= high`.
84
+ * 2. The recovery servers can recover the account without the device —
85
+ * `recoveryServer * serverCount >= high`.
86
+ * 3. No single recovery server can act alone — `recoveryServer < low`. This is a choice, not a
87
+ * protocol guarantee: SEP-30 supports both postures. Two or more servers weighted this way
88
+ * means no individual server controls the account, but the spec equally supports a
89
+ * single-server custodial setup where one deliberately does.
90
+ *
91
+ * With two servers, `{ device: 10, recoveryServer: 5 }` against thresholds `{ low: 10, medium: 10,
92
+ * high: 10 }` satisfies all three: the device alone reaches `high`, the two servers together reach
93
+ * `high`, and one server alone reaches nothing.
94
+ *
95
+ * Note that whether the device can act alone is decided by `device` against `high`, per property 1
96
+ * above — not by how `device` compares to `recoveryServer`. A device weighing less than a single
97
+ * recovery server still operates the account independently as long as it meets `high`.
98
+ * @property {number} device - Weight of the device signer, which replaces the master key.
99
+ * @property {number} recoveryServer - Weight given to each recovery server signer.
100
+ */
50
101
  export type SignerWeight = {
51
102
  device: number;
52
103
  recoveryServer: number;
@@ -14,7 +14,7 @@ describe("Customer", () => {
14
14
  await axios.get(
15
15
  "https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
16
16
  );
17
- }, 10000);
17
+ }, 30000);
18
18
 
19
19
  // skipped: the testanchor changed its SEP-12 API behavior
20
20
  test.skip("Sep-12 methods work", async () => {
@@ -89,5 +89,5 @@ describe("Customer", () => {
89
89
 
90
90
  // Delete
91
91
  await sep12.delete();
92
- }, 20000);
92
+ }, 30000);
93
93
  });