# August Digital SDK

TypeScript SDK for interacting with August Digital vaults across EVM, Solana, Stellar, and Sui chains.

## Installation

```bash
npm install @augustdigital/sdk ethers
# or
pnpm add @augustdigital/sdk ethers
# or
yarn add @augustdigital/sdk ethers
```

### Wagmi/Viem Support

The SDK supports both ethers and wagmi/viem signers. If you're using wagmi in your React app, also install viem:

```bash
npm install viem
```

The SDK automatically converts viem `WalletClient` to an ethers-compatible signer.

## Quick Start

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

const sdk = new AugustSDK({
  // Required: stable kebab-case slug identifying your application.
  appName: 'acme-trader',
  providers: {
    1: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
    42161: 'https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY',
    -1: 'https://api.mainnet-beta.solana.com', // Solana
  },
  keys: {
    august: 'YOUR_API_KEY', // Optional: vault reads, allocations and vault-linked sub-account reads work without it
  },
  monitoring: {
    env: 'DEV', // Optional: 'DEV' enables console logging (defaults to 'PROD')
  },
});

// Fetch all vaults
const vaults = await sdk.getVaults();

// Fetch a specific vault with loans and allocations
const vault = await sdk.getVault({
  vault: '0x...',
  options: { loans: true, allocations: true },
});

// Get user positions
const positions = await sdk.getVaultPositions({
  wallet: '0x...',
  showAllVaults: true,
});
```

### App Name

`appName` is required on every `AugustSDK` constructor call. Pass a stable kebab-case slug
identifying your application (e.g. `'acme-trader'`, `'my-defi-app'`):

- **What it's used for.** August Digital tags analytics events with `app.name = <yourSlug>` to
  attribute error spikes and prioritize bug fixes by consuming app.
- **What it is not.** Not a secret, not a license key, not a display label. Pick a slug once
  and reuse it across deployments.
- **Constraints.** 3–64 characters, only `[a-zA-Z0-9._-]`. The SDK throws synchronously from
  the constructor if the value is missing or invalid.

```typescript
// Will throw — appName is required.
new AugustSDK({ providers: { /* ... */ } } as any);

// Will throw — spaces not allowed. Use 'acme-trader'.
new AugustSDK({ appName: 'Acme Trader', providers: { /* ... */ } });

// Correct.
new AugustSDK({ appName: 'acme-trader', providers: { /* ... */ } });
```

### Origin-Code Attribution (SwapRouter)

An integrator with an origin (referral / fee-sharing) code registered on the SwapRouter sets it once on the
SDK instance. Deposits made through the SDK then carry it on-chain:

```typescript
const sdk = new AugustSDK({
  appName: 'acme-trader',
  providers: { 1: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY' },
  attribution: {
    originCode: '0x…', // bytes32 registered on the SwapRouter with addOrigin
    strictOrigin: false, // optional: true throws instead of falling back
  },
});

// Check the code before you ship it.
const { registered, originFeeBps, collector } = await sdk.getOriginInfo({
  chainId: 1,
  originCode: '0x…',
});
```

- **Ethereum mainnet only.** The instance code is sent on chain 1 (`ORIGIN_ATTRIBUTION_CHAINS`) and on no other
  chain, even where a SwapRouter is configured.
- **Where it applies.** `vaultDeposit` sends a deposit through `SwapRouter.deposit` with the code when
  `sdk.evm.getDepositRoute()` returns `via: 'router'`: the vault is registered on the router, the vault accepts
  the asset as-is (its reference asset, or a Tokenized Vault V2 whitelisted asset — never swapped), the router
  whitelists the asset, the router is not paused, the deposit is not a permit, native-token or adapter deposit,
  and the code is registered. `swapRouterDeposit`, `swapAndDeposit`, `depositViaSwapRouter` and `depositNativeViaSwapRouter`
  send it too.
- **Precedence.** An `originCode` passed to a router method wins, then the instance code, then the all-zero
  sentinel (no origin fee). Pass the zero sentinel to opt one call out.
- **Fallback.** When the code cannot be applied — it is not registered, the chain is not mainnet, the router
  is paused (`router-paused`, checked with a 15-second cache) or does not whitelist the asset, the vault does
  not accept it or is not registered on the router, or it is a permit, native-token or adapter deposit —
  `vaultDeposit` takes the vault's native path and logs
  `vaultDeposit:attribution-skipped` with the reason (Sentry tag `sdk.attribution.origin=skipped`). The router
  methods send the zero sentinel for an unregistered instance code and log `<method>:attribution-skipped`.
  With `strictOrigin: true` an unregistered instance code throws `AugustValidationError` before any approval
  instead; a paused router falls back to native even then. An `originCode` passed to a router method that is
  not registered always throws `AugustValidationError` before any approval. While the router is paused, the
  router methods throw `AugustSDKError` (`context.reason: 'router-paused'`) before any approval.
- **Approve `route.spender`, not the vault.** A routed deposit is pulled by the SwapRouter. `vaultDeposit` and
  `approve` handle this; if you send approvals yourself, read the spender first:

  ```typescript
  const route = await sdk.evm.getDepositRoute({ target: vault, depositAsset: usdc });
  // route.spender is the SwapRouter when route.via === 'router', else the vault (or adapter wrapper)
  ```

- **Gas.** Measured on a mainnet fork (repeat 1,000 USDC deposits into Core USDC): direct deposit 102,067 gas;
  router with a 0% code 161,744 (+59.7k); router with a 1% code 173,135 (+71.1k). A routed deposit also needs a
  one-time approval of the router per token.
- **Zero-fee codes.** A code registered with a 0 bps fee is valid for attribution only, but the router emits no
  `OriginFeeApplied` event for it. Its deposits are attributable only from the transaction calldata and SDK
  telemetry.
- **Process-global.** `attribution` is process-global: the last `AugustSDK` constructed wins, and one
  constructed without `attribution` clears it. Run one attribution configuration per process.

## Architecture

```
src.ts/
├── main.ts              # Main SDK class (AugustSDK)
├── core/                # Base utilities
│   ├── base.class.ts    # Base SDK functionality
│   ├── fetcher.ts       # API client with retry logic
│   └── web3.helpers.ts  # Blockchain utilities
├── adapters/            # Chain-specific implementations
│   ├── evm/             # EVM adapter (approve, deposit, redeem, read helpers)
│   ├── solana/          # Solana program adapters
│   ├── stellar/         # Stellar vault adapters
│   └── sui/             # Sui (Ember) vault adapters
├── evm/                 # EVM cross-chain (LayerZero OVault)
├── modules/             # Feature modules
│   ├── vaults/          # Vault read operations
│   ├── sub-accounts/    # Sub-account queries
│   └── api/             # August backend API integration
├── services/            # External service integrations
│   ├── debank/          # DeFi allocation data
│   ├── coingecko/       # Token pricing
│   └── subgraph/        # Historical transaction data
└── types/               # TypeScript interfaces
```

## Key Concepts

### Multi-Chain Support

- **EVM Chains**: Ethereum, Arbitrum, Base, BSC, Avalanche, and more — `wagmi/viem` and `ethers` signers supported
- **Solana**: Native Solana program support with full vault functionality
- **Stellar**: Stellar vault deposit, redeem, and position queries
- **Sui**: Ember vault read operations
- Unified interface across all chains

### Vault Versions

- `evm-0/evm-1`: Legacy vault contracts
- `evm-2`: Current EVM vault architecture (separate receipt tokens)
- `sol-0`: Solana program-based vaults
- `stellar-0`: Stellar-based vaults

### Data Enrichment

All vault queries support optional enrichment:

- `loans`: Include active loan data
- `allocations`: DeFi/CeFi/OTC position breakdowns
- `wallet`: User-specific position data

## API Reference

### Vault Queries

| Method | Description |
| --- | --- |
| `getVaults(options?)` | Fetch all vaults across chains |
| `getVault({ vault, chainId?, options? })` | Get single vault details |
| `getVaultLoans({ vault, chainId? })` | Fetch active loans |
| `getVaultAllocations({ vault, chainId? })` | Get allocation breakdown |
| `getVaultHistoricalTimeseries({ vault, chainId? })` | Historical APY and TVL timeseries |
| `getVaultApy({ vault, historical? })` | **@deprecated** — use `getVaultHistoricalTimeseries` |
| `getVaultTvl({ vault, historical? })` | Current/historical TVL |
| `getVaultAnnualizedApy({ vault })` | Annualized APY for a vault |
| `getVaultSummary({ vault })` | Aggregated vault summary |
| `getVaultPnl({ vault, chainId? })` | Vault-level PnL |
| `getVaultUnrealizedPnlHistory({ vault, chainId?, ... })` | Unrealized PnL timeseries |
| `getLatestUnrealizedPnl()` | Latest unrealized PnL snapshot across all vaults |
| `getYieldLastRealizedOn({ vault, chainId? })` | Timestamp of last yield realization |
| `getTotalDeposited(options?)` | Total deposited across vaults |

### User Positions

| Method | Description |
| --- | --- |
| `getVaultPositions({ wallet?, vault?, chainId? })` | User vault positions |
| `getVaultAvailableRedemptions({ vault, wallet?, chainId, verbose? })` | Claimable redemptions |
| `getVaultRedemptionHistory({ vault, wallet?, chainId? })` | Historical redemptions |
| `getVaultUserHistory({ wallet, vault?, chainId? })` | Transaction history |
| `getVaultUserTransfers({ wallet, vault?, chainId? })` | Transfer history |
| `getVaultUserLifetimePnl({ wallet, vault?, chainId? })` | Lifetime PnL for a wallet |
| `getVaultStakingPositions({ wallet, chainId })` | Staking positions |
| `getVaultBorrowerHealthFactor(props?)` | Borrower health factor |
| `getVaultWithdrawals({ vault, chainId? })` | Pending withdrawals |

### Points

| Method | Description |
| --- | --- |
| `getUserPoints(userAddress)` | Points balance for a wallet |
| `registerUserForPoints({ wallet, referral? })` | Register a wallet for the points program |
| `fetchPointsLeaderboard(params?)` | Points leaderboard |

### Write Operations (top-level)

| Method | Description |
| --- | --- |
| `vaultDeposit(signer, options)` | Deposit into a vault |
| `previewRedemption(props)` | Preview redemption output before broadcasting |

### Cross-Chain (LayerZero)

| Method | Description |
| --- | --- |
| `getLayerZeroDeposits({ wallet?, chainId? })` | LayerZero deposit history |
| `getLayerZeroRedeems(props?)` | LayerZero redemption history |

### Utilities

| Method | Description |
| --- | --- |
| `getPrice(symbol)` | Token price in USD |
| `switchNetwork(chainId)` | Change active chain |
| `updateWallet(address)` | Set active wallet for tracking |

### Sub-Accounts (`sdk.subAccountsModule`)

| Method | Description |
| --- | --- |
| `getSubaccountHealthFactor(address)` | Health factor for a sub-account |
| `getSubaccountLoans(address)` | Active loans for a sub-account |
| `getSubaccountCefiPositions(address)` | CeFi positions |
| `getSubaccountOtcPositions(address)` | OTC positions |
| `getSubaccountSummary(address)` | Aggregated sub-account summary |

### EVM Adapter (`sdk.evm`)

Set a signer before calling write methods:

```typescript
// ethers
import { JsonRpcProvider, Wallet } from 'ethers';
const wallet = new Wallet(process.env.PRIVATE_KEY!, new JsonRpcProvider(rpcUrl));
sdk.evm.setSigner(wallet);

// wagmi/viem (browser)
import { useWalletClient } from 'wagmi';
const { data: walletClient } = useWalletClient();
if (walletClient) sdk.evm.setSigner(walletClient);
```

**Write methods:**

| Method | Description |
| --- | --- |
| `vaultApprove(options)` | Approve token spend for a vault |
| `approve(options)` | Generic ERC-20 approve |
| `vaultDeposit(options)` | Deposit assets into a vault |
| `vaultRequestRedeem(options)` | Request a withdrawal/redemption |
| `vaultRedeem(signer, options)` | Claim an available redemption |
| `depositNative(options)` | Deposit native tokens (ETH, etc.) |
| `swapAndDeposit(options)` | Swap and deposit in one call |
| `depositViaSwapRouter(options)` | Deposit via the swap router |
| `depositNativeViaSwapRouter(options)` | Deposit native via the swap router |

**Read helpers (return raw `bigint`):**

| Method | Description |
| --- | --- |
| `previewDeposit(options)` | Shares minted for a deposit amount |
| `previewRedeem(options)` | Assets returned for a share amount |
| `allowance(options)` | ERC-20 allowance granted to the vault |
| `balanceOf(options)` | ERC-20 balance for any token/owner |
| `maxDeposit(options)` | Maximum deposit accepted by the vault |
| `vaultAllowance(options)` | Vault-specific allowance check |
| `getDeposited(options)` | Current deposited balance |
| `getRemainingAllocations(options)` | Remaining allocation capacity |
| `isWhitelisted(options)` | Check whitelist status |

### Solana Adapter (`sdk.solana`)

| Method | Description |
| --- | --- |
| `getVaultState(...)` | Vault state from the Solana program |
| `getVaultStateReadOnly(...)` | Read-only vault state |
| `getToken(mintAddress)` | SPL token info |
| `getTokenSymbol(mintAddress)` | Token symbol |
| `fetchUserTokenBalance(publicKey, mint)` | User SPL token balance |
| `fetchUserShareBalance(publicKey, vault)` | User share balance |
| `fetchUserShareBalanceRaw(publicKey, vault)` | Raw share balance (bigint) |

### Stellar Adapter (`sdk.stellar`)

| Method | Description |
| --- | --- |
| `vaultDeposit(options)` | Deposit into a Stellar vault |
| `vaultRedeem(options)` | Redeem from a Stellar vault |
| `submitTransaction(signedXdr)` | Submit a signed XDR transaction |
| `getUserPosition(options)` | User position in a Stellar vault |
| `convertToShares(options)` | Convert an asset amount to shares |

### Sui Adapter (`sdk.sui`)

| Method | Description |
| --- | --- |
| `getEmberVaults()` | List all Ember (Sui) vaults |
| `getEmberTVL(limit?)` | Total value locked across Ember vaults |

## Code Conventions

### Naming Patterns

- **Interfaces**: Prefixed with `I` (e.g., `IVault`, `IVaultLoan`)
- **ABIs**: Prefixed with `ABI_` (e.g., `ABI_LENDING_POOL_V2`)
- **Types**: Descriptive names (e.g., `IAddress`, `IChainId`)

### Special Comment Tags

Search the codebase for these to find important areas:

- `@todo`: Planned improvements or missing features
- `@hardcoded`: Hardcoded values that may need configuration
- `@solana`: Solana-specific logic or notes

### Error Handling

Every public method throws typed errors that subclass `AugustSDKError`:

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

try {
  await sdk.evm.vaultDeposit({ target: '0x...', wallet: owner, amount: '1' });
} catch (err) {
  if (err instanceof AugustValidationError) showFormError(err.message);
  else if (err instanceof AugustTimeoutError) scheduleRetry(err.timeoutMs);
  else if (err instanceof AugustSDKError) reportError(err.code, err.cause);
  else throw err;
}
```

- Automatic retry with exponential backoff for network errors
- 90-second request timeout (configurable via `REQUEST_TIMEOUT_MS`)
- Correlation IDs in errors for debugging

### Environment Configuration

```typescript
// Development mode — enables console logging
const sdk = new AugustSDK({ appName: 'my-app', providers: { /* ... */ }, monitoring: { env: 'DEV' } });

// Production mode — default, no console logs
const sdk = new AugustSDK({ appName: 'my-app', providers: { /* ... */ } });
```

## Development

### Running Tests

```bash
pnpm test
```

### Building

```bash
pnpm build
```

## Support

- **Issues**: [GitHub Issues](https://github.com/fractal-protocol/js-sdk/issues)
- **Documentation**: [docs.augustdigital.io](https://docs.augustdigital.io/developers/javascript-sdk)
- **Example App**: [starter.upshift.finance](https://starter.upshift.finance/)
