Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ multiple operations into a single programmable and extensible toolkit.
- Liquidity pool (LP) deposits & withdrawals
- Querying pool reserves and share IDs
- Custom contract integrations (current)
- Claimable balances for conditional / time-locked payments (escrow & vesting)
- Designed for future LP provider integrations
- Supports Testnet & Mainnet

Expand Down Expand Up @@ -331,6 +332,115 @@ const shareId = await agent.lp.getShareId();

---

## 🔒 Claimable Balances (Conditional / Time-Locked Payments)

Stellar's native primitive for **escrow, vesting, and scheduled payouts** —
ideal for AI-agent workflows where funds must be released only when a
predicate is satisfied. Exposed in three ways:

1. Programmatic API on `AgentClient` → `agent.claimable.*`
2. LangChain `DynamicStructuredTool` → `StellarClaimableBalanceTool` (auto-included in `stellarTools`)
3. Lower-level functions in `lib/claimableBalance` (`createClaimableBalance`, `claimClaimableBalance`, `listClaimableBalances`, `buildPredicate`)

### Programmatic API

```typescript
import { AgentClient } from "stellartools";

const agent = new AgentClient({ network: "testnet" });

// 1. Lock 100 XLM for `recipient`, claimable any time within the next 24h
const created = await agent.claimable.create({
sourceSecret: process.env.SOURCE_SECRET!,
asset: { code: "XLM" },
amount: "100",
claimants: [
{
destination: "GRECIPIENT...",
predicate: { type: "beforeRelativeTime", seconds: 86400 },
},
],
});
console.log(created.transactionHash, created.balanceIds);

// 2. List claimable balances awaiting a specific account
const open = await agent.claimable.list({ claimant: "GRECIPIENT..." });

// 3. Claim a balance
await agent.claimable.claim({
claimerSecret: process.env.RECIPIENT_SECRET!,
balanceId: created.balanceIds[0],
});
```

### Composable Predicates

| Type | Meaning |
| ------------------- | -------------------------------------------------- |
| `unconditional` | Always claimable |
| `beforeRelativeTime`| Claimable for N seconds after creation |
| `beforeAbsoluteTime`| Claimable until a Unix epoch timestamp |
| `not` | Negation of an inner predicate |
| `and` / `or` | Logical combination of two inner predicates |

```typescript
// Two-party escrow: claimable AFTER `releaseAt` AND BEFORE 24h elapse
const predicate = {
type: "and",
predicates: [
{ type: "beforeRelativeTime", seconds: 86400 },
{
type: "not",
predicate: { type: "beforeAbsoluteTime", epochSeconds: releaseAt },
},
],
};
```

### Defaults & Overrides

The lib exposes the relevant Stellar protocol limits as named exports
(no magic numbers in user code):

```typescript
import {
MAX_CLAIMANTS_PER_BALANCE, // 10 (per-balance protocol cap)
MAX_OPERATIONS_PER_TRANSACTION, // 100 (default per-tx ops cap)
MAX_PREDICATE_DEPTH, // 5 (default predicate nesting)
DEFAULT_TRANSACTION_TIMEOUT_SECONDS,
} from "stellartools";

// All of these are overridable per-call via `options`:
await agent.claimable.create({
sourceSecret,
asset: { code: "XLM" },
amount: "1",
claimants,
options: {
maxOperationsPerTransaction: 50,
maxPredicateDepth: 3,
transactionTimeoutSeconds: 60,
baseFee: "200",
},
});
```

### LangChain Tool

`StellarClaimableBalanceTool` is auto-bundled into `stellarTools`, so AI
agents can call `create`, `claim`, and `list` directly via natural language:

> _"Lock 50 XLM for GABCD... claimable any time within the next 2 hours."_

It reads `STELLAR_PRIVATE_KEY` for `create`, and either
`STELLAR_CLAIMER_PRIVATE_KEY` (preferred) or `STELLAR_PRIVATE_KEY` for
`claim`. The tool always returns JSON (`{ ok, ... }`) so agents can parse
the result deterministically.

A runnable example lives at `examples/claimable-balance-example.ts`.

---

## 🌐 Supported Networks

- **Testnet** - Full support, no restrictions, safe for development
Expand Down
72 changes: 72 additions & 0 deletions agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ import {
type SwapBestRouteResult,
} from "./lib/dex";
import { bridgeTokenTool } from "./tools/bridge";
import {
createClaimableBalance as cbCreate,
claimClaimableBalance as cbClaim,
listClaimableBalances as cbList,
type CreateClaimableBalanceParams,
type CreateClaimableBalanceResult,
type ClaimClaimableBalanceParams,
type ClaimClaimableBalanceResult,
type ListClaimableBalancesParams,
type ClaimableBalanceRecord,
type ClaimableBalanceOptions,
type ClaimPredicate,
type ClaimantInput,
} from "./lib/claimableBalance";
import { stellarGetBalanceTool, stellarGetAccountInfoTool } from "./tools/stellar";
import {
Horizon,
Expand Down Expand Up @@ -67,6 +81,15 @@ export type {
RouteQuote,
SwapBestRouteParams,
SwapBestRouteResult,
CreateClaimableBalanceParams,
CreateClaimableBalanceResult,
ClaimClaimableBalanceParams,
ClaimClaimableBalanceResult,
ListClaimableBalancesParams,
ClaimableBalanceRecord,
ClaimableBalanceOptions,
ClaimPredicate,
ClaimantInput,
};

export class AgentClient {
Expand Down Expand Up @@ -284,6 +307,55 @@ export class AgentClient {
},
};

/**
* Claimable Balances — conditional / time-locked payments (escrow, vesting,
* scheduled payouts). Useful for AI-agent workflows where funds must be
* released only when a predicate evaluates to true.
*
* Each input claimant becomes its own `CreateClaimableBalance` operation,
* so the returned `balanceIds` map 1:1 with the input `claimants` array.
*
* @example
* // Lock 100 XLM for `recipient`, claimable any time within the next 24h
* await agent.claimable.create({
* sourceSecret: process.env.SOURCE_SECRET!,
* asset: { code: "XLM" },
* amount: "100",
* claimants: [{
* destination: recipient,
* predicate: { type: "beforeRelativeTime", seconds: 86400 },
* }],
* });
*/
public claimable = {
create: async (
params: CreateClaimableBalanceParams
): Promise<CreateClaimableBalanceResult> => {
return await cbCreate(
{ network: this.network, horizonUrl: this.rpcUrl },
params
);
},

claim: async (
params: ClaimClaimableBalanceParams
): Promise<ClaimClaimableBalanceResult> => {
return await cbClaim(
{ network: this.network, horizonUrl: this.rpcUrl },
params
);
},

list: async (
params?: ListClaimableBalancesParams
): Promise<ClaimableBalanceRecord[]> => {
return await cbList(
{ network: this.network, horizonUrl: this.rpcUrl },
params ?? {}
);
},
};

/**
* Launch a new token on the Stellar network.
*
Expand Down
113 changes: 113 additions & 0 deletions examples/claimable-balance-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
* Claimable Balance Usage Example
*
* Demonstrates the three claimable-balance flows exposed by AgentKit:
* 1. create — lock funds with a (possibly composite) claim predicate
* 2. list — discover open claimable balances for a recipient
* 3. claim — release the locked funds to the recipient
*
* Use cases this enables for AI agents:
* - Conditional payouts (only pay if X happens before deadline)
* - Vesting / scheduled payments
* - Two-party escrow (either party can claim before / after deadline)
*
* ⚠️ TESTNET ONLY. Never use mainnet secrets in examples.
*
* Run with: ts-node examples/claimable-balance-example.ts
*/

import { AgentClient } from "../agent";
import { Keypair } from "@stellar/stellar-sdk";

async function exampleClaimableBalances() {
console.log("🔒 Claimable Balance Example");
console.log("=".repeat(60));

const agent = new AgentClient({ network: "testnet" });

// In real usage, source/recipient would be funded testnet accounts.
const source = Keypair.random();
const recipient = Keypair.random();

console.log("\nGenerated test accounts:");
console.log(` Source: ${source.publicKey()}`);
console.log(` Recipient: ${recipient.publicKey()}`);
console.log(
"\n⚠️ Fund these accounts on testnet before running for real:\n" +
" https://laboratory.stellar.org/#account-creator?network=test"
);

// ─── 1. Create ─────────────────────────────────────────────────────────
// Lock 50 XLM for the recipient. Two layered conditions, expressed as a
// composite predicate:
// - claimable for the next 24h (beforeRelativeTime: 86400s)
// - AND only after the configured "release time" has passed
// (i.e. NOT before that absolute timestamp)
const releaseAtUnix = Math.floor(Date.now() / 1000) + 60; // 60s from now

console.log("\nCreating claimable balance with composite predicate:");
console.log(` Amount: 50 XLM`);
console.log(` Window: next 24h`);
console.log(` Earliest: ${new Date(releaseAtUnix * 1000).toISOString()}`);

const created = await agent.claimable.create({
sourceSecret: source.secret(),
asset: { code: "XLM" },
amount: "50",
claimants: [
{
destination: recipient.publicKey(),
predicate: {
type: "and",
predicates: [
{ type: "beforeRelativeTime", seconds: 86400 },
{
type: "not",
predicate: {
type: "beforeAbsoluteTime",
epochSeconds: releaseAtUnix,
},
},
],
},
},
],
});

console.log("\n✅ Created!");
console.log(` Tx hash: ${created.transactionHash}`);
console.log(` Balance id: ${created.balanceIds[0]}`);

// ─── 2. List ───────────────────────────────────────────────────────────
console.log("\nListing open claimable balances for recipient...");
const open = await agent.claimable.list({
claimant: recipient.publicKey(),
});
console.log(` Found ${open.length} balance(s).`);
open.forEach((b) =>
console.log(` - ${b.id} amount=${b.amount} asset=${b.asset}`)
);

// ─── 3. Claim ──────────────────────────────────────────────────────────
// In a real flow you'd wait until the predicate is satisfied. Here we
// demonstrate the call shape — Horizon will reject the claim with a
// descriptive error if the predicate is not yet satisfied.
console.log("\nAttempting to claim (will fail if predicate not yet satisfied)...");
try {
const claimed = await agent.claimable.claim({
claimerSecret: recipient.secret(),
balanceId: created.balanceIds[0],
});
console.log(`✅ Claimed! Tx hash: ${claimed.transactionHash}`);
} catch (err) {
console.log(
`⏳ Not yet claimable (expected): ${(err as Error).message}\n` +
` Wait until ${new Date(releaseAtUnix * 1000).toISOString()} and retry.`
);
}
}

exampleClaimableBalances().catch((err) => {
console.error("Example failed:", err);
process.exit(1);
});
31 changes: 30 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import { StellarLiquidityContractTool } from "./tools/contract";
import { StellarDexTool } from "./tools/dex";
import { StellarContractTool } from "./tools/stake";
import { stellarSendPaymentTool, stellarGetBalanceTool, stellarGetAccountInfoTool } from "./tools/stellar";
import { StellarClaimableBalanceTool } from "./tools/claimableBalance";
export { StellarClaimableBalanceTool } from "./tools/claimableBalance";
export {
MAX_CLAIMANTS_PER_BALANCE,
MAX_OPERATIONS_PER_TRANSACTION,
MAX_PREDICATE_DEPTH,
DEFAULT_TRANSACTION_TIMEOUT_SECONDS,
buildPredicate,
extractBalanceIdsFromTransactionResult,
} from "./lib/claimableBalance";
import {
AgentClient,
AgentConfig,
Expand All @@ -15,6 +25,15 @@ import type {
RouteQuote,
SwapBestRouteParams,
SwapBestRouteResult,
CreateClaimableBalanceParams,
CreateClaimableBalanceResult,
ClaimClaimableBalanceParams,
ClaimClaimableBalanceResult,
ListClaimableBalancesParams,
ClaimableBalanceRecord,
ClaimableBalanceOptions,
ClaimPredicate,
ClaimantInput,
} from "./agent";

export {
Expand All @@ -30,6 +49,15 @@ export type {
RouteQuote,
SwapBestRouteParams,
SwapBestRouteResult,
CreateClaimableBalanceParams,
CreateClaimableBalanceResult,
ClaimClaimableBalanceParams,
ClaimClaimableBalanceResult,
ListClaimableBalancesParams,
ClaimableBalanceRecord,
ClaimableBalanceOptions,
ClaimPredicate,
ClaimantInput,
};
export const stellarTools = [
bridgeTokenTool,
Expand All @@ -38,5 +66,6 @@ export const stellarTools = [
StellarContractTool,
stellarSendPaymentTool,
stellarGetBalanceTool,
stellarGetAccountInfoTool
stellarGetAccountInfoTool,
StellarClaimableBalanceTool,
];
Loading