@stellar/typescript-wallet-sdk 3.0.3 → 4.0.0-beta.1786156822907

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.
@@ -1,4 +1,4 @@
1
- import { Account as StellarAccount, Transaction, Horizon, Memo, xdr } from "@stellar/stellar-sdk";
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 {StellarAccount} sourceAccount - The source account for the transaction.
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: StellarAccount, baseFee?: number, memo?: Memo, timebounds?: Horizon.Server.Timebounds);
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.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@stellar/typescript-wallet-sdk",
3
- "version": "3.0.3",
3
+ "version": "4.0.0-beta.1786156822907",
4
4
  "engines": {
5
- "node": ">=20"
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": "15.0.1",
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
+ }
@@ -96,13 +96,20 @@ export class AccountService {
96
96
  throw new OperationsLimitExceededError(HORIZON_LIMIT_MAX);
97
97
  }
98
98
 
99
- return this.server
99
+ let builder = this.server
100
100
  .operations()
101
101
  .forAccount(accountAddress)
102
102
  .limit(limit)
103
103
  .order(order)
104
- .cursor(cursor)
105
- .includeFailed(includeFailed)
106
- .call();
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
- Account as StellarAccount,
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: StellarAccount;
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
- Account as StellarAccount,
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 {StellarAccount} sourceAccount - The source account for the transaction.
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: StellarAccount,
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
- if (transaction.withdraw_memo_type === "hash") {
270
- try {
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
- const memo = Memo.hash(buffer.toString("hex"));
273
- this.setMemo(memo);
274
- } catch {
275
- throw new WithdrawalTxMemoError();
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
- } else {
278
- this.setMemo(
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(
@@ -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;
@@ -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
+ });
@@ -1,4 +1,5 @@
1
1
  import {
2
+ Account,
2
3
  Keypair,
3
4
  Memo,
4
5
  MemoText,
@@ -22,6 +23,7 @@ import {
22
23
  import { TransactionStatus, WithdrawTransaction } from "../src/walletSdk/Types";
23
24
  import {
24
25
  TransactionSubmitWithFeeIncreaseFailedError,
26
+ WithdrawalTxMemoError,
25
27
  WithdrawalTxMissingDestinationError,
26
28
  WithdrawalTxMissingMemoError,
27
29
  WithdrawalTxNotPendingUserTransferStartError,
@@ -753,3 +755,66 @@ describe("Account Modifying", () => {
753
755
  expect(parseInt(accResp.balances[0].balance)).toBeGreaterThan(19998);
754
756
  }, 30000);
755
757
  });
758
+
759
+ // Hermetic (no network): stellar-sdk v16 tightened Memo validation, so a
760
+ // malformed anchor-supplied withdrawal memo now throws when the Memo is
761
+ // constructed. transferWithdrawalTransaction must map that to the domain
762
+ // WithdrawalTxMemoError for every memo type, not just "hash".
763
+ describe("transferWithdrawalTransaction memo validation", () => {
764
+ const localKp = SigningKeypair.fromSecret(
765
+ "SAS372GXRG6U7FW6W2PRVELKPOJG2FZZUADCIELWU2U3A45TNWXEQUV5",
766
+ );
767
+ let stellarLocal: Stellar;
768
+
769
+ beforeEach(() => {
770
+ stellarLocal = Wallet.TestNet().stellar();
771
+ sinon
772
+ .stub(stellarLocal.server, "loadAccount")
773
+ .resolves(new Account(localKp.publicKey, "1") as any);
774
+ });
775
+
776
+ afterEach(() => {
777
+ sinon.restore();
778
+ });
779
+
780
+ const buildWithdrawal = (memoType: string, memo: string) =>
781
+ ({
782
+ status: TransactionStatus.pending_user_transfer_start,
783
+ withdraw_anchor_account: localKp.publicKey,
784
+ withdraw_memo_type: memoType,
785
+ withdraw_memo: memo,
786
+ amount_in: "10",
787
+ } as unknown as WithdrawTransaction);
788
+
789
+ const usdc = () => new IssuedAssetId("USDC", localKp.publicKey);
790
+
791
+ it("maps a malformed hash memo to WithdrawalTxMemoError", async () => {
792
+ const builder = await stellarLocal.transaction({ sourceAddress: localKp });
793
+ expect(() =>
794
+ builder.transferWithdrawalTransaction(
795
+ buildWithdrawal("hash", "not-a-valid-hash"),
796
+ usdc(),
797
+ ),
798
+ ).toThrow(WithdrawalTxMemoError);
799
+ });
800
+
801
+ it("maps a malformed id memo to WithdrawalTxMemoError", async () => {
802
+ const builder = await stellarLocal.transaction({ sourceAddress: localKp });
803
+ expect(() =>
804
+ builder.transferWithdrawalTransaction(
805
+ buildWithdrawal("id", "not-a-number"),
806
+ usdc(),
807
+ ),
808
+ ).toThrow(WithdrawalTxMemoError);
809
+ });
810
+
811
+ it("maps an over-long text memo to WithdrawalTxMemoError", async () => {
812
+ const builder = await stellarLocal.transaction({ sourceAddress: localKp });
813
+ expect(() =>
814
+ builder.transferWithdrawalTransaction(
815
+ buildWithdrawal("text", "x".repeat(29)),
816
+ usdc(),
817
+ ),
818
+ ).toThrow(WithdrawalTxMemoError);
819
+ });
820
+ });
@@ -0,0 +1,102 @@
1
+ import { StellarToml } from "@stellar/stellar-sdk";
2
+ import { parse } from "smol-toml";
3
+
4
+ import { parseToml } from "../src/walletSdk/Utils/toml";
5
+
6
+ // Hermetic coverage for the SEP-1 TOML path. stellar-sdk v16's
7
+ // StellarToml.Resolver.resolve() fetches the raw text and returns
8
+ // `smol-toml`.parse(text) verbatim (no post-processing), so parsing a fixture
9
+ // string with the same parser here is a faithful, network-free stand-in for
10
+ // the whole pipeline: it exercises v16's strict TOML 1.0 parser and our
11
+ // TomlInfo mapping together.
12
+ const parseTomlString = (toml: string) =>
13
+ parseToml(parse(toml) as unknown as StellarToml.Api.StellarToml);
14
+
15
+ describe("parseToml", () => {
16
+ it("parses and maps a fully populated stellar.toml", () => {
17
+ const toml = `
18
+ VERSION = "2.0.0"
19
+ NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"
20
+ SIGNING_KEY = "GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB"
21
+ WEB_AUTH_ENDPOINT = "https://auth.example.com/auth"
22
+ TRANSFER_SERVER = "https://anchor.example.com/sep6"
23
+ TRANSFER_SERVER_SEP0024 = "https://anchor.example.com/sep24"
24
+ KYC_SERVER = "https://anchor.example.com/kyc"
25
+ HORIZON_URL = "https://horizon.example.com"
26
+ ACCOUNTS = ["GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB"]
27
+
28
+ [DOCUMENTATION]
29
+ ORG_NAME = "Example Anchor"
30
+ ORG_URL = "https://example.com"
31
+
32
+ [[CURRENCIES]]
33
+ code = "USDC"
34
+ issuer = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
35
+ display_decimals = 2
36
+ is_asset_anchored = true
37
+ anchor_asset_type = "fiat"
38
+ collateral_addresses = ["GADDR1"]
39
+
40
+ [[PRINCIPALS]]
41
+ name = "Jane Doe"
42
+ email = "jane@example.com"
43
+ id_photo_hash = "abc123"
44
+
45
+ [[VALIDATORS]]
46
+ PUBLIC_KEY = "GDVALIDATORXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
47
+ HOST = "core.example.com:11625"
48
+ `;
49
+
50
+ const info = parseTomlString(toml);
51
+
52
+ expect(info.version).toBe("2.0.0");
53
+ expect(info.networkPassphrase).toBe("Test SDF Network ; September 2015");
54
+ expect(info.webAuthEndpoint).toBe("https://auth.example.com/auth");
55
+ expect(info.transferServer).toBe("https://anchor.example.com/sep6");
56
+ expect(info.transferServerSep24).toBe("https://anchor.example.com/sep24");
57
+ expect(info.signingKey).toBe(
58
+ "GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB",
59
+ );
60
+ expect(info.horizonUrl).toBe("https://horizon.example.com");
61
+
62
+ expect(info.documentation.orgName).toBe("Example Anchor");
63
+ expect(info.documentation.orgUrl).toBe("https://example.com");
64
+
65
+ expect(info.currencies).toHaveLength(1);
66
+ expect(info.currencies[0].code).toBe("USDC");
67
+ // display_decimals is a TOML integer; the mapping preserves the number.
68
+ expect(info.currencies[0].displayDecimals).toBe(2);
69
+ expect(info.currencies[0].isAssetAnchored).toBe(true);
70
+ expect(info.currencies[0].anchorAssetType).toBe("fiat");
71
+ expect(info.currencies[0].collateralAddresses).toEqual(["GADDR1"]);
72
+
73
+ expect(info.principals).toHaveLength(1);
74
+ expect(info.principals[0].name).toBe("Jane Doe");
75
+ expect(info.principals[0].idPhotoHash).toBe("abc123");
76
+
77
+ expect(info.validators).toHaveLength(1);
78
+ expect(info.validators[0].host).toBe("core.example.com:11625");
79
+ expect(info.validators[0].publicKey).toBe(
80
+ "GDVALIDATORXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
81
+ );
82
+ });
83
+
84
+ it("defaults optional array sections to [] when absent", () => {
85
+ const info = parseTomlString(`
86
+ [DOCUMENTATION]
87
+ ORG_NAME = "Example Anchor"
88
+ `);
89
+
90
+ expect(info.principals).toEqual([]);
91
+ expect(info.currencies).toEqual([]);
92
+ expect(info.validators).toEqual([]);
93
+ });
94
+
95
+ it("rejects malformed TOML (v16 smol-toml strict parsing)", () => {
96
+ // Duplicate keys are invalid under TOML 1.0; smol-toml throws where the
97
+ // previous lenient parser may not have.
98
+ expect(() =>
99
+ parseTomlString(`VERSION = "1"\nVERSION = "2"\n[DOCUMENTATION]\n`),
100
+ ).toThrow();
101
+ });
102
+ });
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "esModuleInterop": true,
4
+ "skipLibCheck": true,
4
5
  "baseUrl": "src/",
5
6
  "outDir": "lib",
6
7
  "declaration": true,