@stellar/typescript-wallet-sdk 1.2.0 → 1.3.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.
Files changed (96) hide show
  1. package/.eslintrc.js +76 -0
  2. package/.github/workflows/integrationTest.yml +19 -0
  3. package/.github/workflows/runTests.yml +14 -0
  4. package/.husky/pre-commit +1 -0
  5. package/README.md +19 -12
  6. package/examples/sep24/.env.example +4 -0
  7. package/examples/sep24/README.md +17 -0
  8. package/examples/sep24/sep24.ts +69 -14
  9. package/examples/tsconfig.json +10 -0
  10. package/jest.config.js +1 -0
  11. package/jest.integration.config.js +9 -0
  12. package/lib/bundle.js +6929 -2377
  13. package/lib/bundle.js.map +1 -1
  14. package/lib/bundle_browser.js +6915 -2410
  15. package/lib/bundle_browser.js.map +1 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/walletSdk/Anchor/Sep24.d.ts +65 -22
  18. package/lib/walletSdk/Anchor/Sep38.d.ts +56 -0
  19. package/lib/walletSdk/Anchor/Sep6.d.ts +127 -0
  20. package/lib/walletSdk/Anchor/index.d.ts +91 -1
  21. package/lib/walletSdk/Asset/index.d.ts +11 -1
  22. package/lib/walletSdk/Auth/WalletSigner.d.ts +41 -1
  23. package/lib/walletSdk/Auth/index.d.ts +21 -0
  24. package/lib/walletSdk/Customer/index.d.ts +70 -0
  25. package/lib/walletSdk/Exceptions/index.d.ts +45 -2
  26. package/lib/walletSdk/Horizon/AccountService.d.ts +30 -20
  27. package/lib/walletSdk/Horizon/Stellar.d.ts +79 -2
  28. package/lib/walletSdk/Horizon/Transaction/CommonTransactionBuilder.d.ts +57 -0
  29. package/lib/walletSdk/Horizon/Transaction/SponsoringBuilder.d.ts +38 -0
  30. package/lib/walletSdk/Horizon/Transaction/TransactionBuilder.d.ts +112 -11
  31. package/lib/walletSdk/Horizon/index.d.ts +3 -1
  32. package/lib/walletSdk/Recovery/AccountRecover.d.ts +58 -0
  33. package/lib/walletSdk/Recovery/index.d.ts +69 -7
  34. package/lib/walletSdk/Types/anchor.d.ts +17 -2
  35. package/lib/walletSdk/Types/auth.d.ts +14 -1
  36. package/lib/walletSdk/Types/horizon.d.ts +17 -6
  37. package/lib/walletSdk/Types/index.d.ts +15 -3
  38. package/lib/walletSdk/Types/recovery.d.ts +128 -0
  39. package/lib/walletSdk/Types/sep12.d.ts +57 -0
  40. package/lib/walletSdk/Types/sep38.d.ts +93 -0
  41. package/lib/walletSdk/Types/sep6.d.ts +160 -0
  42. package/lib/walletSdk/Types/watcher.d.ts +7 -2
  43. package/lib/walletSdk/Utils/extractAxiosErrorData.d.ts +2 -0
  44. package/lib/walletSdk/Utils/index.d.ts +1 -0
  45. package/lib/walletSdk/Utils/toml.d.ts +2 -2
  46. package/lib/walletSdk/Watcher/getTransactions.d.ts +8 -0
  47. package/lib/walletSdk/Watcher/index.d.ts +41 -4
  48. package/lib/walletSdk/index.d.ts +43 -5
  49. package/package.json +17 -4
  50. package/src/index.ts +2 -0
  51. package/src/walletSdk/Anchor/Sep24.ts +93 -86
  52. package/src/walletSdk/Anchor/Sep38.ts +180 -0
  53. package/src/walletSdk/Anchor/Sep6.ts +291 -0
  54. package/src/walletSdk/Anchor/index.ts +138 -5
  55. package/src/walletSdk/Asset/index.ts +21 -4
  56. package/src/walletSdk/Auth/WalletSigner.ts +23 -5
  57. package/src/walletSdk/Auth/index.ts +24 -5
  58. package/src/walletSdk/Customer/index.ts +174 -0
  59. package/src/walletSdk/Exceptions/index.ts +115 -4
  60. package/src/walletSdk/Horizon/AccountService.ts +33 -21
  61. package/src/walletSdk/Horizon/Stellar.ts +89 -5
  62. package/src/walletSdk/Horizon/Transaction/CommonTransactionBuilder.ts +43 -4
  63. package/src/walletSdk/Horizon/Transaction/SponsoringBuilder.ts +30 -7
  64. package/src/walletSdk/Horizon/Transaction/TransactionBuilder.ts +88 -15
  65. package/src/walletSdk/Horizon/index.ts +2 -1
  66. package/src/walletSdk/Recovery/AccountRecover.ts +255 -0
  67. package/src/walletSdk/Recovery/index.ts +314 -13
  68. package/src/walletSdk/Types/anchor.ts +23 -2
  69. package/src/walletSdk/Types/auth.ts +36 -2
  70. package/src/walletSdk/Types/horizon.ts +7 -5
  71. package/src/walletSdk/Types/index.ts +17 -4
  72. package/src/walletSdk/Types/recovery.ts +152 -0
  73. package/src/walletSdk/Types/sep12.ts +61 -0
  74. package/src/walletSdk/Types/sep38.ts +106 -0
  75. package/src/walletSdk/Types/sep6.ts +168 -0
  76. package/src/walletSdk/Types/watcher.ts +8 -2
  77. package/src/walletSdk/Utils/camelToSnakeCase.ts +1 -0
  78. package/src/walletSdk/Utils/extractAxiosErrorData.ts +28 -0
  79. package/src/walletSdk/Utils/index.ts +1 -0
  80. package/src/walletSdk/Utils/toml.ts +2 -2
  81. package/src/walletSdk/Watcher/getTransactions.ts +65 -0
  82. package/src/walletSdk/Watcher/index.ts +70 -9
  83. package/src/walletSdk/index.ts +45 -8
  84. package/test/README.md +18 -0
  85. package/test/accountService.test.ts +21 -3
  86. package/test/customer.test.ts +82 -0
  87. package/test/docker/docker-compose.yml +97 -0
  88. package/test/integration.test.ts +166 -0
  89. package/test/recovery.test.ts +107 -0
  90. package/test/sep38.test.ts +71 -0
  91. package/test/sep6.test.ts +240 -0
  92. package/test/stellar.test.ts +57 -12
  93. package/test/transaction.test.ts +8 -10
  94. package/test/tsconfig.json +10 -0
  95. package/test/utils/index.ts +12 -0
  96. package/test/wallet.test.ts +60 -21
@@ -1,21 +1,30 @@
1
- import StellarSdk, {
2
- TransactionBuilder as StellarTransactionBuilder,
3
- Transaction,
4
- xdr,
5
- } from "stellar-sdk";
6
- import { IssuedAssetId } from "../../Asset";
1
+ import StellarSdk, { xdr } from "stellar-sdk";
7
2
 
8
3
  import { CommonTransactionBuilder } from "./CommonTransactionBuilder";
9
4
  import { AccountKeypair } from "../Account";
10
5
 
6
+ /**
7
+ * Used for building transactions that will include a sponsor.
8
+ * Do not create this object directly, use the TransactionBuilder class to create.
9
+ * @class
10
+ */
11
11
  export class SponsoringBuilder extends CommonTransactionBuilder<SponsoringBuilder> {
12
12
  private sponsorAccount: AccountKeypair;
13
13
 
14
+ /**
15
+ * Creates a new instance of the SponsoringBuilder class.
16
+ * @constructor
17
+ * @param {string} sponsoredAddress - The address of the account being sponsored.
18
+ * @param {AccountKeypair} sponsorAccount - The sponsor account keypair.
19
+ * @param {xdr.Operation[]} operations - An array of Stellar operations.
20
+ * @param {(builder: SponsoringBuilder) => SponsoringBuilder} buildingFunction - Function for creating the
21
+ * operations that will be sponsored.
22
+ */
14
23
  constructor(
15
24
  sponsoredAddress: string,
16
25
  sponsorAccount: AccountKeypair,
17
26
  operations: Array<xdr.Operation>,
18
- buildingFunction: (SponsoringBuilder) => SponsoringBuilder,
27
+ buildingFunction: (builder: SponsoringBuilder) => SponsoringBuilder,
19
28
  ) {
20
29
  super(sponsoredAddress, operations);
21
30
  this.sponsorAccount = sponsorAccount;
@@ -25,6 +34,12 @@ export class SponsoringBuilder extends CommonTransactionBuilder<SponsoringBuilde
25
34
  this.stopSponsoring();
26
35
  }
27
36
 
37
+ /**
38
+ * Creates a Stellar account with sponsored reserves.
39
+ * @param {AccountKeypair} newAccount - The new account's keypair.
40
+ * @param {number} [startingBalance=0] - The starting balance for the new account (default is 0 XLM).
41
+ * @returns {SponsoringBuilder} The SponsoringBuilder instance.
42
+ */
28
43
  createAccount(
29
44
  newAccount: AccountKeypair,
30
45
  startingBalance: number = 0,
@@ -39,6 +54,10 @@ export class SponsoringBuilder extends CommonTransactionBuilder<SponsoringBuilde
39
54
  return this;
40
55
  }
41
56
 
57
+ /**
58
+ * Start sponsoring the future reserves of an account.
59
+ * @returns {void}
60
+ */
42
61
  startSponsoring() {
43
62
  this.operations.push(
44
63
  StellarSdk.Operation.beginSponsoringFutureReserves({
@@ -48,6 +67,10 @@ export class SponsoringBuilder extends CommonTransactionBuilder<SponsoringBuilde
48
67
  );
49
68
  }
50
69
 
70
+ /**
71
+ * Stop sponsoring the future reserves of an account.
72
+ * @returns {void}
73
+ */
51
74
  stopSponsoring() {
52
75
  this.operations.push(
53
76
  StellarSdk.Operation.endSponsoringFutureReserves({
@@ -2,10 +2,9 @@ import StellarSdk, {
2
2
  TransactionBuilder as StellarTransactionBuilder,
3
3
  Account as StellarAccount,
4
4
  Transaction,
5
- Server,
5
+ Horizon,
6
6
  Memo,
7
7
  xdr,
8
- Networks,
9
8
  } from "stellar-sdk";
10
9
 
11
10
  import { Config } from "walletSdk";
@@ -16,7 +15,7 @@ import {
16
15
  WithdrawalTxNotPendingUserTransferStartError,
17
16
  WithdrawalTxMemoError,
18
17
  } from "../../Exceptions";
19
- import { IssuedAssetId, StellarAssetId } from "../../Asset";
18
+ import { StellarAssetId } from "../../Asset";
20
19
  import {
21
20
  WithdrawTransaction,
22
21
  TransactionStatus,
@@ -26,16 +25,30 @@ import { PathPayOnlyOneAmountError } from "../../Exceptions";
26
25
  import { CommonTransactionBuilder } from "./CommonTransactionBuilder";
27
26
  import { SponsoringBuilder } from "./SponsoringBuilder";
28
27
 
28
+ /**
29
+ * Used for building transactions.
30
+ * Do not create this object directly, use the Stellar class to create a transaction.
31
+ * @class
32
+ */
29
33
  export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuilder> {
30
34
  private cfg: Config;
31
35
  private builder: StellarTransactionBuilder;
32
36
 
37
+ /**
38
+ * Creates a new instance of the TransactionBuilder class for constructing Stellar transactions.
39
+ * @constructor
40
+ * @param {Config} cfg - Configuration object for Stellar operations.
41
+ * @param {StellarAccount} sourceAccount - The source account for the transaction.
42
+ * @param {number} [baseFee] - The base fee for the transaction. If not given will use the config base fee.
43
+ * @param {Memo} [memo] - The memo for the transaction.
44
+ * @param {Horizon.Server.Timebounds} [timebounds] - The timebounds for the transaction. If not given will use the config timebounds.
45
+ */
33
46
  constructor(
34
47
  cfg: Config,
35
48
  sourceAccount: StellarAccount,
36
49
  baseFee?: number,
37
50
  memo?: Memo,
38
- timebounds?: Server.Timebounds,
51
+ timebounds?: Horizon.Server.Timebounds,
39
52
  ) {
40
53
  super(sourceAccount.accountId(), []);
41
54
  this.builder = new StellarTransactionBuilder(sourceAccount, {
@@ -49,9 +62,18 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
49
62
  }
50
63
  }
51
64
 
65
+ /**
66
+ * Sponsoring a transaction.
67
+ * @param {AccountKeypair} sponsorAccount - The account doing the sponsoring.
68
+ * @param {(builder: SponsoringBuilder) => SponsoringBuilder} buildingFunction - Function for creating the
69
+ * operations that will be sponsored.
70
+ * @see {@link ./SponsoringBuilder.ts} or {@link ./CommonTransactionBuilder.ts} for operations that can be sponsored.
71
+ * @param {AccountKeypair} [sponsoredAccount] - The account that will be sponsored.
72
+ * @returns {TransactionBuilder} The transaction builder to build the transaction before submitting.
73
+ */
52
74
  sponsoring(
53
75
  sponsorAccount: AccountKeypair,
54
- buildingFunction: (SponsoringBuilder) => SponsoringBuilder,
76
+ buildingFunction: (builder: SponsoringBuilder) => SponsoringBuilder,
55
77
  sponsoredAccount?: AccountKeypair,
56
78
  ): TransactionBuilder {
57
79
  new SponsoringBuilder(
@@ -63,6 +85,13 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
63
85
  return this;
64
86
  }
65
87
 
88
+ /**
89
+ * Creates a Stellar account.
90
+ * @param {AccountKeypair} newAccount - The new account's keypair.
91
+ * @param {number} [startingBalance=1] - The starting balance for the new account (default is 1 XLM).
92
+ * @throws {InsufficientStartingBalanceError} If the starting balance is less than 1.
93
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
94
+ */
66
95
  createAccount(
67
96
  newAccount: AccountKeypair,
68
97
  startingBalance: number = 1,
@@ -81,6 +110,13 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
81
110
  return this;
82
111
  }
83
112
 
113
+ /**
114
+ * Adds a payment operation to transfer an amount of an asset to a destination address.
115
+ * @param {string} destinationAddress - The destination account's public key.
116
+ * @param {StellarAssetId} assetId - The asset to transfer.
117
+ * @param {string} amount - The amount to transfer.
118
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
119
+ */
84
120
  transfer(
85
121
  destinationAddress: string,
86
122
  assetId: StellarAssetId,
@@ -98,19 +134,19 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
98
134
 
99
135
  /**
100
136
  * Creates and adds a path payment operation to the transaction builder.
101
- *
102
- * @param {string} destinationAddress - The destination Stellar address to which the payment is sent.
103
- * @param {StellarAssetId} sendAsset - The asset to be sent.
104
- * @param {StellarAssetId} destAsset - The asset the destination will receive.
105
- * @param {string} [sendAmount] - The amount to be sent. Must specify either sendAmount or destAmount,
137
+ * @param {PathPayParams} params - The path payment parameters.
138
+ * @param {string} params.destinationAddress - The destination Stellar address to which the payment is sent.
139
+ * @param {StellarAssetId} params.sendAsset - The asset to be sent.
140
+ * @param {StellarAssetId} params.destAsset - The asset the destination will receive.
141
+ * @param {string} [params.sendAmount] - The amount to be sent. Must specify either sendAmount or destAmount,
106
142
  * but not both.
107
- * @param {string} [destAmount] - The amount to be received by the destination. Must specify either sendAmount or destAmount,
143
+ * @param {string} [params.destAmount] - The amount to be received by the destination. Must specify either sendAmount or destAmount,
108
144
  * but not both.
109
- * @param {string} [destMin] - The minimum amount of the destination asset to be receive. This is a
145
+ * @param {string} [params.destMin] - The minimum amount of the destination asset to be receive. This is a
110
146
  * protective measure, it allows you to specify a lower bound for an acceptable conversion. Only used
111
147
  * if using sendAmount.
112
148
  * (optional, default is ".0000001").
113
- * @param {string} [sendMax] - The maximum amount of the destination asset to be sent. This is a
149
+ * @param {string} [params.sendMax] - The maximum amount of the destination asset to be sent. This is a
114
150
  * protective measure, it allows you to specify an upper bound for an acceptable conversion. Only used
115
151
  * if using destAmount.
116
152
  * (optional, default is int64 max).
@@ -157,12 +193,10 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
157
193
  /**
158
194
  * Swap assets using the Stellar network. This swaps using the
159
195
  * pathPaymentStrictReceive operation.
160
- *
161
196
  * @param {StellarAssetId} fromAsset - The source asset to be sent.
162
197
  * @param {StellarAssetId} toAsset - The destination asset to receive.
163
198
  * @param {string} amount - The amount of the source asset to be sent.
164
199
  * @param {string} [destMin] - (Optional) The minimum amount of the destination asset to be received.
165
- *
166
200
  * @returns {TransactionBuilder} Returns the current instance of the TransactionBuilder for method chaining.
167
201
  */
168
202
  swap(
@@ -181,16 +215,35 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
181
215
  return this;
182
216
  }
183
217
 
218
+ /**
219
+ * Adds an operation to the transaction.
220
+ * @param {xdr.Operation} op - The operation to add.
221
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
222
+ */
184
223
  addOperation(op: xdr.Operation): TransactionBuilder {
185
224
  this.builder.addOperation(op);
186
225
  return this;
187
226
  }
188
227
 
228
+ /**
229
+ * Add a memo for the transaction.
230
+ * @param {Memo} memo - The memo to add to the transaction.
231
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
232
+ */
189
233
  setMemo(memo: Memo): TransactionBuilder {
190
234
  this.builder.addMemo(memo);
191
235
  return this;
192
236
  }
193
237
 
238
+ /**
239
+ * Add a transfer operation to the builder from a sep-24 withdrawal transaction.
240
+ * @param {WithdrawTransaction} transaction - The withdrawal transaction.
241
+ * @param {StellarAssetId} assetId - The asset ID to transfer.
242
+ * @throws {WithdrawalTxNotPendingUserTransferStartError} If the withdrawal transaction status is not pending_user_transfer_start.
243
+ * @throws {WithdrawalTxMissingMemoError} If the withdrawal transaction is missing a memo.
244
+ * @throws {WithdrawalTxMemoError} If there is an issue with the withdrawal transaction memo.
245
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
246
+ */
194
247
  transferWithdrawalTransaction(
195
248
  transaction: WithdrawTransaction,
196
249
  assetId: StellarAssetId,
@@ -226,6 +279,26 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
226
279
  );
227
280
  }
228
281
 
282
+ /**
283
+ * Merges account into a destination account.
284
+ * **Warning**: This operation will give full control of the account to the destination account,
285
+ * effectively removing the merged account from the network.
286
+ * @param {string} destination - The stellar account merging into.
287
+ * @param {string} [source] - Account that is being merged. If not given then will default to
288
+ * the TransactionBuilder source account.
289
+ * @returns {TransactionBuilder} The TransactionBuilder instance.
290
+ */
291
+ accountMerge(destination: string, source?: string): TransactionBuilder {
292
+ this.operations.push(
293
+ StellarSdk.Operation.accountMerge({ destination, source }),
294
+ );
295
+ return this;
296
+ }
297
+
298
+ /**
299
+ * Builds the Stellar transaction so can be submitted.
300
+ * @returns {Transaction} The built Stellar transaction.
301
+ */
229
302
  build(): Transaction {
230
303
  this.operations.forEach((op) => {
231
304
  this.builder.addOperation(op);
@@ -1,5 +1,6 @@
1
- export { PublicKeypair, SigningKeypair } from "./Account";
1
+ export { AccountKeypair, PublicKeypair, SigningKeypair } from "./Account";
2
2
  export { AccountService } from "./AccountService";
3
3
  export { Stellar } from "./Stellar";
4
+ export { CommonTransactionBuilder } from "./Transaction/CommonTransactionBuilder";
4
5
  export { TransactionBuilder } from "./Transaction/TransactionBuilder";
5
6
  export { SponsoringBuilder } from "./Transaction/SponsoringBuilder";
@@ -0,0 +1,255 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { Horizon, Transaction } from "stellar-sdk";
3
+
4
+ import {
5
+ RecoveryServer,
6
+ RecoveryServerKey,
7
+ RecoveryServerMap,
8
+ RecoveryServerSigning,
9
+ RecoveryServerSigningMap,
10
+ } from "walletSdk/Types";
11
+ import {
12
+ LostSignerKeyNotFound,
13
+ NoDeviceKeyForAccountError,
14
+ NotAllSignaturesFetchedError,
15
+ RecoveryServerNotFoundError,
16
+ ServerRequestFailedError,
17
+ UnableToDeduceKeyError,
18
+ } from "../Exceptions";
19
+ import {
20
+ AccountKeypair,
21
+ PublicKeypair,
22
+ SponsoringBuilder,
23
+ Stellar,
24
+ } from "../Horizon";
25
+
26
+ /**
27
+ * Used for Account Recovery using Sep-30.
28
+ * @see {@link https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md}
29
+ * @class
30
+ */
31
+ export abstract class AccountRecover {
32
+ protected stellar: Stellar;
33
+ protected httpClient: AxiosInstance;
34
+ protected servers: RecoveryServerMap;
35
+
36
+ /**
37
+ * Creates a new instance of the AccountRecover class.
38
+ * @constructor
39
+ * @param {Stellar} stellar - The stellar instance used to interact with Horizon server.
40
+ * @param {AxiosInstance} httpClient - The client used to make http calls.
41
+ * @param {RecoveryServerMap} servers - The recovery servers to use.
42
+ */
43
+ constructor(
44
+ stellar: Stellar,
45
+ httpClient: AxiosInstance,
46
+ servers: RecoveryServerMap,
47
+ ) {
48
+ this.stellar = stellar;
49
+ this.httpClient = httpClient;
50
+ this.servers = servers;
51
+ }
52
+
53
+ /**
54
+ * Sign transaction with recovery servers. It is used to recover an account using
55
+ * [SEP-30](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md).
56
+ * @param {Transaction} transaction - The transaction with new signer to be signed by recovery servers.
57
+ * @param {AccountKeypair} account - The keypair of the account that will be recovered.
58
+ * @param {RecoveryServerSigningMap} serverAuth - The map of recovery servers to use.
59
+ * @returns {Transaction} - The transaction with recovery server signatures
60
+ * @throws {NotAllSignaturesFetchedError} when all recovery servers don't return signatures
61
+ */
62
+ async signWithRecoveryServers(
63
+ transaction: Transaction,
64
+ account: AccountKeypair,
65
+ serverAuth: RecoveryServerSigningMap,
66
+ ): Promise<Transaction> {
67
+ await Promise.all(
68
+ Object.keys(serverAuth).map((serverKey: RecoveryServerKey) =>
69
+ this.addRecoveryServerTxnSignature(transaction, account.publicKey, [
70
+ serverKey,
71
+ serverAuth[serverKey],
72
+ ]),
73
+ ),
74
+ );
75
+ return transaction;
76
+ }
77
+
78
+ /**
79
+ * Replace a lost device key with a new key.
80
+ * @param {AccountKeypair} account - The target account.
81
+ * @param {AccountKeypair} newKey - The key to replace the lost key with.
82
+ * @param {RecoveryServerSigningMap} serverAuth - A map of recovery servers to use.
83
+ * @param {AccountKeypair} [lostKey] - The lost device key. If not specified, try to deduce the key from the account signers list.
84
+ * @param {AccountKeypair} [sponsorAddress] - The sponsor address of the transaction. Please note that not all SEP-30 servers support signing sponsored transactions.
85
+ * @returns {Promise<Transaction>} The transaction with operations for replacing the device key.
86
+ */
87
+ async replaceDeviceKey(
88
+ account: AccountKeypair,
89
+ newKey: AccountKeypair,
90
+ serverAuth: RecoveryServerSigningMap,
91
+ lostKey?: AccountKeypair,
92
+ sponsorAddress?: AccountKeypair,
93
+ ): Promise<Transaction> {
94
+ const stellarAccount = await this.stellar
95
+ .account()
96
+ .getInfo({ accountAddress: account.publicKey });
97
+
98
+ let lost: AccountKeypair;
99
+ let weight: number;
100
+
101
+ if (lostKey) {
102
+ lost = lostKey;
103
+
104
+ const lostSigner = stellarAccount.signers.find(
105
+ ({ key }) => key === lost.publicKey,
106
+ );
107
+ if (!lostSigner) {
108
+ throw new LostSignerKeyNotFound();
109
+ }
110
+
111
+ weight = lostSigner.weight;
112
+ } else {
113
+ const deduced = this.deduceKey(stellarAccount, serverAuth);
114
+ lost = PublicKeypair.fromPublicKey(deduced.key);
115
+ weight = deduced.weight;
116
+ }
117
+
118
+ let transaction: Transaction;
119
+
120
+ const txBuilder = await this.stellar.transaction({
121
+ sourceAddress: account,
122
+ });
123
+
124
+ if (sponsorAddress) {
125
+ const buildingFunction = (builder: SponsoringBuilder) =>
126
+ builder.removeAccountSigner(lost).addAccountSigner(newKey, weight);
127
+
128
+ transaction = txBuilder
129
+ .sponsoring(sponsorAddress, buildingFunction)
130
+ .build();
131
+ } else {
132
+ transaction = txBuilder
133
+ .removeAccountSigner(lost)
134
+ .addAccountSigner(newKey, weight)
135
+ .build();
136
+ }
137
+
138
+ return this.signWithRecoveryServers(transaction, account, serverAuth);
139
+ }
140
+
141
+ protected getServer = (serverKey: RecoveryServerKey): RecoveryServer => {
142
+ const server = this.servers[serverKey];
143
+
144
+ if (!server) {
145
+ throw new RecoveryServerNotFoundError(serverKey);
146
+ }
147
+
148
+ return server;
149
+ };
150
+
151
+ /**
152
+ * Try to deduce the lost key. If any of these criteria match, one of the signers
153
+ * from the account will be recognized as the lost device key:
154
+ * 1. Only signer that's not in [serverAuth]
155
+ * 2. All signers in [serverAuth] have the same weight, and the potential signer is
156
+ * the only one with a different weight.
157
+ * @private
158
+ * @param {Horizon.ServerApi.AccountRecord} stellarAccount - The Stellar account to lookup existing signers on account.
159
+ * @param {RecoveryServerSigningMap} serverAuth - A map of recovery servers to use.
160
+ * @returns {Horizon.ServerApi.AccountRecordSigners} The deduced account signer.
161
+ * @throws {NoDeviceKeyForAccountError} When no existing ("lost") device key is found.
162
+ * @throws {UnableToDeduceKeyError} When no criteria match.
163
+ */
164
+ private deduceKey(
165
+ stellarAccount: Horizon.ServerApi.AccountRecord,
166
+ serverAuth: RecoveryServerSigningMap,
167
+ ): Horizon.ServerApi.AccountRecordSigners {
168
+ // Recovery servers addresses
169
+ const recoveryAddresses = Object.values(serverAuth).map(
170
+ ({ signerAddress }) => signerAddress,
171
+ );
172
+
173
+ // All signers on stellar account
174
+ const accountSigners = stellarAccount.signers;
175
+
176
+ // All signers on stellar account that are not recovery server
177
+ const nonRecoverySigners = accountSigners.filter(
178
+ ({ key, weight }) => !recoveryAddresses.includes(key) && weight > 0,
179
+ );
180
+
181
+ // Throws in case there is no signer other than the recovery signers
182
+ if (nonRecoverySigners.length === 0) {
183
+ throw new NoDeviceKeyForAccountError();
184
+ }
185
+
186
+ // If we have only 1 signer that's not a recovery signer deduce it
187
+ // as the lost key
188
+ if (nonRecoverySigners.length === 1) {
189
+ return nonRecoverySigners[0];
190
+ }
191
+
192
+ // If we have multiple signers that are not recovery signers:
193
+
194
+ // First get all signers on stellar account that are recovery server
195
+ const recoverySigners = accountSigners.filter(({ key }) =>
196
+ recoveryAddresses.includes(key),
197
+ );
198
+
199
+ // Check if all recovery signers have the same weight
200
+ const recoveryWeight = recoverySigners[0]?.weight || 0;
201
+ if (recoverySigners.find(({ weight }) => weight !== recoveryWeight)) {
202
+ throw new UnableToDeduceKeyError();
203
+ }
204
+
205
+ // Then in case we have only one non-recovery signer that has different
206
+ // weight deduce it as the lost key
207
+ const filtered = nonRecoverySigners.filter(
208
+ ({ weight }) => weight !== recoveryWeight,
209
+ );
210
+
211
+ if (filtered.length !== 1) {
212
+ throw new UnableToDeduceKeyError();
213
+ }
214
+
215
+ return filtered[0];
216
+ }
217
+
218
+ private async addRecoveryServerTxnSignature(
219
+ transaction: Transaction,
220
+ accountAddress: string,
221
+ recoveryAuth: [RecoveryServerKey, RecoveryServerSigning],
222
+ ): Promise<void> {
223
+ const [serverKey, auth] = recoveryAuth;
224
+
225
+ const server = this.getServer(serverKey);
226
+
227
+ const requestUrl = `${server.endpoint}/accounts/${accountAddress}/sign/${auth.signerAddress}`;
228
+
229
+ let signature: string;
230
+ try {
231
+ const resp = await this.httpClient.post(
232
+ requestUrl,
233
+ {
234
+ transaction: transaction.toXDR(),
235
+ },
236
+ {
237
+ headers: {
238
+ "Content-Type": "application/json",
239
+ Authorization: `Bearer ${auth.authToken.token}`,
240
+ },
241
+ },
242
+ );
243
+
244
+ signature = resp.data.signature;
245
+
246
+ if (!signature) {
247
+ throw new NotAllSignaturesFetchedError();
248
+ }
249
+ } catch (e) {
250
+ throw new ServerRequestFailedError(e);
251
+ }
252
+
253
+ transaction.addSignature(auth.signerAddress, signature);
254
+ }
255
+ }