@stellar/typescript-wallet-sdk 1.0.0-alpha.2 → 1.1.0-alpha.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 (34) hide show
  1. package/lib/bundle.js +1541 -80
  2. package/lib/bundle.js.map +1 -1
  3. package/lib/walletSdk/Watcher/Types.d.ts +65 -0
  4. package/lib/walletSdk/Watcher/index.d.ts +54 -0
  5. package/lib/walletSdk/anchor/Types.d.ts +76 -0
  6. package/lib/walletSdk/anchor/index.d.ts +78 -7
  7. package/lib/walletSdk/auth/WalletSigner.d.ts +3 -0
  8. package/lib/walletSdk/auth/index.d.ts +8 -5
  9. package/lib/walletSdk/exception/index.d.ts +9 -0
  10. package/lib/walletSdk/horizon/Account.d.ts +20 -0
  11. package/lib/walletSdk/horizon/AccountService.d.ts +8 -0
  12. package/lib/walletSdk/horizon/Stellar.d.ts +5 -1
  13. package/lib/walletSdk/index.d.ts +12 -13
  14. package/lib/walletSdk/interactive/index.d.ts +16 -5
  15. package/lib/walletSdk/util/camelToSnakeCase.d.ts +2 -0
  16. package/lib/walletSdk/util/sleep.d.ts +1 -0
  17. package/package.json +12 -6
  18. package/src/walletSdk/Anchor/Types.ts +79 -0
  19. package/src/walletSdk/Anchor/index.ts +224 -24
  20. package/src/walletSdk/Auth/WalletSigner.ts +18 -3
  21. package/src/walletSdk/Auth/index.ts +41 -15
  22. package/src/walletSdk/Watcher/Types.ts +81 -0
  23. package/src/walletSdk/Watcher/index.ts +355 -0
  24. package/src/walletSdk/exception/index.ts +23 -2
  25. package/src/walletSdk/horizon/Account.ts +52 -0
  26. package/src/walletSdk/horizon/AccountService.ts +19 -0
  27. package/src/walletSdk/horizon/Stellar.ts +10 -2
  28. package/src/walletSdk/index.ts +41 -28
  29. package/src/walletSdk/interactive/index.ts +32 -43
  30. package/src/walletSdk/util/camelToSnakeCase.ts +13 -0
  31. package/test/account.test.ts +36 -0
  32. package/test/fixtures/TransactionsResponse.ts +230 -0
  33. package/test/wallet.test.ts +1722 -0
  34. package/test/index.test.ts +0 -73
@@ -1,57 +1,257 @@
1
+ import { AxiosInstance } from "axios";
2
+ import queryString from "query-string";
1
3
  import { StellarTomlResolver } from "stellar-sdk";
2
- import axios from "axios";
3
4
 
4
5
  import { Auth } from "../Auth";
5
6
  import { Interactive } from "../interactive";
6
7
  import { TomlInfo, parseToml } from "../toml";
7
- import { ServerRequestFailedError } from "../exception";
8
+ import { Watcher } from "../Watcher";
9
+ import {
10
+ MissingTransactionIdError,
11
+ ServerRequestFailedError,
12
+ InvalidTransactionResponseError,
13
+ InvalidTransactionsResponseError,
14
+ AssetNotSupportedError,
15
+ } from "../exception";
16
+ import { camelToSnakeCaseObject } from "../util/camelToSnakeCase";
17
+ import { Config } from "walletSdk";
18
+ import { TransactionStatus } from "../Watcher/Types";
19
+ import { AnchorTransaction, AnchorServiceInfo } from "./Types";
20
+
21
+ type GetTransactionsParams = {
22
+ authToken: string;
23
+ assetCode: string;
24
+ noOlderThan?: string;
25
+ limit?: number;
26
+ kind?: string;
27
+ pagingId?: string;
28
+ lang?: string;
29
+ };
8
30
 
9
31
  // Do not create this object directly, use the Wallet class.
10
32
  export class Anchor {
11
- private homeDomain = "";
12
- private httpClient = null;
13
- private cfg;
33
+ public language: string;
34
+
35
+ private cfg: Config;
36
+ private homeDomain: string;
37
+ private httpClient: AxiosInstance;
38
+ private toml: TomlInfo;
14
39
 
15
- constructor(cfg, homeDomain: string, httpClient) {
40
+ constructor({
41
+ cfg,
42
+ homeDomain,
43
+ httpClient,
44
+ language,
45
+ }: {
46
+ cfg: Config;
47
+ homeDomain: string;
48
+ httpClient: AxiosInstance;
49
+ language: string;
50
+ }) {
51
+ this.cfg = cfg;
16
52
  this.homeDomain = homeDomain;
17
53
  this.httpClient = httpClient;
18
- this.cfg = cfg;
54
+ this.language = language;
19
55
  }
20
56
 
21
- async getInfo(): Promise<TomlInfo> {
22
- const toml = await StellarTomlResolver.resolve(this.homeDomain);
23
- return parseToml(toml);
57
+ async getInfo(shouldRefresh?: boolean): Promise<TomlInfo> {
58
+ // return cached TOML values by default
59
+ if (this.toml && !shouldRefresh) {
60
+ return this.toml;
61
+ }
62
+
63
+ // fetch fresh TOML values from Anchor domain
64
+ const stellarToml = await StellarTomlResolver.resolve(this.homeDomain);
65
+ const parsedToml = parseToml(stellarToml);
66
+ this.toml = parsedToml;
67
+ return parsedToml;
24
68
  }
25
69
 
26
- async auth() {
70
+ async auth(): Promise<Auth> {
27
71
  const tomlInfo = await this.getInfo();
28
- return new Auth(tomlInfo.webAuthEndpoint);
72
+ return new Auth(this.cfg, tomlInfo.webAuthEndpoint, this.httpClient);
29
73
  }
30
74
 
31
- interactive() {
32
- return new Interactive(this.homeDomain, this);
75
+ interactive(): Interactive {
76
+ return new Interactive(this.homeDomain, this, this.httpClient);
33
77
  }
34
78
 
35
- async getServicesInfo() {
79
+ watcher(): Watcher {
80
+ return new Watcher(this);
81
+ }
82
+
83
+ async getServicesInfo(
84
+ lang: string = this.language
85
+ ): Promise<AnchorServiceInfo> {
36
86
  const toml = await this.getInfo();
37
87
  const transferServerEndpoint = toml.transferServerSep24;
38
88
 
39
89
  try {
40
- // TODO - use httpClient
41
- const resp = await axios.get(`${transferServerEndpoint}/info`, {
42
- headers: {
43
- "Content-Type": "application/json",
44
- },
45
- });
90
+ const resp = await this.httpClient.get(
91
+ `${transferServerEndpoint}/info?lang=${lang}`,
92
+ {
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ },
96
+ }
97
+ );
46
98
  return resp.data;
47
99
  } catch (e) {
48
100
  throw new ServerRequestFailedError(e);
49
101
  }
50
102
  }
51
103
 
52
- getTransaction() {}
104
+ /**
105
+ * Get single transaction's current status and details. One of the [id], [stellarTransactionId],
106
+ * [externalTransactionId] must be provided.
107
+ *
108
+ * @param authToken auth token of the account authenticated with the anchor
109
+ * @param id transaction ID
110
+ * @param stellarTransactionId stellar transaction ID
111
+ * @param externalTransactionId external transaction ID
112
+ * @return transaction object
113
+ * @throws [MissingTransactionIdError] if none of the id params is provided
114
+ * @throws [InvalidTransactionResponseError] if Anchor returns an invalid transaction
115
+ * @throws [ServerRequestFailedError] if server request fails
116
+ */
117
+ async getTransactionBy({
118
+ authToken,
119
+ id,
120
+ stellarTransactionId,
121
+ externalTransactionId,
122
+ lang = this.language,
123
+ }: {
124
+ authToken: string;
125
+ id?: string;
126
+ stellarTransactionId?: string;
127
+ externalTransactionId?: string;
128
+ lang?: string;
129
+ }): Promise<AnchorTransaction> {
130
+ if (!id && !stellarTransactionId && !externalTransactionId) {
131
+ throw new MissingTransactionIdError();
132
+ }
133
+
134
+ const toml = await this.getInfo();
135
+ const transferServerEndpoint = toml.transferServerSep24;
136
+
137
+ let qs: { [name: string]: string } = {};
138
+
139
+ if (id) {
140
+ qs = { id };
141
+ } else if (stellarTransactionId) {
142
+ qs = { stellar_transaction_id: stellarTransactionId };
143
+ } else if (externalTransactionId) {
144
+ qs = { external_transaction_id: externalTransactionId };
145
+ }
146
+
147
+ qs = { lang, ...qs };
148
+
149
+ try {
150
+ const resp = await this.httpClient.get(
151
+ `${transferServerEndpoint}/transaction?${queryString.stringify(qs)}`,
152
+ {
153
+ headers: {
154
+ Authorization: `Bearer ${authToken}`,
155
+ },
156
+ }
157
+ );
158
+
159
+ const transaction = resp.data?.transaction;
160
+
161
+ if (!transaction || Object.keys(transaction).length === 0) {
162
+ throw new InvalidTransactionResponseError(transaction);
163
+ }
164
+
165
+ return transaction;
166
+ } catch (e) {
167
+ throw new ServerRequestFailedError(e);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Get account's transactions specified by asset and other params.
173
+ *
174
+ * @param authToken auth token of the account authenticated with the anchor
175
+ * @param assetCode target asset to query for
176
+ * @param noOlderThan response should contain transactions starting on or after this date & time
177
+ * @param limit response should contain at most 'limit' transactions
178
+ * @param kind kind of transaction that is desired. E.g.: 'deposit', 'withdrawal'
179
+ * @param pagingId response should contain transactions starting prior to this ID (exclusive)
180
+ * @param lang desired language (localization), it can also accept locale in the format 'en-US'
181
+ * @return list of transactions as requested by the client, sorted in time-descending order
182
+ * @throws [InvalidTransactionsResponseError] if Anchor returns an invalid response
183
+ * @throws [ServerRequestFailedError] if server request fails
184
+ */
185
+ async getTransactionsForAsset(
186
+ params: GetTransactionsParams
187
+ ): Promise<AnchorTransaction[]> {
188
+ const { authToken, lang = this.language, ...otherParams } = params;
189
+
190
+ const toml = await this.getInfo();
191
+ const transferServerEndpoint = toml.transferServerSep24;
192
+
193
+ // Let's convert all params to snake case for the API call
194
+ const apiParams = camelToSnakeCaseObject({ lang, ...otherParams });
195
+
196
+ try {
197
+ const resp = await this.httpClient.get(
198
+ `${transferServerEndpoint}/transactions?${queryString.stringify(
199
+ apiParams
200
+ )}`,
201
+ {
202
+ headers: {
203
+ Authorization: `Bearer ${authToken}`,
204
+ },
205
+ }
206
+ );
207
+
208
+ const transactions = resp.data?.transactions;
53
209
 
54
- getTransactionForAsset() {}
210
+ if (!transactions || !Array.isArray(transactions)) {
211
+ throw new InvalidTransactionsResponseError(transactions);
212
+ }
55
213
 
56
- getHistory() {}
214
+ return transactions;
215
+ } catch (e) {
216
+ throw new ServerRequestFailedError(e);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Get all successfully finished (either completed or refunded) account transactions for specified
222
+ * asset. Optional field implementation depends on anchor.
223
+ *
224
+ * @param authToken auth token of the account authenticated with the anchor
225
+ * @param assetCode target asset to query for
226
+ * @param noOlderThan response should contain transactions starting on or after this date & time
227
+ * @param limit response should contain at most 'limit' transactions
228
+ * @param kind kind of transaction that is desired. E.g.: 'deposit', 'withdrawal'
229
+ * @param pagingId response should contain transactions starting prior to this ID (exclusive)
230
+ * @param lang desired language (localization), it can also accept locale in the format 'en-US'
231
+ * @return list of filtered transactions that achieved a final state (completed or refunded)
232
+ * @throws [AssetNotSupportedError] if asset is not supported by the anchor
233
+ * @throws [InvalidTransactionsResponseError] if Anchor returns an invalid response
234
+ * @throws [ServerRequestFailedError] if server request fails
235
+ */
236
+
237
+ async getHistory(
238
+ params: GetTransactionsParams
239
+ ): Promise<AnchorTransaction[]> {
240
+ const { assetCode } = params;
241
+
242
+ const toml = await this.getInfo();
243
+ if (!toml.currencies?.find(({ code }) => code === assetCode)) {
244
+ throw new AssetNotSupportedError(null, assetCode);
245
+ }
246
+
247
+ const transactions = await this.getTransactionsForAsset(params);
248
+
249
+ const finishedTransactions = transactions
250
+ .filter(({ status }) => [
251
+ TransactionStatus.completed,
252
+ TransactionStatus.refunded
253
+ ].includes(status));
254
+
255
+ return finishedTransactions;
256
+ }
57
257
  }
@@ -1,4 +1,19 @@
1
- // TODO - https://stellarorg.atlassian.net/browse/WAL-813?atlOrigin=eyJpIjoiODBkZTQ1MDMzYTJmNDFjOWI0NDM3MTQ2YWYxNTEzNTgiLCJwIjoiaiJ9
1
+ import StellarSdk, { Keypair, Transaction } from "stellar-sdk";
2
2
 
3
- export interface WalletSigner {}
4
- export const DefaultSigner: WalletSigner = {};
3
+ export interface WalletSigner {
4
+ signWithClientAccount(txn: Transaction, account: Keypair): Transaction;
5
+ signWithDomainAccount(
6
+ transactionXDR: string,
7
+ networkPassPhrase: string,
8
+ account: Keypair
9
+ ): Transaction;
10
+ }
11
+ export const DefaultSigner: WalletSigner = {
12
+ signWithClientAccount: (txn, account) => {
13
+ txn.sign(account);
14
+ return txn;
15
+ },
16
+ signWithDomainAccount: (transactionXDR, networkPassPhrase, account) => {
17
+ throw new Error("The DefaultSigner can't sign transactions with domain");
18
+ },
19
+ };
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import StellarSdk, { Keypair } from "stellar-sdk";
3
2
 
4
3
  import {
@@ -6,18 +5,23 @@ import {
6
5
  ClientDomainWithMemoError,
7
6
  ServerRequestFailedError,
8
7
  } from "../exception";
8
+ import { WalletSigner } from "./WalletSigner";
9
9
 
10
10
  // Do not create this object directly, use the Wallet class.
11
11
  export class Auth {
12
+ private cfg;
12
13
  private webAuthEndpoint = "";
14
+ private httpClient;
13
15
 
14
- // TODO - add config and custom httpClient functionality
15
- constructor(webAuthEndpoint) {
16
+ constructor(cfg, webAuthEndpoint, httpClient) {
17
+ this.cfg = cfg;
16
18
  this.webAuthEndpoint = webAuthEndpoint;
19
+ this.httpClient = httpClient;
17
20
  }
18
21
 
19
22
  async authenticate(
20
23
  accountKp: Keypair,
24
+ walletSigner?: WalletSigner,
21
25
  memoId?: string,
22
26
  clientDomain?: string
23
27
  ) {
@@ -28,13 +32,17 @@ export class Auth {
28
32
  );
29
33
  const signedTx = this.sign(
30
34
  accountKp,
31
- challengeResponse.transaction,
32
- challengeResponse.network_passphrase
35
+ challengeResponse,
36
+ walletSigner ?? this.cfg.app.defaultSigner
33
37
  );
34
38
  return await this.getToken(signedTx);
35
39
  }
36
40
 
37
- async challenge(accountKp: Keypair, memoId?: string, clientDomain?: string) {
41
+ private async challenge(
42
+ accountKp: Keypair,
43
+ memoId?: string,
44
+ clientDomain?: string
45
+ ) {
38
46
  if (memoId && parseInt(memoId) < 0) {
39
47
  throw new InvalidMemoError();
40
48
  }
@@ -45,26 +53,44 @@ export class Auth {
45
53
  memoId ? `&memo=${memoId}` : ""
46
54
  }${clientDomain ? `&client_domain=${clientDomain}` : ""}`;
47
55
  try {
48
- const auth = await axios.get(url);
56
+ const auth = await this.httpClient.get(url);
49
57
  return auth.data;
50
58
  } catch (e) {
51
59
  throw new ServerRequestFailedError(e);
52
60
  }
53
61
  }
54
62
 
55
- // TODO - add signing with client account functionality
56
- sign(accountKp: Keypair, challengeTx, network) {
57
- const transaction = StellarSdk.TransactionBuilder.fromXDR(
58
- challengeTx,
59
- network
63
+ private sign(
64
+ accountKp: Keypair,
65
+ challengeResponse: {
66
+ transaction: string;
67
+ network_passphrase: string;
68
+ },
69
+ walletSigner: WalletSigner
70
+ ) {
71
+ let transaction = StellarSdk.TransactionBuilder.fromXDR(
72
+ challengeResponse.transaction,
73
+ challengeResponse.network_passphrase
60
74
  );
61
- transaction.sign(accountKp);
75
+
76
+ // check if verifying client domain as well
77
+ for (const op of transaction.operations) {
78
+ if (op.type === "manageData" && op.name === "client_domain") {
79
+ transaction = walletSigner.signWithDomainAccount(
80
+ challengeResponse.transaction,
81
+ challengeResponse.network_passphrase,
82
+ accountKp
83
+ );
84
+ }
85
+ }
86
+
87
+ walletSigner.signWithClientAccount(transaction, accountKp);
62
88
  return transaction;
63
89
  }
64
90
 
65
- async getToken(signedTx) {
91
+ private async getToken(signedTx) {
66
92
  try {
67
- const resp = await axios.post(this.webAuthEndpoint, {
93
+ const resp = await this.httpClient.post(this.webAuthEndpoint, {
68
94
  transaction: signedTx.toXDR(),
69
95
  });
70
96
  return resp.data.token;
@@ -0,0 +1,81 @@
1
+ export enum TransactionStatus {
2
+ /**
3
+ * There is not yet enough information for this transaction to be initiated. Perhaps the user has
4
+ * not yet entered necessary info in an interactive flow
5
+ */
6
+ incomplete = "incomplete",
7
+
8
+ /**
9
+ * The user has not yet initiated their transfer to the anchor. This is the next necessary step in
10
+ * any deposit or withdrawal flow after transitioning from `incomplete`
11
+ */
12
+ pending_user_transfer_start = "pending_user_transfer_start",
13
+
14
+ /**
15
+ * The Stellar payment has been successfully received by the anchor and the off-chain funds are
16
+ * available for the customer to pick up. Only used for withdrawal transactions.
17
+ */
18
+ pending_user_transfer_complete = "pending_user_transfer_complete",
19
+
20
+ /**
21
+ * Pending External deposit/withdrawal has been submitted to external network, but is not yet
22
+ * confirmed. This is the status when waiting on Bitcoin or other external crypto network to
23
+ * complete a transaction, or when waiting on a bank transfer.
24
+ */
25
+ pending_external = "pending_external",
26
+
27
+ /**
28
+ * Deposit/withdrawal is being processed internally by anchor. This can also be used when the
29
+ * anchor must verify KYC information prior to deposit/withdrawal.
30
+ */
31
+ pending_anchor = "pending_anchor",
32
+
33
+ /**
34
+ * Deposit/withdrawal operation has been submitted to Stellar network, but is not yet confirmed.
35
+ */
36
+ pending_stellar = "pending_stellar",
37
+
38
+ /** The user must add a trustline for the asset for the deposit to complete. */
39
+ pending_trust = "pending_trust",
40
+
41
+ /**
42
+ * The user must take additional action before the deposit / withdrawal can complete, for example
43
+ * an email or 2fa confirmation of a withdrawal.
44
+ */
45
+ pending_user = "pending_user",
46
+
47
+ /** Deposit/withdrawal fully completed */
48
+ completed = "completed",
49
+
50
+ /** The deposit/withdrawal is fully refunded */
51
+ refunded = "refunded",
52
+
53
+ /**
54
+ * Funds were never received by the anchor and the transaction is considered abandoned by the
55
+ * user. Anchors are responsible for determining when transactions are considered expired.
56
+ */
57
+ expired = "expired",
58
+
59
+ /**
60
+ * Could not complete deposit because no satisfactory asset/XLM market was available to create the
61
+ * account
62
+ */
63
+ no_market = "no_market",
64
+
65
+ /** Deposit/withdrawal size less than min_amount. */
66
+ too_small = "too_small",
67
+
68
+ /** Deposit/withdrawal size exceeded max_amount. */
69
+ too_large = "too_large",
70
+
71
+ /** Catch-all for any error not enumerated above. */
72
+ error = "error",
73
+ }
74
+
75
+ export type WatcherRefreshFunction = () => void;
76
+ export type WatcherStopFunction = () => void;
77
+
78
+ export interface WatcherResponse {
79
+ refresh: WatcherRefreshFunction;
80
+ stop: WatcherStopFunction;
81
+ }