# arcane-privacy-sdk

TypeScript SDK for interacting with the Arcane privacy protocol on Solana — deposits, withdrawals, private transfers, and fee quotes.

## Installation

```bash
npm install arcane-privacy-sdk
```

Peer dependencies (optional, for React):

```bash
npm install @solana/wallet-adapter-react react
```

## Prerequisites

- A Solana wallet (`Keypair` for backend/scripts, or a wallet adapter with `signMessage` / `signAllTransactions` for frontend)
- A [Range API key](https://range.org) (`rangeApiKey`)
- An RPC endpoint or existing `@solana/web3.js` `Connection`

Supported networks: `mainnet` and `devnet` (inferred from the RPC URL, or set explicitly via `network`).

---

## Creating an `ArcaneSDK` instance

Create one SDK instance per configuration (network, RPC, wallet). Initialization loads active relayers for the target network.

### Node / backend

```ts
import { Keypair } from "@solana/web3.js";
import { ArcaneSDK } from "arcane-privacy-sdk";

const wallet = Keypair.generate(); // or your existing keypair

const sdk = await ArcaneSDK.create({
  rangeApiKey: process.env.RANGE_API_KEY!,
  rpcEndpoint: "https://api.devnet.solana.com",
  wallet,
});
```

### React (wallet-adapter)

Wrap your app with `ConnectionProvider` and `WalletProvider`, then use the React hook:

```tsx
import { useArcaneSdk } from "arcane-privacy-sdk/react";

function MyComponent() {
  const sdk = useArcaneSdk({
    rangeApiKey: import.meta.env.VITE_RANGE_API_KEY,
  });

  if (!sdk) return null; // wallet not connected yet

  // use sdk...
}
```

### Init options

| Option | Required | Description |
|--------|----------|-------------|
| `rangeApiKey` | yes | Range API key for risk/oracle features |
| `wallet` | yes | Anchor-compatible wallet (`Keypair` or wallet adapter) |
| `rpcEndpoint` | one of | Solana JSON-RPC URL (Node/scripts) |
| `connection` | one of | Existing `Connection` (e.g. from `useConnection()`) |
| `network` | no | `"mainnet"` or `"devnet"` — override when the RPC URL has no cluster hint |

After creation, inspect runtime config via `sdk.config` or helpers like `sdk.getNetwork()`, `sdk.relayers`, and `sdk.getTokenMints()`.

---

## `getEncryptionKey`

Derives a note-encryption key from a wallet signature and stores it on the SDK instance (`sdk.encryptKey`). **Required before any deposit that uses on-chain note backup** (`onChainBackup: true`, including `arcaneTransfer`).

The wallet signs the message `arcane:notes:v1`. Accepts a backend `Keypair` or a frontend wallet adapter with `signMessage`.

```ts
await sdk.getEncryptionKey(wallet);
// sdk.encryptKey is now set
```

The key is used to encrypt secret notes before they are stored on-chain via `backupNotes`, so you can recover notes later from the same wallet.

---

## `buildDepositTx`

Builds an unsigned deposit transaction for a pre-generated secret note.

### 1. Generate deposit parameters

```ts
// 1 SOL deposit (lamports)
const { note } = sdk.generateDepositParams(1_000_000_000n, null);

// SPL token deposit — pass the token mint
const usdcMint = sdk.getTokenMint("USDC");
const { note: usdcNote } = sdk.generateDepositParams(1_000_000n, usdcMint);
```

Deposit amounts must match [supported pool denominations](#supported-deposit-denominations) for each token.

### 2. Build the transaction

```ts
await sdk.getEncryptionKey(wallet); // required when onChainBackup is true

const { tx, note } = await sdk.buildDepositTx({
  senderPublicKey: wallet.publicKey,
  note,
  onChainBackup: true,       // encrypt and store note on-chain (recommended)
  computeUnitPrice: 50_000,  // optional priority fee
});
```

Sign and send the returned `tx` with your wallet. **Save the returned `note`** — it is required to withdraw later.

| Parameter | Description |
|-----------|-------------|
| `senderPublicKey` | Depositor's public key |
| `note` | Secret note from `generateDepositParams` |
| `onChainBackup` | When `true`, encrypts the note and adds a `backupNotes` instruction (requires `getEncryptionKey`) |
| `computeUnitPrice` | Priority fee in micro-lamports (default `50000`) |

---

## `withdraw`

Withdraws funds from the privacy pool to a recipient using a secret note from a prior deposit. The SDK:

1. Validates the recipient against the risk API
2. Fetches Merkle commitments from the indexer
3. Generates a ZK withdraw proof
4. Submits the proof to a relayer (chosen at random, or by index)
5. Polls until the relayer confirms the on-chain transaction

```ts
const result = await sdk.withdraw({
  note: "arcane-1-...",           // secret note from deposit
  recipient: "RecipientPubkey...", // withdrawal destination
  relayerIdx: 0,                   // optional — random relayer when omitted
});

console.log(result.txHash);  // on-chain transaction signature
console.log(result.status);  // "success" | "failed"
```

| Parameter | Description |
|-----------|-------------|
| `note` | Secret note from the original deposit |
| `recipient` | Recipient public key (`PublicKey` or base58 string) |
| `relayerIdx` | Optional index into `sdk.relayers` |

Withdraw fees are calculated automatically from the relayer fee and on-chain platform fee.

---

## `arcaneTransfer`

End-to-end private transfer: deposit into the pool (split across supported denominations), then withdraw each successful note to the recipient. Handles signing, sending, and settlement automatically.

**Requires `getEncryptionKey` before calling** — all deposits use on-chain backup.

```ts
import { PublicKey } from "@solana/web3.js";

await sdk.getEncryptionKey(wallet);

const result = await sdk.arcaneTransfer(
  [
    {
      mint: new PublicKey(sdk.getTokenMint("USDC")),
      amount: "25.5",                              // human-readable amount
      recipient: new PublicKey("RecipientPubkey..."),
    },
    {
      mint: new PublicKey(sdk.getTokenMints().Solana),
      amount: "1.5",
      recipient: new PublicKey("AnotherRecipient..."),
    },
  ],
  { uniqueRelayer: true } // optional — use one relayer for all withdraws
);

if (result.error) {
  console.log("Deposit failures:", result.depositFailed);
  console.log("Withdraw failures:", result.withdrawFailed);
}
```

| Field | Description |
|-------|-------------|
| `items[].mint` | Token mint to transfer |
| `items[].amount` | Human-readable amount (e.g. `"1.5"` SOL) |
| `items[].recipient` | Withdrawal destination for that transfer |
| `options.uniqueRelayer` | When `true`, all withdraws share one randomly selected relayer |

The SDK splits each amount into the nearest supported deposit denominations, builds and sends deposit transactions, waits for settlement, then withdraws each note to its recipient. Partial failures are reported in `depositFailed` and `withdrawFailed` without throwing.

---

## `getQuote`

Fetches a withdraw fee quote from the Arcane indexer before executing a withdrawal. Returns estimated relayer fees, recipient amount, ETA, and how the amount splits across denominations.

```ts
const { quote } = await sdk.getQuote({
  amount: 1.5,                    // human-readable amount
  amountLamports: 1_500_000_000,  // same amount in smallest units
  recipient: "RecipientPubkey...",
  asset: "SOL",                   // optional — default "SOL"
});

console.log(quote.recipientAmount);  // amount recipient receives after fees
console.log(quote.totalRelayerFee);
console.log(quote.etaSeconds);
console.log(quote.splits);           // per-denomination breakdown
console.log(quote.platformFee);      // platform fee (added by SDK)
```

| Parameter | Description |
|-----------|-------------|
| `amount` | Human-readable transfer/withdraw amount |
| `amountLamports` | Amount in smallest token units |
| `recipient` | Intended withdrawal recipient |
| `asset` | Indexer asset label (default `"SOL"`) |
| `network` | Optional cluster override |

`getQuote` is also exported as a standalone function but requires an initialized SDK context.

---

## Typical flows

### Deposit and withdraw

```ts
const sdk = await ArcaneSDK.create({ rangeApiKey, rpcEndpoint, wallet });

await sdk.getEncryptionKey(wallet);

const { note } = sdk.generateDepositParams(1_000_000_000n, null);
const { tx } = await sdk.buildDepositTx({
  senderPublicKey: wallet.publicKey,
  note,
  onChainBackup: true,
});

// sign and send tx...

const { txHash } = await sdk.withdraw({
  note,
  recipient: "RecipientPubkey...",
});
```

### Quote before withdraw

```ts
const { quote } = await sdk.getQuote({
  amount: 1,
  amountLamports: 1_000_000_000,
  recipient: "RecipientPubkey...",
});

console.log(`Recipient receives ~${quote.recipientAmount} SOL`);

await sdk.withdraw({ note, recipient: "RecipientPubkey..." });
```

---

## Supported deposit denominations

Amounts must decompose exactly into these pool denominations:

| Token | Denominations |
|-------|---------------|
| SOL | 0.1, 1, 5, 10, 50, 100, 500 |
| USDC | 1, 10, 50, 100, 1000, 10000, 100000, 1000000 |
| Arcane | 1, 10, 50, 100, 500, 1000, 10000, 100000, 1000000 |
| Bitcoin | 0.01, 0.05, 0.1, 0.5, 1, 5 |
| Ethereum | 0.1, 1, 10, 50, 100, 500 |

Use `sdk.getDepositAmounts("Solana")` (or another symbol) to retrieve the list programmatically.

---

## License

MIT
