@stellar/typescript-wallet-sdk 5.1.0 → 6.0.0-beta.1793996806758

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.
@@ -172,6 +172,9 @@ export declare class Sep7InvalidUriError extends Error {
172
172
  export declare class Sep7LongMsgError extends Error {
173
173
  constructor(msgMaxLength: number);
174
174
  }
175
+ export declare class Sep7ContractMemoError extends Error {
176
+ constructor();
177
+ }
175
178
  export declare class Sep7UriTypeNotSupportedError extends Error {
176
179
  constructor(type: string);
177
180
  }
@@ -20,6 +20,8 @@ export declare class Sep7Pay extends Sep7Base {
20
20
  *
21
21
  * @constructor
22
22
  * @param {URL | string} [uri] - uri to initialize the Sep7 instance.
23
+ * @throws {Sep7ContractMemoError} if 'uri' has a memo and a contract ("C...")
24
+ * or muxed contract ("W...") destination.
23
25
  */
24
26
  constructor(uri?: URL | string);
25
27
  /**
@@ -32,6 +34,10 @@ export declare class Sep7Pay extends Sep7Base {
32
34
  * Gets the destination of the payment request, which should be a valid
33
35
  * Stellar address.
34
36
  *
37
+ * A contract ("C...") or muxed contract ("W...") destination can only be
38
+ * paid with a Stellar Asset Contract transfer, not a classic payment, and a
39
+ * uri with one can't have a memo.
40
+ *
35
41
  * @returns {string | undefined} the 'destination' uri param if present.
36
42
  */
37
43
  get destination(): string | undefined;
@@ -42,6 +48,8 @@ export declare class Sep7Pay extends Sep7Base {
42
48
  * Deletes the uri 'destination' param if set as 'undefined'.
43
49
  *
44
50
  * @param {string | undefined} destination the uri 'destination' param to be set.
51
+ * @throws {Sep7ContractMemoError} if 'destination' is a contract ("C...") or
52
+ * muxed contract ("W...") address and the uri has a memo.
45
53
  */
46
54
  set destination(destination: string | undefined);
47
55
  /**
@@ -102,6 +110,8 @@ export declare class Sep7Pay extends Sep7Base {
102
110
  * Deletes the uri 'memo' param if set as 'undefined'.
103
111
  *
104
112
  * @param {string | undefined} memo the uri 'memo' param to be set.
113
+ * @throws {Sep7ContractMemoError} if 'memo' is set and the uri's destination
114
+ * is a contract ("C...") or muxed contract ("W...") address.
105
115
  */
106
116
  set memo(memo: string | undefined);
107
117
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellar/typescript-wallet-sdk",
3
- "version": "5.1.0",
3
+ "version": "6.0.0-beta.1793996806758",
4
4
  "engines": {
5
5
  "node": ">=22.12.0"
6
6
  },
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "@stablelib/base64": "^2.0.0",
51
51
  "@stablelib/utf8": "^2.0.0",
52
- "@stellar/stellar-sdk": "17.0.1",
52
+ "@stellar/stellar-sdk": "17.4.0",
53
53
  "axios": "^1.4.0",
54
54
  "https-browserify": "^1.0.0",
55
55
  "jws": "^4.0.0",
@@ -77,4 +77,4 @@
77
77
  "example:sep24": "ts-node examples/sep24/sep24.ts",
78
78
  "example:sep12": "ts-node examples/sep12/sep12.ts"
79
79
  }
80
- }
80
+ }
@@ -474,6 +474,15 @@ export class Sep7LongMsgError extends Error {
474
474
  }
475
475
  }
476
476
 
477
+ export class Sep7ContractMemoError extends Error {
478
+ constructor() {
479
+ super(
480
+ "the 'memo' parameter is not supported for a contract ('C') or muxed contract ('W') 'destination'",
481
+ );
482
+ Object.setPrototypeOf(this, Sep7ContractMemoError.prototype);
483
+ }
484
+ }
485
+
477
486
  export class Sep7UriTypeNotSupportedError extends Error {
478
487
  constructor(type: string) {
479
488
  super(
@@ -1,6 +1,31 @@
1
- import { MemoType } from "@stellar/stellar-sdk";
1
+ import { MemoType, StrKey } from "@stellar/stellar-sdk";
2
2
  import { Sep7Base } from "../Uri";
3
3
  import { Sep7OperationType, WEB_STELLAR_SCHEME } from "../Types";
4
+ import { Sep7ContractMemoError } from "../Exceptions";
5
+
6
+ /**
7
+ * Throws if a uri would have a memo with a contract ("C...") or muxed
8
+ * contract ("W...") destination, which 'parseSep7Uri' rejects. Paying either
9
+ * takes a Soroban transaction, which can't have a memo.
10
+ *
11
+ * @param {string | undefined} destination the uri 'destination' param.
12
+ * @param {string | undefined} memo the uri 'memo' param.
13
+ * @throws {Sep7ContractMemoError} if both are set and 'destination' is a
14
+ * contract or muxed contract address.
15
+ */
16
+ const assertNoContractMemo = (
17
+ destination: string | undefined,
18
+ memo: string | undefined,
19
+ ) => {
20
+ if (
21
+ memo &&
22
+ destination &&
23
+ (StrKey.isValidContract(destination) ||
24
+ StrKey.isValidMuxedContract(destination))
25
+ ) {
26
+ throw new Sep7ContractMemoError();
27
+ }
28
+ };
4
29
 
5
30
  /**
6
31
  * The Sep-7 'pay' operation represents a request to pay a specific address
@@ -27,9 +52,13 @@ export class Sep7Pay extends Sep7Base {
27
52
  *
28
53
  * @constructor
29
54
  * @param {URL | string} [uri] - uri to initialize the Sep7 instance.
55
+ * @throws {Sep7ContractMemoError} if 'uri' has a memo and a contract ("C...")
56
+ * or muxed contract ("W...") destination.
30
57
  */
31
58
  constructor(uri?: URL | string) {
32
59
  super(uri ?? new URL(`${WEB_STELLAR_SCHEME}${Sep7OperationType.pay}`));
60
+
61
+ assertNoContractMemo(this.destination, this.memo);
33
62
  }
34
63
 
35
64
  /**
@@ -45,6 +74,10 @@ export class Sep7Pay extends Sep7Base {
45
74
  * Gets the destination of the payment request, which should be a valid
46
75
  * Stellar address.
47
76
  *
77
+ * A contract ("C...") or muxed contract ("W...") destination can only be
78
+ * paid with a Stellar Asset Contract transfer, not a classic payment, and a
79
+ * uri with one can't have a memo.
80
+ *
48
81
  * @returns {string | undefined} the 'destination' uri param if present.
49
82
  */
50
83
  get destination(): string | undefined {
@@ -58,8 +91,12 @@ export class Sep7Pay extends Sep7Base {
58
91
  * Deletes the uri 'destination' param if set as 'undefined'.
59
92
  *
60
93
  * @param {string | undefined} destination the uri 'destination' param to be set.
94
+ * @throws {Sep7ContractMemoError} if 'destination' is a contract ("C...") or
95
+ * muxed contract ("W...") address and the uri has a memo.
61
96
  */
62
97
  set destination(destination: string | undefined) {
98
+ assertNoContractMemo(destination, this.memo);
99
+
63
100
  this.setParam("destination", destination);
64
101
  }
65
102
 
@@ -142,8 +179,12 @@ export class Sep7Pay extends Sep7Base {
142
179
  * Deletes the uri 'memo' param if set as 'undefined'.
143
180
  *
144
181
  * @param {string | undefined} memo the uri 'memo' param to be set.
182
+ * @throws {Sep7ContractMemoError} if 'memo' is set and the uri's destination
183
+ * is a contract ("C...") or muxed contract ("W...") address.
145
184
  */
146
185
  set memo(memo: string | undefined) {
186
+ assertNoContractMemo(this.destination, memo);
187
+
147
188
  this.setParam("memo", memo);
148
189
  }
149
190
 
@@ -78,11 +78,15 @@ export const isValidSep7Uri = (uri: string): IsValidSep7UriResult => {
78
78
  }
79
79
 
80
80
  if (type === Sep7OperationType.pay && destination) {
81
- // Checks if it's a valid "G", "M" or "C" Stellar address
81
+ const isContract =
82
+ StrKey.isValidContract(destination) ||
83
+ StrKey.isValidMuxedContract(destination);
84
+
85
+ // Checks if it's a valid "G", "M", "C" or "W" Stellar address
82
86
  const isValidStellarAddress =
83
87
  StrKey.isValidEd25519PublicKey(destination) ||
84
88
  StrKey.isValidMed25519PublicKey(destination) ||
85
- StrKey.isValidContract(destination);
89
+ isContract;
86
90
 
87
91
  if (!isValidStellarAddress) {
88
92
  return {
@@ -91,6 +95,18 @@ export const isValidSep7Uri = (uri: string): IsValidSep7UriResult => {
91
95
  "the provided 'destination' parameter is not a valid Stellar address",
92
96
  };
93
97
  }
98
+
99
+ // A "C" (contract) or "W" (muxed contract) address can only be paid with
100
+ // a Stellar Asset Contract transfer. That's a Soroban transaction, which
101
+ // can't have a memo, so a wallet could only drop it. A "W" address's mux
102
+ // ID already identifies the payee.
103
+ if (isContract && url.searchParams.get("memo")) {
104
+ return {
105
+ result: false,
106
+ reason:
107
+ "the 'memo' parameter is not supported for a contract ('C') or muxed contract ('W') 'destination'",
108
+ };
109
+ }
94
110
  }
95
111
 
96
112
  if (msg?.length > URI_MSG_MAX_LENGTH) {
@@ -11,7 +11,8 @@ let accountAddress: string;
11
11
 
12
12
  describe("Horizon", () => {
13
13
  // Creates testing stellar account with USDC trustline
14
- // in case it doesn't exist just yet
14
+ // in case it doesn't exist just yet. One friendbot call can take 25 s or
15
+ // more on testnet, so this allows 90 s.
15
16
  beforeAll(async () => {
16
17
  const wallet = Wallet.TestNet();
17
18
  const stellar = wallet.stellar();
@@ -46,7 +47,7 @@ describe("Horizon", () => {
46
47
  await stellar.submitTransaction(addUsdcTx);
47
48
 
48
49
  accountAddress = testingAccountKp.publicKey;
49
- }, 30000);
50
+ }, 90000);
50
51
 
51
52
  it("should return stellar account details", async () => {
52
53
  const response = await accountService.getInfo({ accountAddress });
package/test/auth.test.ts CHANGED
@@ -726,6 +726,57 @@ describe("Sep10 challenge validation", () => {
726
726
  });
727
727
  });
728
728
 
729
+ // WebAuth.readChallengeTx checks these since stellar-sdk 17.3. SEP-10
730
+ // requires the client_domain operation's source to be the client domain's
731
+ // signing key.
732
+ describe("client_domain operation source", () => {
733
+ it.each([
734
+ ["has no source account", "none"],
735
+ ["uses the server account as its source", "server"],
736
+ ])(
737
+ "should reject a challenge whose client_domain operation %s",
738
+ async (_, clientDomainSource) => {
739
+ const serverKeypair = Keypair.random();
740
+ const clientKeypair = Keypair.random();
741
+ const { xdr } = buildChallenge({
742
+ serverKeypair,
743
+ clientKeypair,
744
+ additionalOps: [
745
+ Operation.manageData({
746
+ name: "client_domain",
747
+ value: "wallet.example.com",
748
+ source:
749
+ clientDomainSource === "server"
750
+ ? serverKeypair.publicKey()
751
+ : undefined,
752
+ }),
753
+ ],
754
+ });
755
+ const { sep10, postStub } = setupSep10({
756
+ serverSigningKey: serverKeypair.publicKey(),
757
+ challengeXdr: xdr,
758
+ token: createJwt(clientKeypair),
759
+ });
760
+ const signWithDomainAccount = sinon
761
+ .stub()
762
+ .rejects(new Error("the challenge should be rejected first"));
763
+
764
+ const result = sep10.authenticate({
765
+ accountKp: SigningKeypair.fromSecret(clientKeypair.secret()),
766
+ clientDomain: "wallet.example.com",
767
+ walletSigner: {
768
+ signWithClientAccount: ({ transaction }) => transaction,
769
+ signWithDomainAccount,
770
+ },
771
+ });
772
+ await expect(result).rejects.toThrow(ChallengeValidationFailedError);
773
+ await expect(result).rejects.toThrow(/client_domain/);
774
+ expect(signWithDomainAccount.notCalled).toBe(true);
775
+ expect(postStub.notCalled).toBe(true);
776
+ },
777
+ );
778
+ });
779
+
729
780
  describe("network passphrase mismatch", () => {
730
781
  it("should reject when server returns a different network passphrase", async () => {
731
782
  const { xdr, serverKeypair, clientKeypair } = buildChallenge();
@@ -19,9 +19,22 @@ import * as vm from "vm";
19
19
  */
20
20
  const BANNED = ["Buffer", "Event", "EventTarget", "process"];
21
21
 
22
+ // Behaves like fast-text-encoding 1.0.6, a TextDecoder that React Native apps
23
+ // commonly install on Hermes: its constructor throws for `{ fatal: true }`.
24
+ const NonFatalTextDecoder = function (
25
+ label?: string,
26
+ options?: TextDecoderOptions,
27
+ ) {
28
+ if (options?.fatal) {
29
+ throw new RangeError("the 'fatal' option is unsupported");
30
+ }
31
+ return new TextDecoder(label, options);
32
+ } as unknown as typeof TextDecoder;
33
+
22
34
  const makeHermesLikeContext = ({
23
35
  includeBase64 = true,
24
36
  includeTextCodec = true,
37
+ textDecoder = TextDecoder,
25
38
  } = {}) => {
26
39
  const sandbox: Record<string, unknown> = {
27
40
  console,
@@ -56,7 +69,7 @@ const makeHermesLikeContext = ({
56
69
  // "TypeError: globalThis.TextDecoder is not a constructor" at load —
57
70
  // before any of our own code runs. See the test below that enforces this.
58
71
  sandbox.TextEncoder = TextEncoder;
59
- sandbox.TextDecoder = TextDecoder;
72
+ sandbox.TextDecoder = textDecoder;
60
73
  }
61
74
 
62
75
  if (includeBase64) {
@@ -126,6 +139,25 @@ describe("React Native (Hermes-like) bundle compatibility", () => {
126
139
  });
127
140
  }
128
141
 
142
+ for (const [name, relativePath] of bundles) {
143
+ it(`loads the ${name} bundle with a TextDecoder that can't decode strictly`, () => {
144
+ // stellar-sdk 17.3+ builds a strict (`{ fatal: true }`) TextDecoder only
145
+ // when it decodes something: a stellar.toml or federation response, or
146
+ // an XdrString read with toStringStrict(). Loading a bundle must not
147
+ // need one, or an app whose TextDecoder can't decode strictly would
148
+ // crash on startup instead of only on those calls.
149
+ const bundlePath = path.resolve(__dirname, relativePath);
150
+ const context = makeHermesLikeContext({
151
+ textDecoder: NonFatalTextDecoder,
152
+ });
153
+ const code = fs.readFileSync(bundlePath, "utf8");
154
+
155
+ expect(() =>
156
+ vm.runInContext(code, context, { filename: `${name}.js` }),
157
+ ).not.toThrow();
158
+ });
159
+ }
160
+
129
161
  it("exercises the core SDK public API in the sandbox", () => {
130
162
  const context = makeHermesLikeContext();
131
163
  const code = fs.readFileSync(
@@ -154,6 +186,85 @@ describe("React Native (Hermes-like) bundle compatibility", () => {
154
186
  expect(result.signature).toHaveLength(88);
155
187
  });
156
188
 
189
+ it("verifies a SEP-7 signature only with a TextDecoder that decodes strictly", async () => {
190
+ // Verifying reads the origin domain's stellar.toml, and stellar-sdk 17.3+
191
+ // decodes stellar.toml and federation responses with a strict
192
+ // (`{ fatal: true }`) TextDecoder. With a TextDecoder that can't build
193
+ // one, verifySignature() fails closed and returns false for a valid
194
+ // signature, and Anchor.sep1() rejects.
195
+ const verifyWith = (textDecoder: typeof TextDecoder) => {
196
+ const context = makeHermesLikeContext({ textDecoder });
197
+ // React Native provides these, and the SDK's stellar.toml fetch uses
198
+ // them. The fetch stub serves the toml that the code below writes.
199
+ Object.assign(context, {
200
+ Headers,
201
+ AbortController,
202
+ AbortSignal,
203
+ fetch: () => Promise.resolve(new Response(context.stellarToml)),
204
+ });
205
+ const code = fs.readFileSync(
206
+ path.resolve(__dirname, "../../lib/bundle_browser.js"),
207
+ "utf8",
208
+ );
209
+ vm.runInContext(code, context, { filename: "bundle_browser.js" });
210
+
211
+ return vm.runInContext(
212
+ `(() => {
213
+ const { Wallet, Sep7Pay } = module.exports;
214
+ const kp = Wallet.TestNet().stellar().account().createKeypair();
215
+ globalThis.stellarToml = 'URI_REQUEST_SIGNING_KEY="' + kp.publicKey + '"';
216
+ const uri = Sep7Pay.forDestination(kp.publicKey);
217
+ uri.originDomain = "example.com";
218
+ uri.addSignature(kp.keypair);
219
+ return uri.verifySignature();
220
+ })()`,
221
+ context,
222
+ ) as Promise<boolean>;
223
+ };
224
+
225
+ expect(await verifyWith(TextDecoder)).toBe(true);
226
+ expect(await verifyWith(NonFatalTextDecoder)).toBe(false);
227
+ });
228
+
229
+ it("reads an anchor's stellar.toml only with a TextDecoder that decodes strictly", async () => {
230
+ // Every anchor flow starts from Anchor.sep1(), which reads the anchor's
231
+ // stellar.toml. With a TextDecoder that can't decode strictly, it rejects.
232
+ const sep1With = (textDecoder: typeof TextDecoder) => {
233
+ const context = makeHermesLikeContext({ textDecoder });
234
+ // React Native defines `window` as the global object, and
235
+ // Wallet.anchor() reads `window.URL` through it.
236
+ Object.assign(context, {
237
+ window: context,
238
+ Headers,
239
+ AbortController,
240
+ AbortSignal,
241
+ fetch: () =>
242
+ Promise.resolve(
243
+ new Response(
244
+ 'WEB_AUTH_ENDPOINT="https://example.com/auth"\n[DOCUMENTATION]\nORG_NAME="Example"\n',
245
+ ),
246
+ ),
247
+ });
248
+ const code = fs.readFileSync(
249
+ path.resolve(__dirname, "../../lib/bundle_browser.js"),
250
+ "utf8",
251
+ );
252
+ vm.runInContext(code, context, { filename: "bundle_browser.js" });
253
+
254
+ return vm.runInContext(
255
+ `module.exports.Wallet.TestNet().anchor({ homeDomain: "example.com" }).sep1()`,
256
+ context,
257
+ ) as Promise<{ webAuthEndpoint: string }>;
258
+ };
259
+
260
+ expect((await sep1With(TextDecoder)).webAuthEndpoint).toBe(
261
+ "https://example.com/auth",
262
+ );
263
+ await expect(sep1With(NonFatalTextDecoder)).rejects.toThrow(
264
+ "the 'fatal' option is unsupported",
265
+ );
266
+ });
267
+
157
268
  it("requires btoa/atob for base64 paths, while base32/StrKey keypair creation stays pure JS", () => {
158
269
  const context = makeHermesLikeContext({ includeBase64: false });
159
270
  const code = fs.readFileSync(
@@ -0,0 +1,47 @@
1
+ import { Address, Operation, Transaction } from "@stellar/stellar-sdk";
2
+
3
+ // Protocol 30 (CAP-84) muxed contract addresses: a contract ID plus a uint64
4
+ // mux ID, encoded as a "W..." strkey.
5
+
6
+ // SEP-23's test vector: this contract with mux ID 0.
7
+ export const SEP23_CONTRACT =
8
+ "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA";
9
+ export const SEP23_MUXED_CONTRACT =
10
+ "WA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAAAWWC";
11
+
12
+ // The same contract with the largest mux ID, 2^64 - 1, which a JS number
13
+ // can't hold exactly.
14
+ export const MUXED_CONTRACT_MAX_ID =
15
+ "WA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJV7777777777777GMO";
16
+
17
+ // SEP23_MUXED_CONTRACT with its last character changed, so the checksum
18
+ // doesn't match.
19
+ export const MUXED_CONTRACT_BAD_CHECKSUM =
20
+ "WA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAAAWWA";
21
+
22
+ export const TRANSFER_SOURCE =
23
+ "GDVEU3DD4KOFECV66VIHWEZOYX4ZKR3WV27L464SIIPOU2IUI3JCZA57";
24
+
25
+ // The native XLM Stellar Asset Contract on testnet.
26
+ export const TESTNET_NATIVE_SAC =
27
+ "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC";
28
+
29
+ // An unsigned testnet transaction with one operation: TESTNET_NATIVE_SAC's
30
+ // transfer(TRANSFER_SOURCE, MUXED_CONTRACT_MAX_ID, 12345). Its only Protocol 30
31
+ // part is the `to` argument, an ScAddress of type SC_ADDRESS_TYPE_MUXED_CONTRACT.
32
+ export const TRANSFER_TO_MUXED_CONTRACT_XDR =
33
+ "AAAAAgAAAADqSmxj4pxSCr71UHsTLsX5lUd2rr6+e5JCHuppFEbSLAAAAGQAAAAAAAAAZAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAAB15KLcsJwPM/q9+uf9O9NUEpVqLl5/JtFDqLIQrTRzmEAAAAIdHJhbnNmZXIAAAADAAAAEgAAAAAAAAAA6kpsY+KcUgq+9VB7Ey7F+ZVHdq6+vnuSQh7qaRRG0iwAAAASAAAABf//////////Pww0v5OtDZlx0EzMkPcFURyDiq2XNKSi+w16A/x/6JoAAAAKAAAAAAAAAAAAAAAAAAAwOQAAAAAAAAAAAAAAAA==";
34
+
35
+ // Reads the `to` argument of the transfer in a transaction like
36
+ // TRANSFER_TO_MUXED_CONTRACT_XDR, as a strkey.
37
+ export const transferRecipient = (tx: Transaction): string => {
38
+ const { func } = tx.operations[0] as Operation.InvokeHostFunction;
39
+ if (func.type !== "hostFunctionTypeInvokeContract") {
40
+ throw new Error(`expected a contract invocation, not ${func.type}`);
41
+ }
42
+ const to = func.invokeContract.args[1];
43
+ if (to.type !== "scvAddress") {
44
+ throw new Error(`expected an address, not ${to.type}`);
45
+ }
46
+ return Address.fromScAddress(to.address).toString();
47
+ };
@@ -9,6 +9,7 @@ let accountKp;
9
9
  const anchorUrl = "anchor-sep-server-dev.stellar.org";
10
10
 
11
11
  describe("Anchor Platform Integration Tests", () => {
12
+ // One friendbot call can take 25 s or more on testnet, so this allows 90 s.
12
13
  beforeAll(async () => {
13
14
  // Setup
14
15
  wallet = Wallet.TestNet();
@@ -16,7 +17,7 @@ describe("Anchor Platform Integration Tests", () => {
16
17
  anchor = wallet.anchor({ homeDomain: anchorUrl });
17
18
  accountKp = stellar.account().createKeypair();
18
19
  await stellar.fundTestnetAccount(accountKp.publicKey);
19
- }, 30000);
20
+ }, 90000);
20
21
 
21
22
  it("SEP-10 auth should work", async () => {
22
23
  const auth = await anchor.sep10();
@@ -51,15 +51,15 @@ describe("Recovery Integration Tests", () => {
51
51
  await stellar.server.loadAccount(deviceKp.publicKey);
52
52
  await stellar.server.loadAccount(recoveryKp.publicKey);
53
53
  } catch (e) {
54
- await axios.get(
55
- "https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
56
- );
57
- await axios.get(
58
- "https://friendbot.stellar.org/?addr=" + deviceKp.publicKey,
59
- );
60
- await axios.get(
61
- "https://friendbot.stellar.org/?addr=" + recoveryKp.publicKey,
62
- );
54
+ // One friendbot call can take 25 s or more on testnet, so these run in
55
+ // parallel and the test allows 120 s.
56
+ await Promise.all([
57
+ axios.get("https://friendbot.stellar.org/?addr=" + accountKp.publicKey),
58
+ axios.get("https://friendbot.stellar.org/?addr=" + deviceKp.publicKey),
59
+ axios.get(
60
+ "https://friendbot.stellar.org/?addr=" + recoveryKp.publicKey,
61
+ ),
62
+ ]);
63
63
  }
64
64
 
65
65
  // Create SEP-30 identities
@@ -164,5 +164,5 @@ describe("Recovery Integration Tests", () => {
164
164
  expect(resp.signers.find((obj) => obj.key === newKp.publicKey).weight).toBe(
165
165
  10,
166
166
  );
167
- }, 60000);
167
+ }, 120000);
168
168
  });
package/test/sep6.test.ts CHANGED
@@ -7,6 +7,7 @@ let sep6;
7
7
  let accountKp;
8
8
 
9
9
  describe("SEP-6", () => {
10
+ // One friendbot call can take 25 s or more on testnet, so this allows 90 s.
10
11
  beforeAll(async () => {
11
12
  wallet = Wallet.TestNet();
12
13
  anchor = wallet.anchor({ homeDomain: "testanchor.stellar.org" });
@@ -16,7 +17,7 @@ describe("SEP-6", () => {
16
17
  await axios.get(
17
18
  "https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
18
19
  );
19
- }, 30000);
20
+ }, 90000);
20
21
 
21
22
  it("should get anchor info", async () => {
22
23
  const resp = await sep6.info();