@spield/sdk 0.4.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ Breaking. The SDK now targets only the SR-based (v2) contracts on Testnet.
6
+
7
+ - Remove the legacy v1 protocol: the `wrapper`, `vault`, `market`, `tokens`, `portfolio` and
8
+ `protocol` clients, their config addresses, and the PT+YT dual-trustline path. YT is a contract
9
+ token issued by the yield engine and never needed a trustline.
10
+ - Promote the protocol namespaces to the client itself: `client.sr`, `client.yield`, `client.market`
11
+ and friends replace `client.current.*` / `client.v2.*`, which are gone along with `SpieldV2Client`.
12
+ - Flatten the network config: the former `v2.contracts` / `v2.assets` are now `contracts` / `assets`,
13
+ so `SpieldV2NetworkConfig` and the `requireV2` guard are no longer needed.
14
+ - Remove `mainnetPreview()`. The SDK ships Testnet only; use `customNetwork()` for anything else.
15
+ - Map contract errors from `contracts/shared/src/errors.rs` instead of guessing from message text.
16
+ A failed `sr.deposit` previously reported "The wallet needs PT and YT trustlines before receiving
17
+ minted tokens", which was both v1-shaped and wrong for a USDC wrap; `Error(Contract, #N)` codes
18
+ such as the deposit cap (#107), slippage floors (#105) and dust (#106) were not surfaced at all.
19
+ - Move `fromScalar12` to `./sr` alongside `SCALAR_12`; it is still exported from the package root.
20
+ - Cover the USDC → SR → PT + YT path in the integration suite, asserting the exact contract,
21
+ method and argument layout of `sr.deposit` and `yield.mint_py`.
22
+ - Update the six examples and the browser test app to the flat client.
23
+
24
+ ## Unreleased
25
+
26
+ - Point the v2 Testnet config at the 2026-08-27 SR redeploy. The SDK was still pinned to the
27
+ superseded stack recorded in `deploy_sr_testnet.state.pre-redeploy-20260827`, whose deposit cap is
28
+ full (zero headroom), so wraps and deposits could not succeed. SR, strategy, yield engine, market,
29
+ vault, router and the PT SAC all move, and the PT trustline asset becomes `SPLDPT6`.
30
+ - Assert the SR deposit-cap invariant in the Testnet read smoke instead of pinning a live headroom
31
+ value that changes with every deposit.
32
+ - Fix a packaging defect that made the tarball uninstallable under npm: `@spield/sdk` declared
33
+ itself as a `link:` dependency on a machine-local path, so `npm install` failed with
34
+ `EUNSUPPORTEDPROTOCOL`.
35
+ - Drop the unused `dotenv` runtime dependency, so consumers only pull in `@stellar/stellar-sdk`.
36
+ - Fill in the unset `allowBuilds` placeholder in `pnpm-workspace.yaml`; `pnpm install` exited 1 with
37
+ `ERR_PNPM_IGNORED_BUILDS` on a fresh clone.
38
+ - Stop type-checking `examples/sdk-test-app` from the root `tsconfig.json`; its browser wallet
39
+ dependencies are not part of the root install, so `pnpm run typecheck` exited 2. The demo is still
40
+ type-checked by its own `pnpm --dir examples/sdk-test-app run build`.
41
+ - Drop the unfulfilled `MIT` license declaration and its `files` entry; the package ships no
42
+ license for now.
43
+
44
+ ## 0.3.1
45
+
46
+ - Track the Testnet SR deposit-cap rollout with `SrState.depositCapReached`, while preserving the
47
+ existing typed cap and headroom amounts.
48
+ - Verify the live 100 USDC cap and zero headroom without treating the cap as a withdrawal pause.
49
+
50
+ ## 0.3.0
51
+
52
+ - Integrate the 2026-08-27 contract-source changes: Blend strategy reads, holder/LP/receipt
53
+ keep-alives, caller-controlled LP `minShares`, resumable vault redemption state, and vault surplus.
54
+ - Detect the deployed old market ABI for zero-bound liquidity adds and normalize old vault
55
+ receipts/stats so the latest SDK continues to work before the Testnet contracts are upgraded.
56
+ - Track the latest router deployment from `deploy_sr_testnet.state`.
57
+
58
+ ## 0.2.0
59
+
60
+ - Add the current SR-based protocol under `client.current` / `client.v2`.
61
+ - Ship the live 2026-08-25 Testnet SR, yield-engine, PT/SR market, vault, router, PT, and USDC addresses.
62
+ - Add live-safe YT buy/sell orchestration, SR partial exits, v2 health/portfolio reads, and PT-only trustline setup.
63
+ - Keep the original v1 top-level clients for backward compatibility.
64
+
65
+ ## 0.1.0
66
+
67
+ - Initial SDK scaffold for Spield Testnet fixed-rate vault, wrapper, market, tokens, portfolio, and protocol APIs.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @spield/sdk
2
+
3
+ TypeScript SDK for the Spield Protocol on Stellar Testnet. The SDK targets the SR-based contracts
4
+ recorded in `deploy_sr_testnet.state` (2026-08-27 redeploy).
5
+
6
+ ```ts
7
+ import { FreighterSigner, SpieldClient, testnet } from '@spield/sdk';
8
+
9
+ const client = new SpieldClient({
10
+ network: testnet(),
11
+ signer: new FreighterSigner(),
12
+ });
13
+
14
+ const health = await client.protocol.getHealth();
15
+ const srQuote = await client.sr.previewDeposit({ amount: '100' });
16
+ const ptQuote = await client.router.quoteBuyPt({ usdcIn: '100' });
17
+
18
+ await client.tokens.ensurePtTrustline();
19
+ await client.router.buyPt({ usdcIn: '100' });
20
+ ```
21
+
22
+ ## The protocol model
23
+
24
+ USDC wraps into **SR**, a yield-bearing share token over a Blend strategy. SR splits into **PT**
25
+ (principal) and **YT** (yield). PT trades against SR on the market, and the fixed-rate vault sells a
26
+ guaranteed coupon out of its PT inventory.
27
+
28
+ ```
29
+ USDC ──sr.deposit──▶ SR ──yield.mintPy──▶ PT + YT
30
+ ▲ │
31
+ └──yield.redeemPy─────┘
32
+ ```
33
+
34
+ SR is a share token, so **one SR is not one USDC** — use `sr.previewDeposit` / `sr.previewRedeem`, or
35
+ `sr.srToUnderlying` / `sr.underlyingToSr` with the current `exchangeRate`.
36
+
37
+ ## Namespaces
38
+
39
+ | Namespace | What it does |
40
+ |---|---|
41
+ | `client.sr` | Wrap USDC into SR shares; unwrap fully or partially |
42
+ | `client.strategy` | Blend rate guard, position value, withdrawable liquidity |
43
+ | `client.yield` | Mint/redeem PT + YT, read and claim interest, transfer YT |
44
+ | `client.market` | PT/SR quotes, PT and YT trades, liquidity |
45
+ | `client.vault` | Fixed-rate quotes, receipts, deposit, redeem, harvest |
46
+ | `client.router` | One-transaction USDC routes for PT, redemption, and yield claims |
47
+ | `client.tokens` | USDC/SR/PT/YT balances and the single required PT trustline |
48
+ | `client.portfolio` | Wallet-wide balances, interest, LP position, vault receipts |
49
+ | `client.protocol` | Cross-contract health and yield-engine solvency |
50
+
51
+ **Trustlines:** PT is the only classic asset, so it is the only trustline a wallet needs. The yield
52
+ engine contract *is* the YT token — YT is a contract token with no trustline. Call
53
+ `client.tokens.ensurePtTrustline()` once before receiving PT.
54
+
55
+ **YT trades take two signatures.** The deployed Blend-backed stack cannot fit a YT wrap+trade or
56
+ trade+unwrap into a single Soroban transaction. `client.buyYtFromUsdc()` and
57
+ `client.sellYtToUsdc()` deliberately use up to two wallet signatures and report progress.
58
+
59
+ See [USAGE.md](./USAGE.md) for complete examples.
60
+
61
+ ## Scope
62
+
63
+ Testnet only. There is no mainnet configuration in this SDK. Point it at another deployment with
64
+ `customNetwork()` if you need one.
65
+
66
+ ## Development and verification
67
+
68
+ ```bash
69
+ pnpm install
70
+ pnpm run typecheck
71
+ pnpm test
72
+ pnpm run build
73
+ pnpm run test:package
74
+ pnpm run test:e2e:testnet
75
+ ```
76
+
77
+ The Testnet read suite validates SR, Blend liquidity, yield-engine solvency, PT/SR reserves and
78
+ prices, vault backing, and router quotes against the checked-in live deployment, and cross-checks
79
+ that the engine, market and vault all agree on one series expiry. The write smoke test is skipped
80
+ unless a disposable funded `SPIELD_E2E_SECRET` is provided; it only creates/verifies the PT
81
+ trustline.
82
+
83
+ ## Manual SDK test app
84
+
85
+ A browser console that exercises every SDK method against Testnet:
86
+
87
+ ```bash
88
+ pnpm --dir examples/sdk-test-app install
89
+ pnpm demo
90
+ ```
91
+
92
+ Reads work without a wallet. Writing uses the same multi-wallet connector as the Protocol frontend
93
+ and requires a disposable funded Testnet account.
94
+ See [the demo README](./examples/sdk-test-app/README.md) for details.
package/RELEASE.md ADDED
@@ -0,0 +1,8 @@
1
+ # Release
2
+
3
+ 1. Run `pnpm test`.
4
+ 2. Run `pnpm build`.
5
+ 3. Run `npm pack --dry-run`.
6
+ 4. Publish with `npm publish --access public`.
7
+
8
+ Each release should mention the Spield deployment/config it targets.
package/USAGE.md ADDED
@@ -0,0 +1,215 @@
1
+ # Using the Spield SDK
2
+
3
+ ## Create a client
4
+
5
+ ```ts
6
+ import { FreighterSigner, SpieldClient, testnet } from '@spield/sdk';
7
+
8
+ // Omit the signer for read-only use.
9
+ const client = new SpieldClient({
10
+ network: testnet(),
11
+ signer: new FreighterSigner(),
12
+ });
13
+
14
+ // Protocol namespaces live directly on the client: client.sr, client.yield, client.market, ...
15
+ ```
16
+
17
+ `testnet()` contains the verified SR deployment from the 2026-08-27 redeploy, and is the default
18
+ when no `network` is given. The SDK ships Testnet only — use `customNetwork()` to point it at any
19
+ other deployment.
20
+
21
+ ## Amounts
22
+
23
+ Strings and numbers are human token amounts. A `bigint` is already in base units.
24
+
25
+ ```ts
26
+ import { fromBaseUnits, toBaseUnits } from '@spield/sdk';
27
+
28
+ toBaseUnits('10.25'); // 102500000n
29
+ fromBaseUnits(102500000n); // "10.25"
30
+
31
+ const quote = await client.vault.quoteFixedRate({ amount: '100' });
32
+ console.log(quote.payout.formatted, quote.payout.raw);
33
+ ```
34
+
35
+ All protocol tokens use seven decimals. Prices, APYs, and exchange-rate indexes use 12-decimal
36
+ fixed point and are returned as exact `bigint` values plus display-friendly fields where useful.
37
+
38
+ ## PT trustline
39
+
40
+ Only PT requires a trustline. The yield engine itself is the YT token, so creating a YT trustline
41
+ is incorrect.
42
+
43
+ ```ts
44
+ const status = await client.tokens.getPtTrustline({ owner: walletAddress });
45
+ if (!status.ready) await client.tokens.ensurePtTrustline();
46
+ ```
47
+
48
+ Set this up before any flow that delivers PT: buying PT, minting PT+YT, or removing liquidity.
49
+
50
+ ## SR: wrap and unwrap
51
+
52
+ SR represents shares in the Blend-backed strategy. Its USDC value is `shares × exchangeRate / 1e12`.
53
+
54
+ ```ts
55
+ const preview = await client.sr.previewDeposit({ amount: '50' });
56
+ await client.sr.deposit({ amount: '50', minSharesOut: preview.raw * 99n / 100n });
57
+
58
+ const state = await client.sr.getState();
59
+ const balance = await client.sr.getBalance({ owner: walletAddress });
60
+ const value = client.sr.srToUnderlying(balance.raw, state.exchangeRate);
61
+
62
+ await client.sr.redeem({ shares: balance.raw, minUnderlyingOut: 0n });
63
+ ```
64
+
65
+ During a Blend liquidity crunch, query `getState().maxRedeemable` and use `redeemPartial()` so the
66
+ available portion can exit while the rest remains held.
67
+
68
+ `getState()` also reports `depositCap`, `depositHeadroom`, and `depositCapReached`. A reached cap
69
+ blocks new deposits only; it does not block `redeem()` or `redeemPartial()`. The live Testnet cap is
70
+ currently 100 USDC and is below its existing TVL, so `depositCapReached` is expected to be `true`.
71
+
72
+ ## PT and YT engine
73
+
74
+ ```ts
75
+ await client.tokens.ensurePtTrustline();
76
+ await client.yield.mintPy({ srIn: '50' });
77
+
78
+ const interest = await client.yield.getClaimableInterest({ owner: walletAddress });
79
+ await client.yield.claimYield();
80
+
81
+ await client.yield.transferYt({ to: receiver, amount: '5' });
82
+ await client.yield.redeemPy({ amount: '10' });
83
+ ```
84
+
85
+ Before expiry, `redeemPy()` burns both PT and YT. After expiry, it burns PT only and redeems
86
+ principal at face value.
87
+
88
+ ## Buy and sell PT with USDC
89
+
90
+ The router wraps/unwraps SR inside supported routes and applies a 1% output floor by default.
91
+
92
+ ```ts
93
+ await client.tokens.ensurePtTrustline();
94
+
95
+ const buyQuote = await client.router.quoteBuyPt({ usdcIn: '100' });
96
+ await client.router.buyPt({ usdcIn: '100', slippageBps: 100 });
97
+
98
+ const sellQuote = await client.router.quoteSellPt({ ptIn: '25' });
99
+ await client.router.sellPt({ ptIn: '25', slippageBps: 100 });
100
+ ```
101
+
102
+ Pass an explicit `minPtOut` or `minUsdcOut` to replace the quote-derived floor. Zero is valid when
103
+ the caller intentionally disables the floor.
104
+
105
+ ## Buy and sell YT with USDC
106
+
107
+ Use the high-level two-step methods. Against the deployed Blend pool, the combined router YT paths
108
+ exceed Soroban's transaction resource budget even though each leg succeeds separately.
109
+
110
+ ```ts
111
+ await client.buyYtFromUsdc({
112
+ ytOut: '100',
113
+ onProgress: (step, index, total) => console.log(step, `${index}/${total}`),
114
+ });
115
+
116
+ await client.sellYtToUsdc({
117
+ ytIn: '50',
118
+ onProgress: (step, index, total) => console.log(step, `${index}/${total}`),
119
+ });
120
+ ```
121
+
122
+ `buyYtFromUsdc()` checks the wallet's SR balance, wraps only the shortfall, pads `max_sr_in` by 3%
123
+ to keep authorization stable across index drift, and then buys exact YT output. Excess SR is
124
+ refunded by the market. `sellYtToUsdc()` sells YT, reads the actual SR received, and unwraps it.
125
+
126
+ For wallets that already hold SR, use direct market routes:
127
+
128
+ ```ts
129
+ const quote = await client.market.quoteBuyYt({ ytOut: '100' });
130
+ await client.market.buyYt({ ytOut: '100' });
131
+ await client.market.sellYt({ ytIn: '25', minSrOut: 0n });
132
+ ```
133
+
134
+ ## Fixed-rate vault
135
+
136
+ ```ts
137
+ const quote = await client.vault.quoteFixedRate({ amount: '100' });
138
+ const deposit = await client.vault.deposit({ amount: '100' });
139
+
140
+ const stats = await client.vault.getStats();
141
+ const receipts = await client.vault.getOwnerReceipts({ owner: walletAddress });
142
+ const remaining = receipts[0]
143
+ ? await client.vault.getRedeemRemaining({ receiptId: receipts[0].receiptId })
144
+ : null;
145
+
146
+ if (receipts[0]?.open && Date.now() >= receipts[0].maturityDate.getTime()) {
147
+ await client.vault.redeemReceipt({ receiptId: receipts[0].receiptId });
148
+ }
149
+
150
+ await client.vault.harvest();
151
+ ```
152
+
153
+ The vault quote and receipt payout are USDC-denominated. On the latest contract ABI, redemption is
154
+ resumable: a liquidity-constrained call banks progress in `receipt.collected`, while
155
+ `getRedeemRemaining()` reports what is still owed. The SDK derives the same value from old receipts
156
+ when connected to the currently deployed pre-upgrade Testnet vault.
157
+
158
+ ## Market, portfolio, and health
159
+
160
+ ```ts
161
+ const stats = await client.market.getStats();
162
+ const lp = await client.market.getLpPosition({ owner: walletAddress });
163
+
164
+ await client.market.addLiquidity({ ptIn: '10', srIn: '10', minShares: '9.9' });
165
+ await client.market.removeLiquidity({ shares: lp.shares.raw, minPtOut: 0n, minSrOut: 0n });
166
+
167
+ const portfolio = await client.portfolio.getPortfolio({ owner: walletAddress });
168
+ const health = await client.protocol.getHealth();
169
+ const solvency = await client.protocol.getSolvency();
170
+ ```
171
+
172
+ Raw market routes use SR, not USDC. Quote methods return `available: false` when the on-chain quote
173
+ is zero; this means the pool cannot fill the requested size.
174
+
175
+ `minShares` is supported by the latest market ABI. The SDK detects the currently deployed older
176
+ market and uses its three-argument liquidity call when `minShares` is zero. A positive bound on an
177
+ old deployment fails before wallet signing with `SpieldConfigurationError`.
178
+
179
+ ## Contract ABI compatibility
180
+
181
+ The SDK includes the latest source-contract additions: SR/YT/LP/receipt keep-alives, caller-bounded
182
+ LP minting, Blend liquidity state, resumable vault receipt fields, remaining redemption reads, and
183
+ vault surplus reads. As verified on 2026-08-27, the public Testnet SR/market/vault addresses still
184
+ carry the preceding ABI. Read mappings and default liquidity adds remain backward-compatible; new
185
+ write entry points become usable when those contracts are upgraded on chain.
186
+
187
+ ## Custom deployments
188
+
189
+ Supply the full contract set:
190
+
191
+ ```ts
192
+ const network = customNetwork({
193
+ key: 'custom',
194
+ networkName: 'TESTNET',
195
+ networkPassphrase: 'Test SDF Network ; September 2015',
196
+ rpcUrl: 'https://soroban-testnet.stellar.org',
197
+ horizonUrl: 'https://horizon-testnet.stellar.org',
198
+ explorerUrl: 'https://stellar.expert/explorer/testnet',
199
+ decimals: 7,
200
+ contracts: {
201
+ sr: '...', strategy: '...', yieldEngine: '...', market: '...',
202
+ vault: '...', router: '...', pt: '...', usdc: '...',
203
+ },
204
+ assets: { pt: { code: '...', issuer: '...' } },
205
+ });
206
+ ```
207
+
208
+ ## Errors
209
+
210
+ The SDK exports `SpieldValidationError`, `SpieldSignerError`, `SpieldContractError`,
211
+ `SpieldRpcError`, and `SpieldConfigurationError`. Write calls simulate before signing, submit only
212
+ after wallet approval, and return `{ hash, status: 'success', explorerUrl }` after confirmation.
213
+ The SDK polls Testnet RPC for up to two minutes by default because an accepted transaction can be
214
+ indexed after ledger close. Override `confirmationTimeoutMs` and `confirmationPollIntervalMs` in
215
+ `SpieldClient` options when your application needs different confirmation behavior.