Put a deposit anywhere.

Every Aqueduct vault is a Solana Action, so a post, a QR code or a chat message can carry a working deposit. Drop a themable deposit card into any site with one script tag, or build your own flow with the React hooks and public API.

Spec
Solana Actions 2.4
Network
Solana devnet
Bedrock net APY
4.47%
Rapids net APY
7.00%

Embed a deposit card

One tag gives any website a live Aqueduct deposit: current APY, wallet connect, deposit, withdraw and an inline test USDC faucet. It sizes itself, follows your theme and reports confirmed transactions to your page.

index.html
<div data-aqueduct-widget data-tier="bedrock"></div>
<script async src="https://aqueduct.justus.sh/embed.js"></script>
AttributeValuesDefault
data-tierbedrock, rapidsbedrock
data-themelight, darklight
data-accentHex colour for the main buttonAqueduct ink
data-radiusCorner radius in px, 0 to 3220
data-modedeposit, withdrawdeposit
data-amountPrefilled USDC amountempty
data-widthMax width of the frame420px
Track conversions
document.addEventListener("aqueduct:transaction", (e) => {
  // { kind: "deposit" | "withdraw", tier, amount, signature }
  analytics.track("aqueduct_deposit", e.detail);
});
Or a plain iframe
<iframe
  src="https://aqueduct.justus.sh/embed/rapids?theme=dark&accent=7c3aed&radius=14"
  title="Aqueduct deposit" style="width:100%;max-width:420px;height:480px;border:0"></iframe>

Live preview. Wallets that inject into frames (Phantom, Solflare, Backpack) connect inside the card; when none is found it offers the Blink and the full app instead.

Build your own with @aqueduct/react

Hooks and headless components over the public API: no Anchor, no IDL and no RPC keys in your bundle. Transactions are built by the Actions endpoint and signed by whichever wallet you already use.

EarnCard.tsx
import { AqueductProvider, useVault, useDeposit } from "@aqueduct/react";
import { walletAdapterBinding } from "@aqueduct/react/solana";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";

export function App() {
  return (
    <AqueductProvider baseUrl="https://aqueduct.justus.sh">
      <EarnCard />
    </AqueductProvider>
  );
}

function EarnCard() {
  const { vault } = useVault("bedrock");
  const wallet = walletAdapterBinding(useWallet(), useConnection().connection);
  const deposit = useDeposit("bedrock", wallet);
  return (
    <button disabled={deposit.busy} onClick={() => deposit.run(100)}>
      {deposit.busy ? deposit.stage : `Earn ${vault?.apy.toFixed(2)}% on 100 USDC`}
    </button>
  );
}
Headless form
import { AmountFormController, Apy } from "@aqueduct/react";

<p>Bedrock pays <Apy tier="bedrock" /> right now.</p>

<AmountFormController kind="deposit" tier="bedrock" wallet={wallet}>
  {(f) => (
    <form onSubmit={(e) => (e.preventDefault(), f.submit())}>
      <input value={f.value} onChange={(e) => f.setValue(e.target.value)} />
      <button type="button" onClick={f.setMax}>Max</button>
      {f.error && <small>{f.error}</small>}
      <button disabled={!f.canSubmit}>{f.action.busy ? f.action.stage : "Deposit"}</button>
    </form>
  )}
</AmountFormController>
ExportWhat it gives you
useVaults() / useVault(tier)Live net APY, TVL, share price and allocation; polls every 30 s, SSR-seedable
usePosition(owner, tier)The wallet's position value and shares, plus its USDC balance
useDecisions({ tier, limit })The keeper's rebalance decisions with sentinel verdicts
useDeposit / useWithdraw / useFaucetbuild, sign, confirm, refresh: stage, message, signature and readable errors
useAmountForm(kind, tier, wallet)Input state, Max, validation against balance, position and vault cap
<Apy>, <VaultData>, <AmountFormController>, <Decisions>Headless render-prop components
@aqueduct/react/solanawalletAdapterBinding and keypairBinding for @solana/web3.js wallets
@aqueduct/react/coreThe framework-free client, cache and transaction runner

HTTP API

Everything above runs on public, CORS-open endpoints. Reads are cached for a few seconds at the edge; nothing requires a key.

EndpointReturns
GET /api/v1/vaultsBoth vaults: APY, gross APY, TVL, idle, share price, cap, fees, strategies
GET /api/v1/positions/{owner}A wallet's USDC balance and its position in each vault
GET|POST /api/actions/{tier}/{deposit|withdraw}Solana Action metadata, or an unsigned transaction for { account }
GET|POST /api/actions/faucetMint up to 10,000 devnet test USDC, chained into a deposit
GET /api/actions/icon/{tier|faucet}800 x 800 PNG with the live APY, used as the Blink image
GET /actions.jsonBlink URL mapping for this domain
GET /embed/{tier}The deposit card, for iframes
GET keeper /rates, /decisions, /targets, /feesLive venue rates and the rebalancer's decisions and targets

Hosting the embed yourself

The site sends a strict content security policy with frame-ancestors 'none'. The card only works if /embed/** is excluded from it and served with frame-ancestors * (and no X-Frame-Options) instead. Actions and the API set their own CORS headers in their route handlers, so they work under any page policy.

next.config.ts
async headers() {
  return [
    // Everything except /embed/** keeps the strict policy.
    { source: "/:path((?!embed/).*)", headers: securityHeaders },
    // The deposit card is meant to be framed by any site.
    { source: "/embed/:path*", headers: embedHeaders },
  ];
}