@stellar/typescript-wallet-sdk 3.0.2 → 3.0.3-beta.1786398758713
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.
- package/CHANGELOG.MD +8 -0
- package/jest.e2e.config.js +11 -1
- package/jest.integration.config.js +11 -1
- package/lib/bundle.js +42724 -70958
- package/lib/bundle.js.map +1 -1
- package/lib/bundle_browser.js +33687 -438
- package/lib/bundle_browser.js.map +1 -1
- package/lib/walletSdk/Horizon/Transaction/TransactionBuilder.d.ts +3 -3
- package/lib/walletSdk/Types/sep7.d.ts +18 -0
- package/package.json +5 -4
- package/src/shims/eventsource.js +45 -0
- package/src/walletSdk/Horizon/AccountService.ts +11 -4
- package/src/walletSdk/Horizon/Stellar.ts +2 -2
- package/src/walletSdk/Horizon/Transaction/TransactionBuilder.ts +16 -13
- package/src/walletSdk/Types/sep7.ts +22 -0
- package/src/walletSdk/Uri/Sep7Base.ts +14 -1
- package/src/walletSdk/Uri/sep7Parser.ts +14 -0
- package/test/accountService.test.ts +58 -2
- package/test/customer.test.ts +2 -2
- package/test/recovery.test.ts +1 -1
- package/test/sep38.test.ts +1 -1
- package/test/sep6.test.ts +2 -2
- package/test/sep7.test.ts +139 -2
- package/test/stellar.test.ts +81 -16
- package/test/toml.test.ts +102 -0
- package/test/transaction.test.ts +8 -8
- package/test/wallet.test.ts +1 -1
- package/tsconfig.json +1 -0
- package/webpack.config.js +11 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TransactionSource, Transaction, Horizon, Memo, xdr } from "@stellar/stellar-sdk";
|
|
2
2
|
import { Config } from "../../";
|
|
3
3
|
import { AccountKeypair } from "../Account";
|
|
4
4
|
import { StellarAssetId } from "../../Asset";
|
|
@@ -17,12 +17,12 @@ export declare class TransactionBuilder extends CommonTransactionBuilder<Transac
|
|
|
17
17
|
* Creates a new instance of the TransactionBuilder class for constructing Stellar transactions.
|
|
18
18
|
* @constructor
|
|
19
19
|
* @param {Config} cfg - Configuration object for Stellar operations.
|
|
20
|
-
* @param {
|
|
20
|
+
* @param {TransactionSource} sourceAccount - The source account for the transaction.
|
|
21
21
|
* @param {number} [baseFee] - The base fee for the transaction. If not given will use the config base fee.
|
|
22
22
|
* @param {Memo} [memo] - The memo for the transaction.
|
|
23
23
|
* @param {Horizon.Server.Timebounds} [timebounds] - The timebounds for the transaction. If not given will use the config timebounds.
|
|
24
24
|
*/
|
|
25
|
-
constructor(cfg: Config, sourceAccount:
|
|
25
|
+
constructor(cfg: Config, sourceAccount: TransactionSource, baseFee?: number, memo?: Memo, timebounds?: Horizon.Server.Timebounds);
|
|
26
26
|
/**
|
|
27
27
|
* Sponsoring a transaction.
|
|
28
28
|
* @param {AccountKeypair} sponsorAccount - The account doing the sponsoring.
|
|
@@ -14,3 +14,21 @@ export type IsValidSep7UriResult = {
|
|
|
14
14
|
result: boolean;
|
|
15
15
|
reason?: string;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Matches a fully qualified domain name (e.g. "example.com", "sub.example.co.uk").
|
|
19
|
+
*
|
|
20
|
+
* Rejects userinfo ('@'), ports (':'), paths/queries ('/', '?'), scheme
|
|
21
|
+
* prefixes ('//'), bare hostnames like 'localhost', and raw IPv4/IPv6
|
|
22
|
+
* addresses (the all-numeric final label fails the alphabetic TLD rule).
|
|
23
|
+
*
|
|
24
|
+
* @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0007.md#verifying-the-signature step 3.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FULLY_QUALIFIED_DOMAIN_NAME_REGEX: RegExp;
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the given string is a valid fully qualified domain name.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} domain the domain string to validate.
|
|
31
|
+
*
|
|
32
|
+
* @returns {boolean} true if 'domain' is a valid fully qualified domain name.
|
|
33
|
+
*/
|
|
34
|
+
export declare const isValidFullyQualifiedDomainName: (domain: string) => boolean;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stellar/typescript-wallet-sdk",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3-beta.1786398758713",
|
|
4
4
|
"engines": {
|
|
5
|
-
"node": ">=
|
|
5
|
+
"node": ">=22"
|
|
6
6
|
},
|
|
7
7
|
"browser": "./lib/bundle_browser.js",
|
|
8
8
|
"main": "./lib/bundle.js",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"pretty-quick": "^2.0.1",
|
|
36
36
|
"process": "^0.11.10",
|
|
37
37
|
"sinon": "^15.1.0",
|
|
38
|
+
"smol-toml": "^1.7.1",
|
|
38
39
|
"stream-browserify": "^3.0.0",
|
|
39
40
|
"ts-jest": "^29.0.5",
|
|
40
41
|
"ts-loader": "^9.4.2",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@stablelib/base64": "^2.0.0",
|
|
49
50
|
"@stablelib/utf8": "^2.0.0",
|
|
50
|
-
"@stellar/stellar-sdk": "
|
|
51
|
+
"@stellar/stellar-sdk": "16.2.0",
|
|
51
52
|
"axios": "^1.4.0",
|
|
52
53
|
"base64url": "^3.0.1",
|
|
53
54
|
"https-browserify": "^1.0.0",
|
|
@@ -75,4 +76,4 @@
|
|
|
75
76
|
"example:sep24": "ts-node examples/sep24/sep24.ts",
|
|
76
77
|
"example:sep12": "ts-node examples/sep12/sep12.ts"
|
|
77
78
|
}
|
|
78
|
-
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy `eventsource` facade.
|
|
3
|
+
*
|
|
4
|
+
* eventsource v4 subclasses the DOM globals `Event`/`EventTarget` at module
|
|
5
|
+
* scope, which React Native's Hermes does not provide. Horizon's CallBuilder
|
|
6
|
+
* requires this module eagerly but only reads it inside `stream()`, so deferring
|
|
7
|
+
* evaluation to first property access keeps imports working everywhere while
|
|
8
|
+
* leaving streaming intact wherever those globals exist.
|
|
9
|
+
*
|
|
10
|
+
* Aliased in webpack.config.js: `eventsource$` resolves here, `eventsource-real$`
|
|
11
|
+
* to the real package.
|
|
12
|
+
*/
|
|
13
|
+
let realModule;
|
|
14
|
+
|
|
15
|
+
const load = () => {
|
|
16
|
+
if (!realModule) {
|
|
17
|
+
try {
|
|
18
|
+
// eslint-disable-next-line global-require
|
|
19
|
+
realModule = require("eventsource-real");
|
|
20
|
+
} catch (cause) {
|
|
21
|
+
// CallBuilder.stream() catches this and retries on a timer, so make the
|
|
22
|
+
// error it reports to onerror self-explanatory.
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Horizon streaming needs the DOM Event/EventTarget globals and a " +
|
|
25
|
+
"streaming fetch body, which React Native does not provide. Alias " +
|
|
26
|
+
'"eventsource" to a native SSE client (e.g. react-native-sse) to use ' +
|
|
27
|
+
`CallBuilder.stream(). Original error: ${cause.message}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return realModule;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
Object.defineProperty(exports, "EventSource", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
get: () => load().EventSource,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(exports, "ErrorEvent", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
get: () => load().ErrorEvent,
|
|
45
|
+
});
|
|
@@ -96,13 +96,20 @@ export class AccountService {
|
|
|
96
96
|
throw new OperationsLimitExceededError(HORIZON_LIMIT_MAX);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
let builder = this.server
|
|
100
100
|
.operations()
|
|
101
101
|
.forAccount(accountAddress)
|
|
102
102
|
.limit(limit)
|
|
103
103
|
.order(order)
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
.includeFailed(includeFailed);
|
|
105
|
+
|
|
106
|
+
// Only set the cursor when provided. In stellar-sdk v16 the call builder
|
|
107
|
+
// serializes query params via URLSearchParams, so passing an undefined
|
|
108
|
+
// cursor would send the literal "cursor=undefined" and Horizon returns 400.
|
|
109
|
+
if (cursor) {
|
|
110
|
+
builder = builder.cursor(cursor);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return builder.call();
|
|
107
114
|
}
|
|
108
115
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
TransactionSource,
|
|
3
3
|
Horizon,
|
|
4
4
|
Transaction,
|
|
5
5
|
TransactionBuilder as StellarTransactionBuilder,
|
|
@@ -75,7 +75,7 @@ export class Stellar {
|
|
|
75
75
|
memo,
|
|
76
76
|
timebounds,
|
|
77
77
|
}: TransactionParams): Promise<TransactionBuilder> {
|
|
78
|
-
let sourceAccount:
|
|
78
|
+
let sourceAccount: TransactionSource;
|
|
79
79
|
try {
|
|
80
80
|
sourceAccount = await this.cfg.stellar.server.loadAccount(
|
|
81
81
|
sourceAddress.publicKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Operation,
|
|
3
3
|
TransactionBuilder as StellarTransactionBuilder,
|
|
4
|
-
|
|
4
|
+
TransactionSource,
|
|
5
5
|
Transaction,
|
|
6
6
|
Horizon,
|
|
7
7
|
Memo,
|
|
@@ -40,14 +40,14 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
|
|
|
40
40
|
* Creates a new instance of the TransactionBuilder class for constructing Stellar transactions.
|
|
41
41
|
* @constructor
|
|
42
42
|
* @param {Config} cfg - Configuration object for Stellar operations.
|
|
43
|
-
* @param {
|
|
43
|
+
* @param {TransactionSource} sourceAccount - The source account for the transaction.
|
|
44
44
|
* @param {number} [baseFee] - The base fee for the transaction. If not given will use the config base fee.
|
|
45
45
|
* @param {Memo} [memo] - The memo for the transaction.
|
|
46
46
|
* @param {Horizon.Server.Timebounds} [timebounds] - The timebounds for the transaction. If not given will use the config timebounds.
|
|
47
47
|
*/
|
|
48
48
|
constructor(
|
|
49
49
|
cfg: Config,
|
|
50
|
-
sourceAccount:
|
|
50
|
+
sourceAccount: TransactionSource,
|
|
51
51
|
baseFee?: number,
|
|
52
52
|
memo?: Memo,
|
|
53
53
|
timebounds?: Horizon.Server.Timebounds,
|
|
@@ -266,18 +266,21 @@ export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuil
|
|
|
266
266
|
throw new WithdrawalTxMissingMemoError();
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
// The memo type/value come from the anchor (untrusted input). stellar-sdk
|
|
270
|
+
// validates them when the Memo is constructed and throws on anything
|
|
271
|
+
// malformed, so map any failure to a domain WithdrawalTxMemoError for both
|
|
272
|
+
// the hash and non-hash paths rather than leaking the raw SDK error.
|
|
273
|
+
try {
|
|
274
|
+
if (transaction.withdraw_memo_type === "hash") {
|
|
271
275
|
const buffer = Buffer.from(transaction.withdraw_memo, "base64");
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
+
this.setMemo(Memo.hash(buffer.toString("hex")));
|
|
277
|
+
} else {
|
|
278
|
+
this.setMemo(
|
|
279
|
+
new Memo(transaction.withdraw_memo_type, transaction.withdraw_memo),
|
|
280
|
+
);
|
|
276
281
|
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
new Memo(transaction.withdraw_memo_type, transaction.withdraw_memo),
|
|
280
|
-
);
|
|
282
|
+
} catch {
|
|
283
|
+
throw new WithdrawalTxMemoError();
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
return this.transfer(
|
|
@@ -18,3 +18,25 @@ export type IsValidSep7UriResult = {
|
|
|
18
18
|
result: boolean;
|
|
19
19
|
reason?: string;
|
|
20
20
|
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Matches a fully qualified domain name (e.g. "example.com", "sub.example.co.uk").
|
|
24
|
+
*
|
|
25
|
+
* Rejects userinfo ('@'), ports (':'), paths/queries ('/', '?'), scheme
|
|
26
|
+
* prefixes ('//'), bare hostnames like 'localhost', and raw IPv4/IPv6
|
|
27
|
+
* addresses (the all-numeric final label fails the alphabetic TLD rule).
|
|
28
|
+
*
|
|
29
|
+
* @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0007.md#verifying-the-signature step 3.
|
|
30
|
+
*/
|
|
31
|
+
export const FULLY_QUALIFIED_DOMAIN_NAME_REGEX =
|
|
32
|
+
/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if the given string is a valid fully qualified domain name.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} domain the domain string to validate.
|
|
38
|
+
*
|
|
39
|
+
* @returns {boolean} true if 'domain' is a valid fully qualified domain name.
|
|
40
|
+
*/
|
|
41
|
+
export const isValidFullyQualifiedDomainName = (domain: string): boolean =>
|
|
42
|
+
FULLY_QUALIFIED_DOMAIN_NAME_REGEX.test(domain);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Keypair, Networks, StellarToml } from "@stellar/stellar-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Sep7OperationType,
|
|
4
|
+
URI_MSG_MAX_LENGTH,
|
|
5
|
+
isValidFullyQualifiedDomainName,
|
|
6
|
+
} from "../Types";
|
|
3
7
|
import { Sep7LongMsgError } from "../Exceptions";
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -234,6 +238,15 @@ export abstract class Sep7Base {
|
|
|
234
238
|
return false;
|
|
235
239
|
}
|
|
236
240
|
|
|
241
|
+
// SEP-7 step 3: "If the origin_domain is not a valid fully qualified
|
|
242
|
+
// domain name, do not allow the user to sign the transaction." This must
|
|
243
|
+
// be enforced before resolving the stellar.toml, otherwise a malformed
|
|
244
|
+
// origin_domain (e.g. containing userinfo, a port, or a path) could
|
|
245
|
+
// redirect the trust-anchoring TOML fetch to an attacker-controlled host.
|
|
246
|
+
if (!isValidFullyQualifiedDomainName(originDomain)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
try {
|
|
238
251
|
const toml = await StellarToml.Resolver.resolve(originDomain);
|
|
239
252
|
const signingKey = toml.URI_REQUEST_SIGNING_KEY;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
WEB_STELLAR_SCHEME,
|
|
9
9
|
URI_MSG_MAX_LENGTH,
|
|
10
10
|
URI_REPLACE_MAX_LENGTH,
|
|
11
|
+
isValidFullyQualifiedDomainName,
|
|
11
12
|
} from "../Types";
|
|
12
13
|
import {
|
|
13
14
|
Sep7InvalidUriError,
|
|
@@ -42,6 +43,7 @@ export const isValidSep7Uri = (uri: string): IsValidSep7UriResult => {
|
|
|
42
43
|
url.searchParams.get("network_passphrase") || Networks.PUBLIC;
|
|
43
44
|
const destination = url.searchParams.get("destination");
|
|
44
45
|
const msg = url.searchParams.get("msg");
|
|
46
|
+
const originDomain = url.searchParams.get("origin_domain");
|
|
45
47
|
|
|
46
48
|
if (![Sep7OperationType.tx, Sep7OperationType.pay].includes(type)) {
|
|
47
49
|
return {
|
|
@@ -98,6 +100,18 @@ export const isValidSep7Uri = (uri: string): IsValidSep7UriResult => {
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
// 'origin_domain' is optional (SEP-7), but when present it must be a valid
|
|
104
|
+
// fully qualified domain name (SEP-7 step 3), otherwise a malformed value
|
|
105
|
+
// could later redirect the trust-anchoring stellar.toml fetch in
|
|
106
|
+
// Sep7Base.verifySignature() to an attacker-controlled host.
|
|
107
|
+
if (originDomain && !isValidFullyQualifiedDomainName(originDomain)) {
|
|
108
|
+
return {
|
|
109
|
+
result: false,
|
|
110
|
+
reason:
|
|
111
|
+
"the provided 'origin_domain' parameter is not a valid fully qualified domain name",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
return {
|
|
102
116
|
result: true,
|
|
103
117
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { Horizon } from "@stellar/stellar-sdk";
|
|
3
|
+
import sinon from "sinon";
|
|
3
4
|
|
|
4
5
|
import { AccountService, SigningKeypair, Wallet } from "../src";
|
|
5
|
-
import { HORIZON_ORDER } from "../src/walletSdk/Types";
|
|
6
|
+
import { HORIZON_ORDER, HORIZON_LIMIT_DEFAULT } from "../src/walletSdk/Types";
|
|
6
7
|
import { IssuedAssetId } from "../src/walletSdk/Asset";
|
|
7
8
|
|
|
8
9
|
let accountService: AccountService;
|
|
@@ -45,7 +46,7 @@ describe("Horizon", () => {
|
|
|
45
46
|
await stellar.submitTransaction(addUsdcTx);
|
|
46
47
|
|
|
47
48
|
accountAddress = testingAccountKp.publicKey;
|
|
48
|
-
},
|
|
49
|
+
}, 120000);
|
|
49
50
|
|
|
50
51
|
it("should return stellar account details", async () => {
|
|
51
52
|
const response = await accountService.getInfo({ accountAddress });
|
|
@@ -97,3 +98,58 @@ describe("Horizon", () => {
|
|
|
97
98
|
).toBeTruthy();
|
|
98
99
|
});
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
// Hermetic (no network) — kept out of the "Horizon" describe so its
|
|
103
|
+
// friendbot/loadAccount beforeAll does not run.
|
|
104
|
+
describe("AccountService.getHistory cursor handling", () => {
|
|
105
|
+
const accountAddress =
|
|
106
|
+
"GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB";
|
|
107
|
+
let service: AccountService;
|
|
108
|
+
let fakeBuilder: Record<string, sinon.SinonStub>;
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
service = Wallet.TestNet().stellar().account();
|
|
112
|
+
fakeBuilder = {
|
|
113
|
+
forAccount: sinon.stub(),
|
|
114
|
+
limit: sinon.stub(),
|
|
115
|
+
order: sinon.stub(),
|
|
116
|
+
includeFailed: sinon.stub(),
|
|
117
|
+
cursor: sinon.stub(),
|
|
118
|
+
call: sinon.stub().resolves({ records: [] }),
|
|
119
|
+
};
|
|
120
|
+
// Every chained method returns the builder itself.
|
|
121
|
+
["forAccount", "limit", "order", "includeFailed", "cursor"].forEach((m) =>
|
|
122
|
+
fakeBuilder[m].returns(fakeBuilder),
|
|
123
|
+
);
|
|
124
|
+
sinon
|
|
125
|
+
.stub(
|
|
126
|
+
service["server"] as unknown as { operations: () => unknown },
|
|
127
|
+
"operations",
|
|
128
|
+
)
|
|
129
|
+
.returns(fakeBuilder);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
afterEach(() => {
|
|
133
|
+
sinon.restore();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Regression guard for the v16 URLSearchParams change: passing an undefined
|
|
137
|
+
// cursor into the call builder would serialize "cursor=undefined" and Horizon
|
|
138
|
+
// responds 400, so getHistory must omit .cursor() when none is provided.
|
|
139
|
+
it("omits .cursor() when no cursor is provided", async () => {
|
|
140
|
+
await service.getHistory({ accountAddress });
|
|
141
|
+
|
|
142
|
+
expect(fakeBuilder.cursor.called).toBe(false);
|
|
143
|
+
expect(fakeBuilder.call.calledOnce).toBe(true);
|
|
144
|
+
expect(fakeBuilder.limit.calledWith(HORIZON_LIMIT_DEFAULT)).toBe(true);
|
|
145
|
+
expect(fakeBuilder.order.calledWith(HORIZON_ORDER.DESC)).toBe(true);
|
|
146
|
+
expect(fakeBuilder.includeFailed.calledWith(false)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("applies .cursor(value) when a cursor is provided", async () => {
|
|
150
|
+
await service.getHistory({ accountAddress, cursor: "abc123" });
|
|
151
|
+
|
|
152
|
+
expect(fakeBuilder.cursor.calledOnceWithExactly("abc123")).toBe(true);
|
|
153
|
+
expect(fakeBuilder.call.calledOnce).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
});
|
package/test/customer.test.ts
CHANGED
|
@@ -14,7 +14,7 @@ describe("Customer", () => {
|
|
|
14
14
|
await axios.get(
|
|
15
15
|
"https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
|
|
16
16
|
);
|
|
17
|
-
},
|
|
17
|
+
}, 120000);
|
|
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
|
-
},
|
|
92
|
+
}, 120000);
|
|
93
93
|
});
|
package/test/recovery.test.ts
CHANGED
package/test/sep38.test.ts
CHANGED
|
@@ -11,7 +11,7 @@ describe("SEP-38", () => {
|
|
|
11
11
|
anchor = wallet.anchor({ homeDomain: "testanchor.stellar.org" });
|
|
12
12
|
sep38 = anchor.sep38();
|
|
13
13
|
accountKp = accountKp = wallet.stellar().account().createKeypair();
|
|
14
|
-
},
|
|
14
|
+
}, 120000);
|
|
15
15
|
|
|
16
16
|
it("should get Sep-38 anchor info", async () => {
|
|
17
17
|
const resp = await sep38.info();
|
package/test/sep6.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ describe("SEP-6", () => {
|
|
|
16
16
|
await axios.get(
|
|
17
17
|
"https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
|
|
18
18
|
);
|
|
19
|
-
},
|
|
19
|
+
}, 120000);
|
|
20
20
|
|
|
21
21
|
it("should get anchor info", async () => {
|
|
22
22
|
const resp = await sep6.info();
|
|
@@ -172,7 +172,7 @@ describe("SEP-6", () => {
|
|
|
172
172
|
resp = await anchor.sep6().getTransactionBy({ authToken, id });
|
|
173
173
|
|
|
174
174
|
expect(resp.id).toEqual(id);
|
|
175
|
-
},
|
|
175
|
+
}, 120000);
|
|
176
176
|
|
|
177
177
|
let txId;
|
|
178
178
|
|
package/test/sep7.test.ts
CHANGED
|
@@ -226,6 +226,115 @@ describe("Sep7Base", () => {
|
|
|
226
226
|
|
|
227
227
|
expect(await uri.verifySignature()).toBe(true);
|
|
228
228
|
});
|
|
229
|
+
|
|
230
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain has userinfo (RFC 3986 confusion)", async () => {
|
|
231
|
+
const uriStr =
|
|
232
|
+
"web+stellar:tx?xdr=test&origin_domain=stellar.org%40evil.com&signature=sig";
|
|
233
|
+
const uri = new Sep7Tx(uriStr);
|
|
234
|
+
|
|
235
|
+
const resolveSpy = jest
|
|
236
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
237
|
+
.mockResolvedValue({
|
|
238
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
239
|
+
});
|
|
240
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
241
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
242
|
+
resolveSpy.mockClear();
|
|
243
|
+
|
|
244
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
245
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain has a port", async () => {
|
|
249
|
+
const uriStr =
|
|
250
|
+
"web+stellar:tx?xdr=test&origin_domain=example.com%3A8443&signature=sig";
|
|
251
|
+
const uri = new Sep7Tx(uriStr);
|
|
252
|
+
|
|
253
|
+
const resolveSpy = jest
|
|
254
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
255
|
+
.mockResolvedValue({
|
|
256
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
257
|
+
});
|
|
258
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
259
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
260
|
+
resolveSpy.mockClear();
|
|
261
|
+
|
|
262
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
263
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain has a path", async () => {
|
|
267
|
+
const uriStr =
|
|
268
|
+
"web+stellar:tx?xdr=test&origin_domain=example.com%2Fpath&signature=sig";
|
|
269
|
+
const uri = new Sep7Tx(uriStr);
|
|
270
|
+
|
|
271
|
+
const resolveSpy = jest
|
|
272
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
273
|
+
.mockResolvedValue({
|
|
274
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
275
|
+
});
|
|
276
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
277
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
278
|
+
resolveSpy.mockClear();
|
|
279
|
+
|
|
280
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
281
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain is a raw IPv4 address", async () => {
|
|
285
|
+
const uriStr =
|
|
286
|
+
"web+stellar:tx?xdr=test&origin_domain=127.0.0.1&signature=sig";
|
|
287
|
+
const uri = new Sep7Tx(uriStr);
|
|
288
|
+
|
|
289
|
+
const resolveSpy = jest
|
|
290
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
291
|
+
.mockResolvedValue({
|
|
292
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
293
|
+
});
|
|
294
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
295
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
296
|
+
resolveSpy.mockClear();
|
|
297
|
+
|
|
298
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
299
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain is 'localhost'", async () => {
|
|
303
|
+
const uriStr =
|
|
304
|
+
"web+stellar:tx?xdr=test&origin_domain=localhost&signature=sig";
|
|
305
|
+
const uri = new Sep7Tx(uriStr);
|
|
306
|
+
|
|
307
|
+
const resolveSpy = jest
|
|
308
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
309
|
+
.mockResolvedValue({
|
|
310
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
311
|
+
});
|
|
312
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
313
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
314
|
+
resolveSpy.mockClear();
|
|
315
|
+
|
|
316
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
317
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("verifySignature() returns false and never resolves the toml when origin_domain has an injected CRLF", async () => {
|
|
321
|
+
// 'stellar.org%0d%0aevil.com' URL-decodes to 'stellar.org\r\nevil.com'.
|
|
322
|
+
const uriStr =
|
|
323
|
+
"web+stellar:tx?xdr=test&origin_domain=stellar.org%0d%0aevil.com&signature=sig";
|
|
324
|
+
const uri = new Sep7Tx(uriStr);
|
|
325
|
+
|
|
326
|
+
const resolveSpy = jest
|
|
327
|
+
.spyOn(StellarToml.Resolver, "resolve")
|
|
328
|
+
.mockResolvedValue({
|
|
329
|
+
URI_REQUEST_SIGNING_KEY: testKp1.publicKey,
|
|
330
|
+
});
|
|
331
|
+
// jest.spyOn() re-spying on an already-mocked function preserves the
|
|
332
|
+
// existing call history, so clear it to accurately assert non-invocation.
|
|
333
|
+
resolveSpy.mockClear();
|
|
334
|
+
|
|
335
|
+
expect(await uri.verifySignature()).toBe(false);
|
|
336
|
+
expect(resolveSpy).not.toHaveBeenCalled();
|
|
337
|
+
});
|
|
229
338
|
});
|
|
230
339
|
|
|
231
340
|
describe("Sep7Tx", () => {
|
|
@@ -240,7 +349,7 @@ describe("Sep7Tx", () => {
|
|
|
240
349
|
await stellar.fundTestnetAccount(testKp1.publicKey);
|
|
241
350
|
await stellar.fundTestnetAccount(testKp2.publicKey);
|
|
242
351
|
}
|
|
243
|
-
},
|
|
352
|
+
}, 120000);
|
|
244
353
|
|
|
245
354
|
it("forTransaction sets the tx parameter", async () => {
|
|
246
355
|
const txBuilder = await stellar.transaction({
|
|
@@ -589,6 +698,32 @@ describe("sep7Parser", () => {
|
|
|
589
698
|
).toBe(true);
|
|
590
699
|
});
|
|
591
700
|
|
|
701
|
+
it("isValidSep7Uri(uri) returns true when 'origin_domain' is absent", () => {
|
|
702
|
+
expect(
|
|
703
|
+
isValidSep7Uri(
|
|
704
|
+
"web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&msg=pay%20me%20with%20lumens",
|
|
705
|
+
).result,
|
|
706
|
+
).toBe(true);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it("isValidSep7Uri(uri) returns true when 'origin_domain' is a valid fully qualified domain name", () => {
|
|
710
|
+
expect(
|
|
711
|
+
isValidSep7Uri(
|
|
712
|
+
"web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&msg=pay%20me%20with%20lumens&origin_domain=someDomain.com",
|
|
713
|
+
).result,
|
|
714
|
+
).toBe(true);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("isValidSep7Uri(uri) returns 'false' with 'reason' when 'origin_domain' is not a valid fully qualified domain name", () => {
|
|
718
|
+
const validation = isValidSep7Uri(
|
|
719
|
+
"web+stellar:pay?destination=GCALNQQBXAPZ2WIRSDDBMSTAKCUH5SG6U76YBFLQLIXJTF7FE5AX7AOO&amount=120.1234567&memo=skdjfasf&msg=pay%20me%20with%20lumens&origin_domain=stellar.org%40evil.com",
|
|
720
|
+
);
|
|
721
|
+
expect(validation.result).toBe(false);
|
|
722
|
+
expect(validation.reason).toBe(
|
|
723
|
+
"the provided 'origin_domain' parameter is not a valid fully qualified domain name",
|
|
724
|
+
);
|
|
725
|
+
});
|
|
726
|
+
|
|
592
727
|
it("isValidSep7Uri(uri) returns 'false' with 'reason' when it is not valid in some way", () => {
|
|
593
728
|
const validation1 = isValidSep7Uri(
|
|
594
729
|
"not-a-stellar-uri:tx?xdr=AAAAAP%2Byw%2BZEuNg533pUmwlYxfrq6%2FBoMJqiJ8vuQhf6rHWmAAAAZAB8NHAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABSFVHAAAAAABAH0wIyY3BJBS2qHdRPAV80M8hF7NBpxRjXyjuT9kEbH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAAAAAAAAA%3D&callback=url%3Ahttps%3A%2F%2FsomeSigningService.com%2Fa8f7asdfkjha&pubkey=GAU2ZSYYEYO5S5ZQSMMUENJ2TANY4FPXYGGIMU6GMGKTNVDG5QYFW6JS&msg=order%20number%2024",
|
|
@@ -834,7 +969,9 @@ describe("sep7Parser", () => {
|
|
|
834
969
|
|
|
835
970
|
it("Sep7Tx.getReplacements() throws on replace param missing hints", () => {
|
|
836
971
|
const uri = new Sep7Tx(
|
|
837
|
-
`web+stellar:tx?xdr=test&replace=${encodeURIComponent(
|
|
972
|
+
`web+stellar:tx?xdr=test&replace=${encodeURIComponent(
|
|
973
|
+
"sourceAccount:X",
|
|
974
|
+
)}`,
|
|
838
975
|
);
|
|
839
976
|
|
|
840
977
|
expect(() => uri.getReplacements()).toThrow(Sep7InvalidUriError);
|