agent-operator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +171 -0
  2. package/dist/cli/add-wallet.d.ts +1 -0
  3. package/dist/cli/add-wallet.js +15 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +34 -0
  6. package/dist/cli/init.d.ts +1 -0
  7. package/dist/cli/init.js +200 -0
  8. package/dist/cli/status.d.ts +1 -0
  9. package/dist/cli/status.js +25 -0
  10. package/dist/config.d.ts +18 -0
  11. package/dist/config.js +57 -0
  12. package/dist/connectors/base.d.ts +12 -0
  13. package/dist/connectors/base.js +119 -0
  14. package/dist/connectors/solana.d.ts +12 -0
  15. package/dist/connectors/solana.js +91 -0
  16. package/dist/connectors/tempo.d.ts +12 -0
  17. package/dist/connectors/tempo.js +102 -0
  18. package/dist/connectors/types.d.ts +28 -0
  19. package/dist/connectors/types.js +1 -0
  20. package/dist/index.d.ts +15 -0
  21. package/dist/index.js +16 -0
  22. package/dist/notifications/engine.d.ts +11 -0
  23. package/dist/notifications/engine.js +64 -0
  24. package/dist/notifications/telegram.d.ts +21 -0
  25. package/dist/notifications/telegram.js +146 -0
  26. package/dist/notifications/types.d.ts +32 -0
  27. package/dist/notifications/types.js +1 -0
  28. package/dist/operator.d.ts +26 -0
  29. package/dist/operator.js +206 -0
  30. package/dist/policies/daily-limit.d.ts +1 -0
  31. package/dist/policies/daily-limit.js +15 -0
  32. package/dist/policies/engine.d.ts +16 -0
  33. package/dist/policies/engine.js +24 -0
  34. package/dist/policies/high-value-gate.d.ts +2 -0
  35. package/dist/policies/high-value-gate.js +28 -0
  36. package/dist/policies/low-balance.d.ts +3 -0
  37. package/dist/policies/low-balance.js +23 -0
  38. package/dist/policies/new-counterparty.d.ts +2 -0
  39. package/dist/policies/new-counterparty.js +29 -0
  40. package/dist/policies/session-cap.d.ts +1 -0
  41. package/dist/policies/session-cap.js +11 -0
  42. package/dist/policies/types.d.ts +18 -0
  43. package/dist/policies/types.js +20 -0
  44. package/dist/spend/tracker.d.ts +17 -0
  45. package/dist/spend/tracker.js +101 -0
  46. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # agent-operator
2
+
3
+ Drop-in toolkit for governed, observable AI agent payments.
4
+
5
+ MPP for payments via chain connectors. OWS for authorization + policies. Pluggable notifications for human oversight.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install agent-operator
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # interactive setup — creates agent-operator.json
17
+ npx agent-operator init
18
+ ```
19
+
20
+ ```typescript
21
+ import { createOperator } from "agent-operator"
22
+
23
+ const operator = await createOperator()
24
+
25
+ await operator.pay("my-agent", {
26
+ service: "https://api.example.com",
27
+ chain: "base",
28
+ maxBudget: 2,
29
+ })
30
+
31
+ await operator.shutdown()
32
+ ```
33
+
34
+ ## Config
35
+
36
+ All configuration lives in `agent-operator.json`. The CLI generates it, or create it manually:
37
+
38
+ ```json
39
+ {
40
+ "wallets": {
41
+ "my-agent": {
42
+ "walletId": "ows-wallet-id",
43
+ "chains": ["base", "solana", "tempo"],
44
+ "policies": {
45
+ "dailyLimit": 50,
46
+ "sessionCap": 5,
47
+ "newCounterpartyHold": true,
48
+ "lowBalanceThreshold": 10,
49
+ "highValueThreshold": 200
50
+ },
51
+ "notifications": {
52
+ "provider": "telegram",
53
+ "botToken": "YOUR_BOT_TOKEN",
54
+ "chatId": "YOUR_CHAT_ID",
55
+ "alerts": {
56
+ "lowBalance": true,
57
+ "highValueTx": true,
58
+ "newCounterparty": true,
59
+ "policyDenied": true,
60
+ "dailySummary": true,
61
+ "sessionOpened": false
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ One agent-operator instance manages multiple wallets. Each wallet has its own chains, policies, and notification provider.
70
+
71
+ ## Connectors
72
+
73
+ Each connector wraps MPP for a specific chain. Built-in:
74
+
75
+ - **Tempo** — uses built-in `mppx` session method
76
+ - **Base** — custom MPPX method with OWS EVM signing, USDC balance via RPC
77
+ - **Solana** — wraps `@solana/mpp` with OWS Ed25519 signing, USDC balance via RPC
78
+
79
+ Connectors are resolved automatically from the wallet's `chains` array.
80
+
81
+ ## Policies
82
+
83
+ The policy engine runs before every `pay()` call, in order:
84
+
85
+ 1. **Session cap** — reject if `maxBudget > sessionCap`
86
+ 2. **Daily limit** — reject if today's spend + `maxBudget > dailyLimit`
87
+ 3. **High value gate** — if above threshold, request human approval via notification
88
+ 4. **New counterparty hold** — if first-time service, request human approval
89
+ 5. **Low balance alert** — non-blocking notification if below threshold
90
+
91
+ Spend is tracked locally in `~/.agent-operator/spend.json`.
92
+
93
+ ## Notifications
94
+
95
+ Pluggable provider system. Each wallet can use a different provider.
96
+
97
+ ### TelegramProvider (built-in)
98
+
99
+ Uses [grammy](https://grammy.dev) with long-polling for approval flows. Included as a dependency — no extra install needed.
100
+
101
+ Approval messages show inline buttons. First response wins. Timeout (default 5 min) = rejected.
102
+
103
+ ### Custom Provider
104
+
105
+ ```typescript
106
+ import { NotificationProvider, AgentEvent, ApprovalEvent, ApprovalResult } from "agent-operator"
107
+
108
+ class MyProvider implements NotificationProvider {
109
+ name = "my-platform"
110
+ async start() {}
111
+ async stop() {}
112
+ async send(event: AgentEvent) { /* fire-and-forget alert */ }
113
+ async sendWithApproval(event: ApprovalEvent): Promise<ApprovalResult> {
114
+ const approved = await askUser(event)
115
+ return { approved, approvalId: event.approvalId }
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## CLI
121
+
122
+ ```bash
123
+ npx agent-operator init # interactive setup
124
+ npx agent-operator add-wallet # add wallet to existing config
125
+ npx agent-operator status # show wallets, balances, today's spend
126
+ ```
127
+
128
+ ## API
129
+
130
+ ```typescript
131
+ const operator = await createOperator() // reads ./agent-operator.json
132
+ const operator = await createOperator(path) // custom config path
133
+
134
+ await operator.pay(walletName, { service, chain, maxBudget })
135
+ await operator.getBalance(walletName, chain)
136
+ await operator.closeSession(walletName, sessionId)
137
+ operator.getWalletNames()
138
+ await operator.shutdown()
139
+ ```
140
+
141
+ ## How It Works
142
+
143
+ ```
144
+ agent code
145
+ |
146
+ v
147
+ agent-operator SDK (in-process)
148
+ +-- Connectors
149
+ | +-- TempoConnector (mppx session + OWS signing)
150
+ | +-- BaseConnector (custom mppx method + OWS EVM signing)
151
+ | +-- SolanaConnector (@solana/mpp + OWS Ed25519 signing)
152
+ |
153
+ +-- Policy Engine
154
+ | +-- daily-limit, session-cap, high-value-gate
155
+ | +-- new-counterparty-hold, low-balance-alert
156
+ |
157
+ +-- Notification Engine
158
+ +-- TelegramProvider (grammy long-polling)
159
+ +-- [custom providers]
160
+
161
+ No server. No KV. No separate process.
162
+ ```
163
+
164
+ ## Dependencies
165
+
166
+ | Package | Role |
167
+ |---------|------|
168
+ | `@open-wallet-standard/core` | Wallet management, signing, policies |
169
+ | `mppx` | MPP protocol client (Tempo built-in) |
170
+ | `@solana/mpp` | Solana MPP implementation |
171
+ | `grammy` | Telegram bot for notifications |
@@ -0,0 +1 @@
1
+ export declare function addWallet(): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ const CONFIG_FILE = "agent-operator.json";
4
+ export async function addWallet() {
5
+ const configPath = resolve(process.cwd(), CONFIG_FILE);
6
+ if (!existsSync(configPath)) {
7
+ console.log(`No ${CONFIG_FILE} found. Run 'agent-operator init' first.`);
8
+ process.exit(1);
9
+ }
10
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
11
+ // re-use the init wallet prompt flow
12
+ const { init } = await import("./init.js");
13
+ // the init flow handles adding to existing config
14
+ await init();
15
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ const command = process.argv[2];
3
+ async function main() {
4
+ switch (command) {
5
+ case "init": {
6
+ const { init } = await import("./init.js");
7
+ await init();
8
+ break;
9
+ }
10
+ case "add-wallet": {
11
+ const { addWallet } = await import("./add-wallet.js");
12
+ await addWallet();
13
+ break;
14
+ }
15
+ case "status": {
16
+ const { status } = await import("./status.js");
17
+ await status();
18
+ break;
19
+ }
20
+ default:
21
+ console.log(`agent-operator - governed AI agent payments\n`);
22
+ console.log(`Commands:`);
23
+ console.log(` init Interactive setup — creates agent-operator.json`);
24
+ console.log(` add-wallet Add a wallet to existing config`);
25
+ console.log(` status Show all wallets, balances, today's spend`);
26
+ console.log(`\nUsage: npx agent-operator <command>`);
27
+ break;
28
+ }
29
+ }
30
+ main().catch((err) => {
31
+ console.error(err);
32
+ process.exit(1);
33
+ });
34
+ export {};
@@ -0,0 +1 @@
1
+ export declare function init(): Promise<void>;
@@ -0,0 +1,200 @@
1
+ import prompts from "prompts";
2
+ import { writeFileSync, existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ const CONFIG_FILE = "agent-operator.json";
5
+ async function promptWallet() {
6
+ const { name } = await prompts({
7
+ type: "text",
8
+ name: "name",
9
+ message: "Wallet name",
10
+ validate: (v) => (v.length > 0 ? true : "Name is required"),
11
+ });
12
+ if (!name)
13
+ return null;
14
+ const { chains } = await prompts({
15
+ type: "multiselect",
16
+ name: "chains",
17
+ message: "Chains (space to select)",
18
+ choices: [
19
+ { title: "base", value: "base" },
20
+ { title: "solana", value: "solana" },
21
+ { title: "tempo", value: "tempo" },
22
+ ],
23
+ min: 1,
24
+ });
25
+ if (!chains || chains.length === 0)
26
+ return null;
27
+ const policies = await prompts([
28
+ {
29
+ type: "number",
30
+ name: "dailyLimit",
31
+ message: "Daily spend limit ($)",
32
+ initial: 50,
33
+ },
34
+ {
35
+ type: "number",
36
+ name: "sessionCap",
37
+ message: "Session cap ($)",
38
+ initial: 5,
39
+ },
40
+ {
41
+ type: "confirm",
42
+ name: "newCounterpartyHold",
43
+ message: "Hold new counterparties for approval?",
44
+ initial: true,
45
+ },
46
+ {
47
+ type: "number",
48
+ name: "lowBalanceThreshold",
49
+ message: "Low balance threshold ($)",
50
+ initial: 10,
51
+ },
52
+ {
53
+ type: "number",
54
+ name: "highValueThreshold",
55
+ message: "High value threshold ($)",
56
+ initial: 200,
57
+ },
58
+ ]);
59
+ const { provider } = await prompts({
60
+ type: "select",
61
+ name: "provider",
62
+ message: "Notification provider",
63
+ choices: [
64
+ { title: "Telegram", value: "telegram" },
65
+ { title: "Webhook", value: "webhook" },
66
+ ],
67
+ });
68
+ if (provider === undefined)
69
+ return null;
70
+ let notifications;
71
+ if (provider === "telegram") {
72
+ const tg = await prompts([
73
+ {
74
+ type: "password",
75
+ name: "botToken",
76
+ message: "Telegram bot token",
77
+ },
78
+ {
79
+ type: "text",
80
+ name: "chatId",
81
+ message: "Telegram chat ID",
82
+ },
83
+ ]);
84
+ notifications = {
85
+ provider: "telegram",
86
+ botToken: tg.botToken,
87
+ chatId: tg.chatId,
88
+ };
89
+ }
90
+ else {
91
+ const wh = await prompts([
92
+ {
93
+ type: "text",
94
+ name: "url",
95
+ message: "Webhook URL",
96
+ },
97
+ {
98
+ type: "password",
99
+ name: "secret",
100
+ message: "Webhook secret (optional)",
101
+ },
102
+ ]);
103
+ notifications = {
104
+ provider: "webhook",
105
+ url: wh.url,
106
+ secret: wh.secret || undefined,
107
+ };
108
+ }
109
+ // try to create/find OWS wallet
110
+ let walletId = "";
111
+ try {
112
+ const ows = await import("@open-wallet-standard/core");
113
+ console.log(`\nChecking for OWS wallet "${name}"...`);
114
+ try {
115
+ const existing = ows.getWallet(name);
116
+ walletId = existing.id;
117
+ console.log(` → Found existing wallet: ${walletId}`);
118
+ }
119
+ catch {
120
+ console.log(` → Not found. Creating wallet via OWS...`);
121
+ const created = ows.createWallet(name);
122
+ walletId = created.id;
123
+ console.log(` ✅ Wallet created: ${name} (id: ${walletId})`);
124
+ // generate API key
125
+ try {
126
+ const apiKey = ows.createApiKey(name, [walletId], [], "");
127
+ console.log(` ✅ API key generated: ${apiKey.id}`);
128
+ }
129
+ catch {
130
+ console.log(` ⚠️ Could not generate API key — set up manually`);
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ console.log(`\n⚠️ OWS not available. Enter wallet ID manually.`);
136
+ const { id } = await prompts({
137
+ type: "text",
138
+ name: "id",
139
+ message: "OWS wallet ID",
140
+ });
141
+ walletId = id || "PLACEHOLDER";
142
+ }
143
+ return {
144
+ name,
145
+ wallet: {
146
+ walletId,
147
+ chains,
148
+ policies: {
149
+ dailyLimit: policies.dailyLimit,
150
+ sessionCap: policies.sessionCap,
151
+ newCounterpartyHold: policies.newCounterpartyHold,
152
+ lowBalanceThreshold: policies.lowBalanceThreshold,
153
+ highValueThreshold: policies.highValueThreshold,
154
+ },
155
+ notifications,
156
+ },
157
+ };
158
+ }
159
+ export async function init() {
160
+ console.log("\nWelcome to agent-operator setup.\n");
161
+ const configPath = resolve(process.cwd(), CONFIG_FILE);
162
+ let config = { wallets: {} };
163
+ if (existsSync(configPath)) {
164
+ const { action } = await prompts({
165
+ type: "select",
166
+ name: "action",
167
+ message: `${CONFIG_FILE} already exists`,
168
+ choices: [
169
+ { title: "Overwrite", value: "overwrite" },
170
+ { title: "Add wallet to existing", value: "add" },
171
+ { title: "Cancel", value: "cancel" },
172
+ ],
173
+ });
174
+ if (action === "cancel")
175
+ return;
176
+ if (action === "add") {
177
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
178
+ }
179
+ }
180
+ let addMore = true;
181
+ while (addMore) {
182
+ const result = await promptWallet();
183
+ if (!result)
184
+ break;
185
+ config.wallets[result.name] = result.wallet;
186
+ const { more } = await prompts({
187
+ type: "confirm",
188
+ name: "more",
189
+ message: "Add another wallet?",
190
+ initial: false,
191
+ });
192
+ addMore = more;
193
+ }
194
+ if (Object.keys(config.wallets).length === 0) {
195
+ console.log("\nNo wallets configured. Exiting.");
196
+ return;
197
+ }
198
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
199
+ console.log(`\n✅ Config written: ${configPath}`);
200
+ }
@@ -0,0 +1 @@
1
+ export declare function status(): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { getSpendSummary, getCounterparties } from "../spend/tracker.js";
4
+ const CONFIG_FILE = "agent-operator.json";
5
+ export async function status() {
6
+ const configPath = resolve(process.cwd(), CONFIG_FILE);
7
+ if (!existsSync(configPath)) {
8
+ console.log(`No ${CONFIG_FILE} found. Run 'agent-operator init' first.`);
9
+ process.exit(1);
10
+ }
11
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
12
+ console.log("\n📊 agent-operator status\n");
13
+ for (const [name, wallet] of Object.entries(config.wallets)) {
14
+ const summary = getSpendSummary(name);
15
+ const counterparties = getCounterparties(name);
16
+ console.log(` ${name}`);
17
+ console.log(` ├── wallet: ${wallet.walletId}`);
18
+ console.log(` ├── chains: ${wallet.chains.join(", ")}`);
19
+ console.log(` ├── spent today: $${summary.spent.toFixed(2)}${wallet.policies.dailyLimit ? ` / $${wallet.policies.dailyLimit}` : ""}`);
20
+ console.log(` ├── sessions today: ${summary.sessionCount}`);
21
+ console.log(` ├── known counterparties: ${counterparties.length > 0 ? counterparties.join(", ") : "none"}`);
22
+ console.log(` └── notification: ${wallet.notifications.provider}`);
23
+ console.log();
24
+ }
25
+ }
@@ -0,0 +1,18 @@
1
+ import type { PoliciesConfig } from "./policies/types.js";
2
+ import type { AlertsConfig } from "./notifications/types.js";
3
+ export interface WalletConfig {
4
+ walletId: string;
5
+ chains: string[];
6
+ policies: PoliciesConfig;
7
+ notifications: NotificationConfig;
8
+ }
9
+ export interface NotificationConfig {
10
+ provider: string;
11
+ alerts?: AlertsConfig;
12
+ [key: string]: unknown;
13
+ }
14
+ export interface OperatorConfig {
15
+ wallets: Record<string, WalletConfig>;
16
+ connectors?: Record<string, string>;
17
+ }
18
+ export declare function loadConfig(configPath?: string): OperatorConfig;
package/dist/config.js ADDED
@@ -0,0 +1,57 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ const DEFAULT_ALERTS = {
4
+ lowBalance: true,
5
+ highValueTx: true,
6
+ newCounterparty: true,
7
+ policyDenied: true,
8
+ dailySummary: true,
9
+ sessionOpened: false,
10
+ };
11
+ export function loadConfig(configPath) {
12
+ const filePath = configPath ?? resolve(process.cwd(), "agent-operator.json");
13
+ if (!existsSync(filePath)) {
14
+ throw new Error(`Config not found at ${filePath}. Run 'npx agent-operator init' to create one.`);
15
+ }
16
+ const raw = readFileSync(filePath, "utf-8");
17
+ let config;
18
+ try {
19
+ config = JSON.parse(raw);
20
+ }
21
+ catch {
22
+ throw new Error(`Invalid JSON in ${filePath}`);
23
+ }
24
+ validateConfig(config);
25
+ applyDefaults(config);
26
+ return config;
27
+ }
28
+ function validateConfig(config) {
29
+ if (!config.wallets || typeof config.wallets !== "object") {
30
+ throw new Error("Config must have a 'wallets' object");
31
+ }
32
+ const validChains = ["base", "solana", "tempo"];
33
+ for (const [name, wallet] of Object.entries(config.wallets)) {
34
+ if (!wallet.walletId) {
35
+ throw new Error(`Wallet '${name}' is missing 'walletId'`);
36
+ }
37
+ if (!Array.isArray(wallet.chains) || wallet.chains.length === 0) {
38
+ throw new Error(`Wallet '${name}' must have at least one chain`);
39
+ }
40
+ for (const chain of wallet.chains) {
41
+ if (!validChains.includes(chain)) {
42
+ throw new Error(`Wallet '${name}' has unknown chain '${chain}'. Valid: ${validChains.join(", ")}`);
43
+ }
44
+ }
45
+ if (!wallet.notifications?.provider) {
46
+ throw new Error(`Wallet '${name}' is missing notification provider`);
47
+ }
48
+ }
49
+ }
50
+ function applyDefaults(config) {
51
+ for (const wallet of Object.values(config.wallets)) {
52
+ wallet.notifications.alerts = {
53
+ ...DEFAULT_ALERTS,
54
+ ...wallet.notifications.alerts,
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,12 @@
1
+ import type { Connector, Session, SessionResult, Balance } from "./types.js";
2
+ export declare class BaseConnector implements Connector {
3
+ chain: string;
4
+ private sessions;
5
+ openSession(params: {
6
+ service: string;
7
+ maxBudget: number;
8
+ walletId: string;
9
+ }): Promise<Session>;
10
+ closeSession(sessionId: string): Promise<SessionResult>;
11
+ getBalance(walletId: string): Promise<Balance>;
12
+ }
@@ -0,0 +1,119 @@
1
+ import { Mppx } from "mppx/client";
2
+ import { Method, Credential } from "mppx";
3
+ import { signTransaction, getWallet } from "@open-wallet-standard/core";
4
+ // Base chain ID for EIP-155
5
+ const BASE_CHAIN_ID = "eip155:8453";
6
+ export class BaseConnector {
7
+ chain = "base";
8
+ sessions = new Map();
9
+ async openSession(params) {
10
+ const { service, maxBudget, walletId } = params;
11
+ const wallet = getWallet(walletId);
12
+ const baseAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155"));
13
+ if (!baseAccount) {
14
+ throw new Error(`Wallet '${walletId}' has no EVM account for Base`);
15
+ }
16
+ // define a custom charge method for Base
17
+ const baseChargeMethod = Method.from({
18
+ name: "base",
19
+ intent: "charge",
20
+ schema: {
21
+ credential: {
22
+ payload: {
23
+ "~standard": { version: 1 },
24
+ parse: (v) => v,
25
+ },
26
+ },
27
+ request: {
28
+ "~standard": { version: 1 },
29
+ parse: (v) => v,
30
+ },
31
+ },
32
+ });
33
+ const clientMethod = Method.toClient(baseChargeMethod, {
34
+ async createCredential({ challenge }) {
35
+ // sign the payment via OWS (policies enforced here)
36
+ const txData = JSON.stringify({
37
+ challengeId: challenge.id,
38
+ from: baseAccount.address,
39
+ amount: challenge.request?.amount,
40
+ chain: "base",
41
+ });
42
+ const result = signTransaction(walletId, BASE_CHAIN_ID, txData);
43
+ return Credential.serialize({
44
+ challenge,
45
+ payload: { signature: result.signature },
46
+ source: `did:pkh:eip155:8453:${baseAccount.address}`,
47
+ });
48
+ },
49
+ });
50
+ const mppxClient = Mppx.create({
51
+ methods: [clientMethod],
52
+ });
53
+ // initiate the 402 flow
54
+ const response = await mppxClient.fetch(service, {
55
+ headers: { "X-Max-Budget": String(maxBudget) },
56
+ });
57
+ const sessionId = crypto.randomUUID();
58
+ this.sessions.set(sessionId, {
59
+ sessionId,
60
+ service,
61
+ maxBudget,
62
+ spent: 0,
63
+ mppxClient,
64
+ });
65
+ return {
66
+ sessionId,
67
+ chain: this.chain,
68
+ service,
69
+ maxBudget,
70
+ spent: 0,
71
+ status: "active",
72
+ };
73
+ }
74
+ async closeSession(sessionId) {
75
+ const session = this.sessions.get(sessionId);
76
+ if (!session) {
77
+ throw new Error(`Session '${sessionId}' not found`);
78
+ }
79
+ this.sessions.delete(sessionId);
80
+ return {
81
+ sessionId,
82
+ totalSpent: session.spent,
83
+ refund: session.maxBudget - session.spent,
84
+ };
85
+ }
86
+ async getBalance(walletId) {
87
+ const wallet = getWallet(walletId);
88
+ const baseAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155"));
89
+ if (!baseAccount) {
90
+ return { chain: this.chain, amount: 0, currency: "USDC" };
91
+ }
92
+ try {
93
+ const { createPublicClient, http, parseAbi, formatUnits } = await import("viem");
94
+ const { base } = await import("viem/chains");
95
+ const client = createPublicClient({
96
+ chain: base,
97
+ transport: http(),
98
+ });
99
+ // USDC on Base
100
+ const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
101
+ const balance = await client.readContract({
102
+ address: USDC_ADDRESS,
103
+ abi: parseAbi([
104
+ "function balanceOf(address) view returns (uint256)",
105
+ ]),
106
+ functionName: "balanceOf",
107
+ args: [baseAccount.address],
108
+ });
109
+ return {
110
+ chain: this.chain,
111
+ amount: Number(formatUnits(balance, 6)),
112
+ currency: "USDC",
113
+ };
114
+ }
115
+ catch {
116
+ return { chain: this.chain, amount: 0, currency: "USDC" };
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,12 @@
1
+ import type { Connector, Session, SessionResult, Balance } from "./types.js";
2
+ export declare class SolanaConnector implements Connector {
3
+ chain: string;
4
+ private sessions;
5
+ openSession(params: {
6
+ service: string;
7
+ maxBudget: number;
8
+ walletId: string;
9
+ }): Promise<Session>;
10
+ closeSession(sessionId: string): Promise<SessionResult>;
11
+ getBalance(walletId: string): Promise<Balance>;
12
+ }