# fluid-wallet-agentkit

**The official SDK for AI agents to interact with Fluid Wallet.**

Give your AI agent a crypto wallet — send payments, swap tokens, check balances, and pay other agents, all without exposing a seed phrase or private key.

[![npm version](https://img.shields.io/npm/v/fluid-wallet-agentkit)](https://www.npmjs.com/package/fluid-wallet-agentkit)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

---

## What is Fluid Wallet?

[Fluid Wallet](https://fluidnative.com) is a self-custodial crypto wallet built on Base. It lets users create email-based wallets secured by a seed phrase and Google Authenticator (TOTP). Once an account is created, the owner can generate **Agentic Keys** — scoped API keys that allow AI agents to operate the wallet within set limits.

**No seed phrase. No private key. Just a scoped API key your agent uses.**

---

## How It Works

```
User registers at fluidnative.com
         ↓
Creates an Agentic Key with scopes + spend limits
         ↓
Gives the key (fwag_...) to the AI agent
         ↓
Agent uses fluid-wallet-agentkit SDK to send, swap, pay
         ↓
Transactions above $100 → owner gets email approval request
All transactions → owner gets email notification
```

---

## Installation

```bash
npm install fluid-wallet-agentkit
```

---

## Setting Up — New Account

If you need a brand new Fluid Wallet account:

```bash
npx fluid-wallet register
```

This wizard guides you through:
1. Opening `https://fluidnative.com` in a browser
2. Registering with email + password
3. Saving the 12-word seed phrase (shown once, must be written down)
4. Scanning Google Authenticator QR code
5. Creating an Agentic Key in the dashboard
6. Verifying identity in the terminal

> **Note:** The seed phrase and OTP steps require a human present at the browser. The agent handles the terminal steps.

---

## Setting Up — Existing Account

If you already have a Fluid Wallet account:

```bash
npx fluid-wallet setup
```

Enter your email, password, seed phrase, and OTP when prompted. Your agent key is saved to `.env` automatically.

---

## Check Agent Status

```bash
npx fluid-wallet status
```

---

## Framework Integration

### OpenAI SDK

```typescript
import OpenAI from "openai";
import { FluidAgent, toOpenAITools, runOpenAIToolCall } from "fluid-wallet-agentkit";

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });
const client = new OpenAI();

const response = await client.chat.completions.create({
  model: "gpt-4o",
  tools: toOpenAITools(agent),   // ← drop-in
  messages: [{ role: "user", content: "What's my wallet balance?" }],
});

// Execute whichever tool OpenAI chose
for (const call of response.choices[0].message.tool_calls ?? []) {
  const result = await runOpenAIToolCall(agent, call);
  console.log(result);
}
```

### Anthropic Claude SDK

```typescript
import Anthropic from "@anthropic-ai/sdk";
import { FluidAgent, toAnthropicTools, runAnthropicToolCall } from "fluid-wallet-agentkit";

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-7",
  max_tokens: 1024,
  tools: toAnthropicTools(agent),   // ← drop-in
  messages: [{ role: "user", content: "Send 0.01 ETH to 0xABC..." }],
});

for (const block of response.content) {
  if (block.type === "tool_use") {
    const result = await runAnthropicToolCall(agent, block);
    console.log(result);
  }
}
```

### Vercel AI SDK

```typescript
import { generateText, jsonSchema } from "ai";
import { openai } from "@ai-sdk/openai";
import { FluidAgent, toVercelAITools } from "fluid-wallet-agentkit";

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });

const { text } = await generateText({
  model: openai("gpt-4o"),
  tools: toVercelAITools(agent, jsonSchema),   // ← drop-in
  prompt: "Swap 0.1 ETH to USDC and tell me the result.",
});
```

### LangChain / LangGraph

```typescript
import { ChatOpenAI } from "@langchain/openai";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { FluidAgent, toLangChainTools } from "fluid-wallet-agentkit";

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });

// Wrap into LangChain DynamicStructuredTool objects
const tools = toLangChainTools(agent).map(
  (t) =>
    new DynamicStructuredTool({
      name: t.name,
      description: t.description,
      schema: z.object({}).passthrough(),
      func: t.func,
    })
);

// Use with createReactAgent, AgentExecutor, etc.
```

### Raw tools (any framework)

```typescript
import { FluidAgent, getFluidTools } from "fluid-wallet-agentkit";

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });
const tools = getFluidTools(agent);

// Each tool has: name, description, inputSchema (JSON Schema), run(args)
for (const tool of tools) {
  console.log(tool.name, tool.description);
}

// Execute any tool by name
const result = await tools
  .find(t => t.name === "fluid_balance")!
  .run({});
console.log(result); // {"walletAddress":"0x...","balances":[...]}
```

---

## Available Tools

| Tool | Scope required | Description |
|---|---|---|
| `fluid_me` | any | Identity: email, wallet address, key name, scopes |
| `fluid_balance` | `read` | On-chain balances in crypto and USD |
| `fluid_history` | `read` | Recent transaction history |
| `fluid_resolve` | `read` | Resolve email → wallet address |
| `fluid_quote_swap` | `read` | Price quote for a swap (no execution) |
| `fluid_estimate_gas` | `read` | Gas cost estimate for a send |
| `fluid_send` | `pay` | Send ETH or tokens on-chain |
| `fluid_swap` | `swap` | Swap tokens via Fluid SOR |
| `fluid_agent_pay` | `agentpay` | Pay another Fluid user by email |

---

## Usage

### Initialize

```typescript
import { FluidAgent } from 'fluid-wallet-agentkit';

const agent = new FluidAgent({
  apiKey: process.env.FLUID_AGENT_KEY, // fwag_...
});
```

### Get Wallet Identity

```typescript
const me = await agent.me();
console.log(me.email);         // owner email
console.log(me.walletAddress); // Base wallet address
console.log(me.scopes);        // ['read', 'pay', 'swap', 'agentpay']
```

### Check Balance

```typescript
const identity = await agent.identity;
// Use the API directly for balance
const r = await fetch('https://fluidnative.com/v1/agents/balance', {
  headers: { 'X-Agent-Key': process.env.FLUID_AGENT_KEY }
});
const balance = await r.json();
console.log(balance);
```

### Send a Payment

```typescript
const result = await agent.payments.send({
  to: '0xRecipientAddress',   // wallet address
  amount: '0.01',              // amount as string
  token: 'ETH',                // ETH, USDC, USDT, etc.
  chain: 'base',               // base | ethereum
});

console.log(result.txHash);
console.log(result.explorerUrl);
```

> **If the amount is above your per-transaction limit ($100 by default):**
> The request returns `202 pending_approval`. The wallet owner receives an email with Approve / Reject buttons. Your agent should poll the approval status endpoint.

```typescript
// Handle approval flow
if (result.status === 'pending_approval') {
  console.log('Waiting for owner approval...');
  console.log('Poll:', result.pollUrl);

  // Poll every 5 seconds
  const pollApproval = async (token: string) => {
    while (true) {
      const r = await fetch(`https://fluidnative.com/api/agent-approval-status/${token}`);
      const status = await r.json();
      if (status.status === 'approved') return true;
      if (status.status === 'rejected') throw new Error('Transaction rejected by owner');
      if (status.status === 'expired') throw new Error('Approval request expired');
      await new Promise(resolve => setTimeout(resolve, 5000));
    }
  };

  await pollApproval(result.approvalToken);
  // Re-submit the transaction after approval
}
```

### Swap Tokens

```typescript
// Get a price quote first (no execution)
const quote = await agent.payments.quoteSwap({
  fromToken: 'ETH',
  toToken: 'USDC',
  amount: '0.1',
});
console.log(quote);

// Execute the swap
const swap = await agent.payments.swap({
  fromToken: 'ETH',
  toToken: 'USDC',
  amount: '0.1',
  slippage: '0.5', // optional, default 0.5%
});
console.log(swap.txHash);
```

### Pay Another Fluid User by Email

```typescript
const result = await agent.payments.agentPay({
  toEmail: 'another-user@example.com',
  amount: '10',
  token: 'USDC',
  memo: 'Payment for services', // optional
});
```

### Resolve Email → Wallet Address

```typescript
const identity = await agent.identity.resolve('user@example.com');
console.log(identity.walletAddress); // 0x...
console.log(identity.username);      // their Fluid username
```

### Transaction History

```typescript
const history = await agent.identity.history(20); // last 20 txs
```

### Estimate Gas

```typescript
const gas = await agent.payments.estimateGas({
  to: '0xRecipientAddress',
  amount: '0.01',
  token: 'ETH',
  chain: 'base',
});
console.log(gas.estimatedGasUsd); // "$0.01-0.05"
```

---

## Spend Limits & Approvals

Every Agentic Key has two limits set by the wallet owner:

| Limit | Default | Behavior |
|---|---|---|
| Per-transaction limit | $100 USD | Transactions above this require owner email approval |
| Daily spend cap | $1000 USD | Hard daily limit — blocked if exceeded |

**Approval flow:**
1. Agent sends request → server checks USD value
2. If above per-tx limit → `202` response with `approvalToken`
3. Owner receives email with **Approve** / **Reject** buttons
4. Agent polls `GET /api/agent-approval-status/:token`
5. Owner approves → agent re-submits transaction

The owner also receives an **email notification** for every transaction above $100, regardless of approval status.

---

## Agentic Key Scopes

| Scope | What it allows |
|---|---|
| `read` | Balance, history, price quotes, gas estimates |
| `pay` | Send ETH or ERC-20 tokens on-chain |
| `swap` | Execute token swaps via Fluid SOR |
| `agentpay` | Pay another Fluid user by their email |

Keys that lack a required scope return `403 Forbidden`.

---

## Direct API (without SDK)

All endpoints require: `X-Agent-Key: fwag_...`

Base URL: `https://fluidnative.com`

```bash
# Identity
GET  /v1/agents/me
GET  /v1/agents/balance
GET  /v1/agents/history?limit=20
GET  /v1/agents/identity/resolve?email=user@example.com

# Payments
POST /v1/agents/send
POST /v1/agents/swap
POST /v1/agents/agent-pay
POST /v1/agents/quote-swap
POST /v1/agents/estimate-gas

# Approvals
GET  /api/agent-approval-status/:token
```

Example with curl:
```bash
curl https://fluidnative.com/v1/agents/me \
  -H "X-Agent-Key: fwag_your_key_here"
```

---

## Full Example — Autonomous Payment Agent

```typescript
import { FluidAgent } from 'fluid-wallet-agentkit';

const agent = new FluidAgent({ apiKey: process.env.FLUID_AGENT_KEY! });

async function payForService(recipientEmail: string, usdAmount: string) {
  // 1. Verify identity
  const me = await agent.me();
  console.log(`Agent wallet: ${me.walletAddress}`);

  // 2. Check recipient
  const recipient = await agent.identity.resolve(recipientEmail);
  console.log(`Paying: ${recipient.walletAddress}`);

  // 3. Send payment
  const result = await agent.payments.agentPay({
    toEmail: recipientEmail,
    amount: usdAmount,
    token: 'USDC',
  });

  // 4. Handle approval if needed
  if ((result as any).status === 'pending_approval') {
    console.log('Waiting for owner approval...');
    const token = (result as any).approvalToken;
    let approved = false;
    while (!approved) {
      await new Promise(r => setTimeout(r, 5000));
      const s = await fetch(`https://fluidnative.com/api/agent-approval-status/${token}`);
      const { status } = await s.json();
      if (status === 'approved') approved = true;
      else if (status === 'rejected') throw new Error('Payment rejected by owner');
      else if (status === 'expired') throw new Error('Approval expired');
    }
    // Re-submit after approval
    return payForService(recipientEmail, usdAmount);
  }

  console.log(`Payment sent! TX: ${(result as any).txHash}`);
  return result;
}

payForService('collaborator@example.com', '25');
```

---

## Security

| Concern | How it's handled |
|---|---|
| Seed phrase | Never stored, never sent to SDK — zeroed immediately after CLI verification |
| Private key | Never exposed — server-side signing only |
| API key | Store in `.env` only — never hardcode or log |
| Key creation | Browser-only — cannot be created via CLI or API |
| Spend limits | Owner sets per-tx and daily limits at key creation |
| Approval flow | Transactions above limit require owner email approval |
| Notifications | Owner receives email for every transaction above $100 |

---

## Environment Variables

```bash
FLUID_AGENT_KEY=fwag_...          # your agentic key (required)
FLUID_BASE_URL=https://fluidnative.com  # optional, defaults to production
```

---

## Supported Chains

| Chain | Network | Native Token |
|---|---|---|
| `base` | Base mainnet | ETH |
| `ethereum` | Ethereum mainnet | ETH |
| `solana` | Solana mainnet | SOL |
| `injective` | Injective mainnet | INJ |

---

## Links

- **Website**: https://fluidnative.com
- **Create Agentic Key**: https://fluidnative.com/agentic-keys
- **Agent API**: https://fluidnative.com/.well-known/agent.json
- **MCP descriptor**: https://fluidnative.com/.well-known/mcp.json
- **npm**: https://www.npmjs.com/package/fluid-wallet-agentkit
- **GitHub**: https://github.com/fluidbase9/fluid-wallet-agentkit

---

## License

MIT © Fluid Wallet
