> For the complete documentation index, see [llms.txt](https://docs.augustdigital.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.augustdigital.io/developers/typescript-sdk/code-examples/stellar-actions.md).

# Stellar Actions

Deposit into and redeem from Stellar (Soroban) vaults such as Gami earnUSDC / earnXLM. Write operations build an unsigned transaction that you sign with the user's wallet and submit back through the S

### Overview

Stellar vault interactions live on the **Stellar adapter** (`sdk.stellar`), not on the higher-level vaults module (which is read-only for Stellar). Unlike EVM — where a deposit is signed and broadcast in one call — Stellar write methods follow a **build → sign → submit** flow:

1. `sdk.stellar.vaultDeposit()` / `sdk.stellar.vaultRedeem()` build an **unsigned transaction** and return it as a base64-encoded **XDR** string.
2. You sign that XDR with the user's Stellar wallet (e.g. [Freighter](https://www.freighter.app/)).
3. `sdk.stellar.submitTransaction()` broadcasts the signed XDR and resolves to the transaction hash once the network confirms it.

The SDK does **not** hardcode vault names like `earnUSDC` or `earnXLM`. You pass a vault's **contract ID** (the `C…` address); resolve it from the vault listing (see [Get Vault Data](#get-vault-data)).

{% hint style="info" %}
`sdk.stellar` runs against Stellar **mainnet**. For testnet, see [Networks](#networks) at the bottom of this page.
{% endhint %}

### Setup

`sdk.stellar` is always available — no extra provider configuration is required, since Stellar vault metadata is served from the August backend.

```typescript
import AugustSDK from '@augustdigital/sdk';

const sdk = new AugustSDK({
  appName: 'your-app-name', // required: stable kebab-case slug identifying your app
});

// Access the Stellar adapter
const stellar = sdk.stellar;
```

{% hint style="info" %}
`AugustSDK` is the package's **default** export — import it as `import AugustSDK from '@augustdigital/sdk'` (not a named import). `appName` is required. No `providers` entry is needed for Stellar, since its vault data is served from the August backend; add `providers` only if you also read EVM/Solana vaults.
{% endhint %}

Signing happens in your app via the user's wallet — there is no `setWalletProvider` step for Stellar. Whatever wallet you use must expose a "sign XDR" call (Freighter's `signTransaction`, for example).

### Adapter API

#### Methods

| Method                                                 | Returns                                 | Description                                   |
| ------------------------------------------------------ | --------------------------------------- | --------------------------------------------- |
| `vaultDeposit({ contractId, amount, senderAddress })`  | `Promise<string>` (XDR)                 | Build an unsigned deposit transaction.        |
| `vaultRedeem({ contractId, shares, receiverAddress })` | `Promise<string>` (XDR)                 | Build an unsigned redeem transaction.         |
| `submitTransaction(signedXdr)`                         | `Promise<string>` (tx hash)             | Submit a signed XDR and poll until confirmed. |
| `getUserPosition(vaultAddress, walletAddress)`         | `Promise<IStellarUserPosition \| null>` | Read the user's on-chain share balance.       |
| `convertToShares(vaultAddress, rawAmount)`             | `Promise<string \| null>`               | Preview shares a deposit amount would yield.  |
| `isStellarAddress(address)`                            | `boolean`                               | Validate a Stellar address (`C…` or `G…`).    |
| `getExplorerLink(id, type?)`                           | `string`                                | Build a Stellar explorer URL.                 |

### Get Vault Data

Stellar vaults appear in the standard listing with `chain_type === 'stellar'`. Use a vault's `address` as the `contractId` for deposit/redeem.

```typescript
// List all vaults, then narrow to Stellar
const vaults = await sdk.getVaults();
const stellarVaults = vaults.filter((v) => v.chain_type === 'stellar');

// e.g. find "earnUSDC" by name/symbol
const earnUsdc = stellarVaults.find((v) => v.name?.includes('earnUSDC'));
const contractId = earnUsdc?.address; // "C…"
```

#### Get User Positions

To include a user's Stellar balances in the unified positions call, pass `stellarWallet` (the user's `G…` address):

```typescript
const positions = await sdk.getVaultPositions({
  stellarWallet: 'G...',
});

positions.forEach((position) => {
  console.log(`Vault: ${position.vault}`);
  console.log(`Balance: ${position.walletBalance.normalized}`);
});
```

For a direct on-chain read of a single vault, use the adapter (this is also how you obtain the `shares` value needed to redeem):

```typescript
const position = await sdk.stellar.getUserPosition(contractId, 'G...');
// { shares: string, decimals: number, decimalsFromFallback?: boolean } | null
```

{% hint style="warning" %}
**Sizing a redeem from `getUserPosition`:** if `position.decimalsFromFallback === true`, the on-chain `decimals()` read failed and `decimals` is a fabricated fallback (`7`). **Do not trust it to size a redeem** — against an offset vault (share decimals = asset + offset) this under-redeems by `10^offset`. Refuse and retry instead.
{% endhint %}

### Vault Deposit

Build an unsigned deposit transaction. This is a **self-deposit**: internally the receiver, from, and operator roles are all set to `senderAddress`.

```typescript
sdk.stellar.vaultDeposit({
  contractId: string;     // Stellar vault contract ("C…")
  amount: string;         // deposit amount in the token's smallest unit
  senderAddress: string;  // user's Stellar account ("G…")
}): Promise<string>       // → unsigned base64 XDR
```

#### Parameters

| Parameter       | Type     | Required | Description                                                                      |
| --------------- | -------- | -------- | -------------------------------------------------------------------------------- |
| `contractId`    | `string` | Yes      | Stellar vault contract address (`C…`).                                           |
| `amount`        | `string` | Yes      | Amount in the deposit token's smallest unit (e.g. 1 USDC @ 7 dp → `"10000000"`). |
| `senderAddress` | `string` | Yes      | User's Stellar account (`G…`); pays for and receives the deposit.                |

#### Example

```typescript
// 1. Build the unsigned transaction
const unsignedXdr = await sdk.stellar.vaultDeposit({
  contractId,
  amount: '10000000', // 1 USDC at 7 decimals
  senderAddress: 'G...',
});

// 2. Sign with the user's wallet (Freighter shown here)
import { signTransaction } from '@stellar/freighter-api';
const { signedTxXdr } = await signTransaction(unsignedXdr, { networkPassphrase: '...' });

// 3. Submit and wait for confirmation
const txHash = await sdk.stellar.submitTransaction(signedTxXdr);
console.log('Deposit confirmed:', txHash);
```

Optionally preview the shares a deposit would mint before building it:

```typescript
const expectedShares = await sdk.stellar.convertToShares(contractId, '10000000');
// string | null
```

### Vault Redeem

Build an unsigned redeem transaction. Redeem burns **shares** (not asset amount), so read the user's position first. This is a **self-redeem**: receiver, owner, and operator roles are all set to `receiverAddress`.

```typescript
sdk.stellar.vaultRedeem({
  contractId: string;       // Stellar vault contract ("C…")
  shares: string;           // amount of shares to redeem, in smallest unit
  receiverAddress: string;  // user's Stellar account ("G…")
}): Promise<string>         // → unsigned base64 XDR
```

#### Parameters

| Parameter         | Type     | Required | Description                                                  |
| ----------------- | -------- | -------- | ------------------------------------------------------------ |
| `contractId`      | `string` | Yes      | Stellar vault contract address (`C…`).                       |
| `shares`          | `string` | Yes      | Shares to redeem, in the share token's smallest unit.        |
| `receiverAddress` | `string` | Yes      | User's Stellar account (`G…`); receives the redeemed assets. |

#### Example

```typescript
// 1. Read the user's share balance
const position = await sdk.stellar.getUserPosition(contractId, 'G...');
if (!position || position.shares === '0') {
  throw new Error('No balance to redeem');
}
if (position.decimalsFromFallback) {
  throw new Error('Decimals unresolved — refusing to size redeem with fallback decimals');
}

// 2. Build the unsigned transaction (redeem all)
const unsignedXdr = await sdk.stellar.vaultRedeem({
  contractId,
  shares: position.shares,
  receiverAddress: 'G...',
});

// 3. Sign with the user's wallet, then submit
const { signedTxXdr } = await signTransaction(unsignedXdr, { networkPassphrase: '...' });
const txHash = await sdk.stellar.submitTransaction(signedTxXdr);
console.log('Redeem confirmed:', txHash);
```

### Submit a Signed Transaction

`submitTransaction` broadcasts a signed XDR and polls until the network confirms it as successful, returning the transaction hash. The XDR must be signed for the same network the adapter is using.

```typescript
const txHash = await sdk.stellar.submitTransaction(signedTxXdr);
```

### Error Handling

`submitTransaction` throws typed [August SDK errors](/developers/typescript-sdk/errors.md):

* **`AugustTimeoutError`** — the transaction was not confirmed within the poll budget.
* **`AugustSDKError`** — the RPC rejected the submission, or the transaction confirmed as failed. Inspect `err.context` for `{ network, status }` and, when the result XDR decodes, a `resultCode` string carrying the transaction-level reason (e.g. `"txBadSeq"`, `"txTooLate"`). `resultCode` is `undefined` when it cannot be decoded.

```typescript
import { AugustSDKError } from '@augustdigital/sdk';

try {
  const txHash = await sdk.stellar.submitTransaction(signedTxXdr);
} catch (err) {
  if (err instanceof AugustSDKError && err.context?.resultCode === 'txBadSeq') {
    // Stale sequence number — rebuild the transaction and resubmit.
  } else {
    throw err;
  }
}
```

### Curator Notifications

Stellar vaults are **instant-redeem only** — there is no withdrawal queue — so a curator whose depositors cannot redeem has no other way to find out. When a redemption fails, the SDK reports it to the August API (the same base URL it reads vault data from), which validates the report and forwards it to the vault curator's own Telegram group. Curators are onboarded per vault, so a vault nobody has opted in is never reported on.

Two cases are reported:

1. the vault rejecting the redeem at build time — either a Soroban simulation failure or a vault whose ledger state has been archived and needs restoring; and
2. a submitted redeem whose operation ran and failed (`resultCode === 'txFailed'`).

The body sent is exactly: an event name, the chain family (`"stellar"`), which of the two cases it was, the network, the vault contract ID, the redeeming account, the share amount (unscaled), an ISO timestamp, the failure reason (secrets scrubbed, capped at 1500 characters — on the submission path this is the decoded transaction result, not prose), your `appName`, and for case 2 the transaction hash and result code. Repeated attempts at the same failure on the same vault and account collapse to one report per 10 minutes.

Deliberately **not** reported, because none of them is a vault rejecting a redemption: an invalid address, an unfunded Stellar account, an RPC outage, a broadcast the RPC rejected, a confirmation timeout, and any transaction-level failure where the redeem never executed (`txBadSeq`, `txInsufficientBalance`, `txTooLate`, or an undecodable result code). Deposits are never reported. Note that a redeem which exhausts its Soroban resource budget *is* reported — the operation ran, so it arrives as `txFailed`.

Nothing is sent for networks other than mainnet — unless you set `curatorAlerts.endpoint`, which lifts that restriction so your own relay can receive testnet failures. Nothing is sent when `monitoring.env` is set to anything other than `PROD`, when `NODE_ENV` is `development` or `test`, or when the page is served from a loopback, private, or link-local host — so your CI and local dev runs stay silent. That last check covers:

| Kind            | Covered                                                                                               |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| Reserved names  | `localhost`, any `*.localhost`, any `*.local`                                                         |
| IPv4 loopback   | `127.0.0.0/8` (not just `127.0.0.1`), `0.0.0.0/8`                                                     |
| IPv4 private    | `10.0.0.0/8`, `172.16.0.0/12` (Docker's default bridge lives here), `192.168.0.0/16`                  |
| IPv4 link-local | `169.254.0.0/16`                                                                                      |
| IPv6            | `[::1]` and `::`, unique-local `fc00::/7`, link-local `fe80::/10`, and IPv4-mapped forms of the above |

A public host that merely contains a reserved name — `localhost.example.com` — is treated as a real deployment and does report.

The hostname check is what covers browsers: bundlers strip `process.env`, so `NODE_ENV` is not readable there and cannot be relied on to keep a `next dev` tab quiet.

Note that `monitoring.env` **defaults to `PROD`**, so reporting is on unless you say otherwise; and code that drives the `Stellar` namespace without constructing an `AugustSDK` has no `monitoring.env` at all, where `NODE_ENV` and the hostname are the only gates.

{% hint style="warning" %}
Setting `curatorAlerts.enabled: true` **bypasses all three environment gates** — `monitoring.env`, `NODE_ENV`, and the local-host check. It does *not* lift the mainnet-only restriction; only `endpoint` does that. It is a force-on, not a "yes, use the defaults": a `DEV` or test integration that sets it and then fails a mainnet redeem will page a real curator. Leave it unset to get the safe defaults, and if you set it to exercise the path, point `endpoint` at a relay of your own. The `AUGUST_SDK_DISABLE_CURATOR_ALERTS` env var still overrides it.
{% endhint %}

Enabled by default. To turn it off:

```typescript
const sdk = new AugustSDK({
  appName: 'your-app-name',
  keys: { august: 'your-api-key' },
  monitoring: { curatorAlerts: { enabled: false } },
});
```

In Node you can also set `AUGUST_SDK_DISABLE_CURATOR_ALERTS` (to `1`, `true`, `yes`, or `on`). If you call the `Stellar` namespace directly rather than constructing an `AugustSDK` (see [Networks](#networks) below), opt out with the top-level `configureCuratorAlerts` — in a browser this is the only way, since environment variables are unreadable there:

```typescript
import { configureCuratorAlerts } from '@augustdigital/sdk';

configureCuratorAlerts({ enabled: false });
```

{% hint style="warning" %}
Constructing an `AugustSDK` afterwards **resets** this setting, so call it after any SDK construction, not before.
{% endhint %}

{% hint style="info" %}
The reporting endpoint is chain-agnostic — the chain travels in the payload — but it only serves the families August has enabled, which today means Stellar alone. Curators are onboarded on August's side; there is nothing to configure in the SDK to route notifications to a particular channel. Ask your August contact to enable a vault, and to allowlist the origin your users load the app from: delivery is a cross-origin browser request, so an origin that is not on August's CORS allowlist loses these reports at the preflight. The `redeem` check on the submit path reads the transaction's contract call rather than a list of August vaults, so submitting an unrelated Soroban contract's failed `redeem` through `submitTransaction` also sends a report; the API discards any vault it does not have an opted-in curator for.
{% endhint %}

### Networks

`sdk.stellar` is hardwired to **mainnet**. To target **testnet**, use the `Stellar` namespace directly with an explicit `network`:

```typescript
import { Stellar } from '@augustdigital/sdk';

// Build an unsigned deposit on testnet
const unsignedXdr = await Stellar.actions.handleStellarDeposit({
  contractId,
  amount: '10000000',
  senderAddress: 'G...',
  network: 'testnet',
});

// Submit on testnet
const txHash = await Stellar.submit.submitStellarTransaction(signedTxXdr, 'testnet');
```

### Notes & Caveats

* **Self-deposit / self-redeem only.** All role addresses are set to the single account you pass; there is no third-party/operator variant.
* **Standard vault ABI assumed.** Redeem assumes the deployed vault exposes `redeem(shares, receiver, owner, operator)`. A divergent ABI surfaces as a generic Soroban simulation error.
* **Read-only on the vaults module.** Available redemptions and redemption history are **not yet supported** for Stellar vaults; deposit/redeem are available only on `sdk.stellar`.
