@circuit-llm/wallet 0.2.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Circuit LLM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @circuit-llm/wallet
2
+
3
+ > A Solana wallet for the Circuit network: SOL + CIRC (Token-2022) balances, transfers, and Jupiter swaps — and the concrete `PaymentWallet` that powers x402.
4
+
5
+ Part of the **[Circuit SDK](https://github.com/Circuit-LLM/circuit-sdk)**. [Packages →](https://github.com/Circuit-LLM/circuit-sdk/blob/main/docs/packages.md)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @circuit-llm/wallet
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { makeWallet, keypairFromSecret } from '@circuit-llm/wallet';
17
+
18
+ const wallet = makeWallet(); // from CIRCUIT_WALLET
19
+ // const wallet = makeWallet({ keypair: keypairFromSecret(secret) });
20
+
21
+ await wallet.solBalance();
22
+ await wallet.circBalance();
23
+ await wallet.sendCirc(recipient, 1_000_000n); // (to, amountRaw) — base units, 6 decimals
24
+ await wallet.swap(inputMint, outputMint, amount); // (inMint, outMint, amount) via Jupiter
25
+ ```
26
+
27
+ > Swaps use Jupiter's free endpoint, which rate-limits hard (`429`). For real usage, pass a Jupiter API key — `makeWallet({ jupiterApiKey })` or the `JUPITER_API_KEY` env var — to use the keyed host.
28
+
29
+ Implements `@circuit-llm/x402`'s `PaymentWallet`, so it drops straight into `Inference` / `Data`. Multi-RPC failover is built in; an underfunded send surfaces as a typed **`InsufficientFundsError`** (which token, and the shortfall) instead of an opaque chain error, and the wallet warns once if it's on the rate-limited default public RPC — pass `rpcUrl` (or set `CIRCUIT_RPC_URL`) to override. Also exports `generateKeypair`, `loadKeypairFromEnv`, `isValidAddress`, and `walletTradeExecutor` (self-custody trading for agents).
@@ -0,0 +1,107 @@
1
+ import { Keypair, Connection } from '@solana/web3.js';
2
+ import { CircuitConfig } from '@circuit-llm/core';
3
+ import { PaymentWallet } from '@circuit-llm/x402';
4
+
5
+ /** Parse a secret key from base58, a JSON byte-array string, an array, or raw bytes. */
6
+ declare function keypairFromSecret(input: string | number[] | Uint8Array): Keypair;
7
+ /** Load the signing keypair from the CIRCUIT_WALLET env var; null if unset. */
8
+ declare function loadKeypairFromEnv(env?: NodeJS.ProcessEnv): Keypair | null;
9
+ declare function generateKeypair(): Keypair;
10
+ declare const secretKeyBase58: (kp: Keypair) => string;
11
+ declare function isValidAddress(s: string): boolean;
12
+
13
+ /** The fetch shape the wallet uses — injectable for tests, defaults to the global `fetch`. */
14
+ type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
15
+ interface WalletOptions {
16
+ keypair?: Keypair | null;
17
+ /** Read-only mode: watch this address (no signing). */
18
+ address?: string;
19
+ config?: CircuitConfig;
20
+ /** Inject a connection (for tests / custom RPC); else built from rpcUrl/config. */
21
+ connection?: Connection;
22
+ /** Inject a list of connections to fail over across (tests / advanced); else [primary, ...fallbacks]. */
23
+ connections?: Connection[];
24
+ rpcUrl?: string;
25
+ /** Jupiter API key for swaps — lifts the free tier's rate limit. Falls back to `JUPITER_API_KEY`. */
26
+ jupiterApiKey?: string;
27
+ /** Override the Jupiter swap base URL (else the keyed host when a key is set, else the free `lite-api`). */
28
+ jupiterBaseUrl?: string;
29
+ /** Inject a `fetch` implementation (tests / custom transport); else the global `fetch`. */
30
+ fetchImpl?: FetchLike;
31
+ }
32
+ declare class Wallet implements PaymentWallet {
33
+ readonly keypair: Keypair | null;
34
+ readonly connection: Connection;
35
+ private readonly connections;
36
+ readonly address: string | null;
37
+ readonly readOnly: boolean;
38
+ private readonly circMint;
39
+ private readonly tokenProgram;
40
+ private readonly decimals;
41
+ private readonly pubkey;
42
+ private readonly jupiterBase;
43
+ private readonly jupiterKey;
44
+ private readonly fetchImpl;
45
+ constructor(opts?: WalletOptions);
46
+ solBalance(): Promise<number | null>;
47
+ circBalance(): Promise<number>;
48
+ /** PaymentWallet.sendCirc — transfer CIRC (Token-2022). amountRaw = base units. */
49
+ sendCirc(toAddress: string, amountRaw: bigint): Promise<string>;
50
+ sendSol(toAddress: string, sol: number): Promise<string>;
51
+ /** Jupiter quote (read-only). amount = base units of inputMint. */
52
+ swapQuote(inputMint: string, outputMint: string, amount: bigint | number, slippageBps?: number): Promise<unknown>;
53
+ /** Jupiter request headers — adds `x-api-key` when a key is configured. */
54
+ private jupHeaders;
55
+ /** Execute a swap via Jupiter (sign + send the returned versioned tx). */
56
+ swap(inputMint: string, outputMint: string, amount: bigint | number, slippageBps?: number): Promise<{
57
+ sig: string;
58
+ quote: unknown;
59
+ }>;
60
+ private sendSigned;
61
+ private withRpc;
62
+ private classifyFundsError;
63
+ private circBalanceRawStrict;
64
+ private solLamports;
65
+ private requireKeypair;
66
+ }
67
+ /** Build a Wallet, loading the keypair from CIRCUIT_WALLET when none is given. */
68
+ declare function makeWallet(opts?: WalletOptions): Wallet;
69
+
70
+ /** The buy/sell shape LocalKeypairCustody hands the executor — a structural subset of @circuit-llm/agent's `Intent`. */
71
+ interface TradeIntent {
72
+ kind: 'buy' | 'sell';
73
+ /** Token mint to trade. */
74
+ token?: string;
75
+ /** SOL notional for a buy (also the accounted `solValue`). */
76
+ sizeSol?: number;
77
+ /** Token base units for a live sell. */
78
+ amount?: number;
79
+ maxSlippageBps?: number;
80
+ }
81
+ interface WalletTradeResult {
82
+ signature: string;
83
+ solValue?: number;
84
+ }
85
+ interface WalletExecutor {
86
+ execute(intent: TradeIntent): Promise<WalletTradeResult>;
87
+ }
88
+ /**
89
+ * Build a self-custody executor from a keyed {@link Wallet}. `buy` swaps SOL → token for `sizeSol`;
90
+ * `sell` swaps `amount` (token base units) → SOL. It signs + sends with the wallet's own keypair, so
91
+ * it is only appropriate on hardware you control. Plug it in with
92
+ * `new LocalKeypairCustody({ executor: walletTradeExecutor(wallet), paper: false })`.
93
+ */
94
+ declare function walletTradeExecutor(wallet: Wallet): WalletExecutor;
95
+
96
+ /**
97
+ * Thrown when a transfer fails because the wallet is underfunded — instead of the opaque Solana
98
+ * transaction error the RPC returns. `haveRaw`/`needRaw` are base units (CIRC: 6 decimals; SOL: lamports).
99
+ */
100
+ declare class InsufficientFundsError extends Error {
101
+ readonly token: 'CIRC' | 'SOL';
102
+ readonly haveRaw: bigint;
103
+ readonly needRaw: bigint;
104
+ constructor(token: 'CIRC' | 'SOL', haveRaw: bigint, needRaw: bigint);
105
+ }
106
+
107
+ export { type FetchLike, InsufficientFundsError, type TradeIntent, Wallet, type WalletExecutor, type WalletOptions, type WalletTradeResult, generateKeypair, isValidAddress, keypairFromSecret, loadKeypairFromEnv, makeWallet, secretKeyBase58, walletTradeExecutor };
package/dist/index.js ADDED
@@ -0,0 +1,328 @@
1
+ // src/keypair.ts
2
+ import { Keypair, PublicKey } from "@solana/web3.js";
3
+ import bs58 from "bs58";
4
+ function keypairFromSecret(input) {
5
+ if (input instanceof Uint8Array) return Keypair.fromSecretKey(input);
6
+ if (Array.isArray(input)) return Keypair.fromSecretKey(Uint8Array.from(input));
7
+ const s = String(input).trim();
8
+ if (s.startsWith("[")) return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(s)));
9
+ return Keypair.fromSecretKey(bs58.decode(s));
10
+ }
11
+ function loadKeypairFromEnv(env = process.env) {
12
+ const v = env.CIRCUIT_WALLET;
13
+ if (!v) return null;
14
+ try {
15
+ return keypairFromSecret(v.trim());
16
+ } catch {
17
+ throw new Error("CIRCUIT_WALLET is set but is not a valid base58/array secret key");
18
+ }
19
+ }
20
+ function generateKeypair() {
21
+ return Keypair.generate();
22
+ }
23
+ var secretKeyBase58 = (kp) => bs58.encode(kp.secretKey);
24
+ function isValidAddress(s) {
25
+ try {
26
+ new PublicKey(s);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ // src/wallet.ts
34
+ import {
35
+ Connection,
36
+ PublicKey as PublicKey2,
37
+ LAMPORTS_PER_SOL,
38
+ Transaction,
39
+ SystemProgram,
40
+ VersionedTransaction
41
+ } from "@solana/web3.js";
42
+ import {
43
+ getAssociatedTokenAddressSync,
44
+ createTransferCheckedInstruction,
45
+ createAssociatedTokenAccountIdempotentInstruction
46
+ } from "@solana/spl-token";
47
+ import { DEFAULT_CONFIG as DEFAULT_CONFIG2 } from "@circuit-llm/core";
48
+
49
+ // src/errors.ts
50
+ var DECIMALS = { CIRC: 6, SOL: 9 };
51
+ function formatAmount(raw, decimals) {
52
+ const negative = raw < 0n;
53
+ const abs = negative ? -raw : raw;
54
+ const base = 10n ** BigInt(decimals);
55
+ const whole = abs / base;
56
+ const frac = abs % base;
57
+ let out = whole.toString();
58
+ if (frac > 0n) out += `.${frac.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
59
+ return negative ? `-${out}` : out;
60
+ }
61
+ var InsufficientFundsError = class extends Error {
62
+ token;
63
+ haveRaw;
64
+ needRaw;
65
+ constructor(token, haveRaw, needRaw) {
66
+ const d = DECIMALS[token];
67
+ super(
68
+ `Insufficient ${token}: have ${formatAmount(haveRaw, d)}, need ${formatAmount(needRaw, d)} ${token}. Fund the wallet and retry.`
69
+ );
70
+ this.name = "InsufficientFundsError";
71
+ this.token = token;
72
+ this.haveRaw = haveRaw;
73
+ this.needRaw = needRaw;
74
+ }
75
+ };
76
+
77
+ // src/rpc-warning.ts
78
+ import { DEFAULT_CONFIG } from "@circuit-llm/core";
79
+ function usesDefaultPublicRpc(opts) {
80
+ if (opts.connection || opts.connections || opts.rpcUrl) return false;
81
+ return (opts.config?.rpcUrl ?? DEFAULT_CONFIG.rpcUrl) === DEFAULT_CONFIG.rpcUrl;
82
+ }
83
+ var warned = false;
84
+ function warnIfDefaultPublicRpc(opts) {
85
+ if (warned) return;
86
+ if (!usesDefaultPublicRpc(opts)) return;
87
+ if (process.env.CIRCUIT_SUPPRESS_RPC_WARNING === "1") return;
88
+ warned = true;
89
+ console.warn(
90
+ "[circuit-sdk] Using the public Solana RPC (api.mainnet-beta.solana.com), which rate-limits \u2014 calls and payments may fail under load. Set your own with makeWallet({ rpcUrl }) or the CIRCUIT_RPC_URL env var. Silence this with CIRCUIT_SUPPRESS_RPC_WARNING=1."
91
+ );
92
+ }
93
+
94
+ // src/wallet.ts
95
+ var JUP_LITE = "https://lite-api.jup.ag/swap/v1";
96
+ var JUP_PRO = "https://api.jup.ag/swap/v1";
97
+ var FALLBACK_RPCS = ["https://api.mainnet-beta.solana.com", "https://rpc.ankr.com/solana"];
98
+ var isRateLimited = (e) => /429|Too Many Requests|max usage|rate limit/i.test(e?.message ?? "");
99
+ var BASE_FEE_LAMPORTS = 5000n;
100
+ var isMissingAccount = (e) => /could not find account|account (does not exist|not found)|find account|no ata/i.test(
101
+ e?.message ?? ""
102
+ );
103
+ var Wallet = class {
104
+ keypair;
105
+ connection;
106
+ // the primary (back-compat); reads/sends fail over across `connections`
107
+ connections;
108
+ address;
109
+ readOnly;
110
+ circMint;
111
+ tokenProgram;
112
+ decimals;
113
+ pubkey;
114
+ jupiterBase;
115
+ jupiterKey;
116
+ fetchImpl;
117
+ constructor(opts = {}) {
118
+ const cfg = opts.config ?? DEFAULT_CONFIG2;
119
+ this.keypair = opts.keypair ?? null;
120
+ this.pubkey = this.keypair ? this.keypair.publicKey : opts.address ? new PublicKey2(opts.address) : null;
121
+ this.address = this.pubkey ? this.pubkey.toBase58() : null;
122
+ this.readOnly = !this.keypair;
123
+ const primary = opts.rpcUrl ?? cfg.rpcUrl;
124
+ const urls = [primary, ...FALLBACK_RPCS].filter((u, i, a) => !!u && a.indexOf(u) === i);
125
+ this.connections = opts.connections ?? (opts.connection ? [opts.connection] : urls.map((u) => new Connection(u, { commitment: "confirmed", disableRetryOnRateLimit: true })));
126
+ this.connection = this.connections[0];
127
+ this.circMint = new PublicKey2(cfg.circMint);
128
+ this.tokenProgram = new PublicKey2(cfg.circTokenProgram);
129
+ this.decimals = cfg.circDecimals;
130
+ this.jupiterKey = opts.jupiterApiKey ?? process.env.JUPITER_API_KEY ?? null;
131
+ this.jupiterBase = (opts.jupiterBaseUrl ?? (this.jupiterKey ? JUP_PRO : JUP_LITE)).replace(/\/$/, "");
132
+ this.fetchImpl = opts.fetchImpl ?? ((input, init) => fetch(input, init));
133
+ warnIfDefaultPublicRpc(opts);
134
+ }
135
+ async solBalance() {
136
+ if (!this.pubkey) return null;
137
+ const pk = this.pubkey;
138
+ const lamports = await this.withRpc((c) => c.getBalance(pk, "confirmed"));
139
+ return lamports / LAMPORTS_PER_SOL;
140
+ }
141
+ async circBalance() {
142
+ if (!this.pubkey) return 0;
143
+ const ata = getAssociatedTokenAddressSync(this.circMint, this.pubkey, false, this.tokenProgram);
144
+ try {
145
+ const r = await this.withRpc((c) => c.getTokenAccountBalance(ata, "confirmed"));
146
+ return Number(r.value.amount) / 10 ** this.decimals;
147
+ } catch {
148
+ return 0;
149
+ }
150
+ }
151
+ /** PaymentWallet.sendCirc — transfer CIRC (Token-2022). amountRaw = base units. */
152
+ async sendCirc(toAddress, amountRaw) {
153
+ const kp = this.requireKeypair();
154
+ const to = new PublicKey2(toAddress);
155
+ const fromAta = getAssociatedTokenAddressSync(this.circMint, kp.publicKey, false, this.tokenProgram);
156
+ const toAta = getAssociatedTokenAddressSync(this.circMint, to, false, this.tokenProgram);
157
+ const tx = new Transaction().add(
158
+ createAssociatedTokenAccountIdempotentInstruction(kp.publicKey, toAta, to, this.circMint, this.tokenProgram),
159
+ createTransferCheckedInstruction(fromAta, this.circMint, toAta, kp.publicKey, amountRaw, this.decimals, [], this.tokenProgram)
160
+ );
161
+ try {
162
+ return await this.sendSigned(tx, kp);
163
+ } catch (err) {
164
+ throw await this.classifyFundsError({ circRaw: amountRaw }) ?? err;
165
+ }
166
+ }
167
+ async sendSol(toAddress, sol) {
168
+ const kp = this.requireKeypair();
169
+ const lamports = Math.round(sol * LAMPORTS_PER_SOL);
170
+ const tx = new Transaction().add(
171
+ SystemProgram.transfer({ fromPubkey: kp.publicKey, toPubkey: new PublicKey2(toAddress), lamports })
172
+ );
173
+ try {
174
+ return await this.sendSigned(tx, kp);
175
+ } catch (err) {
176
+ throw await this.classifyFundsError({ solLamports: BigInt(lamports) + BASE_FEE_LAMPORTS }) ?? err;
177
+ }
178
+ }
179
+ /** Jupiter quote (read-only). amount = base units of inputMint. */
180
+ async swapQuote(inputMint, outputMint, amount, slippageBps = 100) {
181
+ const u = `${this.jupiterBase}/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${slippageBps}&restrictIntermediateTokens=true`;
182
+ const resp = await this.fetchImpl(u, { headers: this.jupHeaders(), signal: AbortSignal.timeout(12e3) });
183
+ if (!resp.ok) throw new Error(`Jupiter quote ${resp.status}`);
184
+ return resp.json();
185
+ }
186
+ /** Jupiter request headers — adds `x-api-key` when a key is configured. */
187
+ jupHeaders(extra) {
188
+ return { ...this.jupiterKey ? { "x-api-key": this.jupiterKey } : {}, ...extra };
189
+ }
190
+ /** Execute a swap via Jupiter (sign + send the returned versioned tx). */
191
+ async swap(inputMint, outputMint, amount, slippageBps = 100) {
192
+ const kp = this.requireKeypair();
193
+ const quote = await this.swapQuote(inputMint, outputMint, amount, slippageBps);
194
+ const swapResp = await this.fetchImpl(`${this.jupiterBase}/swap`, {
195
+ method: "POST",
196
+ headers: this.jupHeaders({ "Content-Type": "application/json" }),
197
+ body: JSON.stringify({
198
+ quoteResponse: quote,
199
+ userPublicKey: this.address,
200
+ wrapAndUnwrapSol: true,
201
+ dynamicComputeUnitLimit: true
202
+ }),
203
+ signal: AbortSignal.timeout(2e4)
204
+ });
205
+ if (!swapResp.ok) throw new Error(`Jupiter swap ${swapResp.status}`);
206
+ const { swapTransaction } = await swapResp.json();
207
+ const tx = VersionedTransaction.deserialize(Buffer.from(swapTransaction, "base64"));
208
+ tx.sign([kp]);
209
+ const sig = await this.withRpc(async (c) => {
210
+ const s = await c.sendRawTransaction(tx.serialize(), { maxRetries: 3 });
211
+ await c.confirmTransaction(s, "confirmed");
212
+ return s;
213
+ });
214
+ return { sig, quote };
215
+ }
216
+ // Sign a legacy Transaction ONCE against a fresh blockhash, then broadcast the fixed-signature bytes.
217
+ // Critical for failover: because the signature is fixed, a retry on another RPC re-broadcasts the SAME
218
+ // transaction (Solana dedups by signature) — it can never produce a second, differently-signed tx. If
219
+ // we instead handed an unsigned tx to sendAndConfirmTransaction inside withRpc, each RPC would fetch a
220
+ // new blockhash and re-sign → two transactions could land (a double-spend).
221
+ async sendSigned(tx, kp) {
222
+ const { blockhash } = await this.withRpc((c) => c.getLatestBlockhash("confirmed"));
223
+ tx.recentBlockhash = blockhash;
224
+ tx.feePayer = kp.publicKey;
225
+ tx.sign(kp);
226
+ const raw = tx.serialize();
227
+ return this.withRpc(async (c) => {
228
+ const sig = await c.sendRawTransaction(raw, { maxRetries: 3 });
229
+ await c.confirmTransaction(sig, "confirmed");
230
+ return sig;
231
+ });
232
+ }
233
+ // Try each RPC in turn; advance to the next on a rate-limit error OR a per-try timeout (a capped RPC
234
+ // sometimes hangs rather than throwing). A real (non-rate-limit) error propagates immediately.
235
+ async withRpc(fn, perTryMs = 25e3) {
236
+ let last;
237
+ for (const conn of this.connections) {
238
+ try {
239
+ return await Promise.race([
240
+ fn(conn),
241
+ new Promise((_, reject) => {
242
+ setTimeout(() => reject(Object.assign(new Error("rpc timeout"), { _timeout: true })), perTryMs).unref();
243
+ })
244
+ ]);
245
+ } catch (e) {
246
+ last = e;
247
+ if (!isRateLimited(e) && !e?._timeout) throw e;
248
+ }
249
+ }
250
+ throw last;
251
+ }
252
+ // After a send fails, read balances to see whether it was actually a funds problem — and if so, say so
253
+ // clearly. Returns an InsufficientFundsError to throw, or null to let the original send error propagate.
254
+ // A probe that itself fails returns null, so a real error is never masked by a misattributed message.
255
+ async classifyFundsError(need) {
256
+ try {
257
+ if (need.circRaw != null) {
258
+ const have = await this.circBalanceRawStrict();
259
+ if (have < need.circRaw) return new InsufficientFundsError("CIRC", have, need.circRaw);
260
+ }
261
+ const solNeed = need.solLamports ?? BASE_FEE_LAMPORTS;
262
+ const haveSol = await this.solLamports();
263
+ if (haveSol < solNeed) return new InsufficientFundsError("SOL", haveSol, solNeed);
264
+ return null;
265
+ } catch {
266
+ return null;
267
+ }
268
+ }
269
+ // Raw CIRC balance in base units. 0 for a genuinely-missing ATA; a real RPC failure is re-thrown.
270
+ async circBalanceRawStrict() {
271
+ if (!this.pubkey) return 0n;
272
+ const ata = getAssociatedTokenAddressSync(this.circMint, this.pubkey, false, this.tokenProgram);
273
+ try {
274
+ const r = await this.withRpc((c) => c.getTokenAccountBalance(ata, "confirmed"));
275
+ return BigInt(r.value.amount);
276
+ } catch (e) {
277
+ if (isMissingAccount(e)) return 0n;
278
+ throw e;
279
+ }
280
+ }
281
+ // Raw SOL balance in lamports.
282
+ async solLamports() {
283
+ if (!this.pubkey) return 0n;
284
+ const pk = this.pubkey;
285
+ return BigInt(await this.withRpc((c) => c.getBalance(pk, "confirmed")));
286
+ }
287
+ requireKeypair() {
288
+ if (!this.keypair) throw new Error("No wallet loaded \u2014 pass a keypair or set CIRCUIT_WALLET");
289
+ return this.keypair;
290
+ }
291
+ };
292
+ function makeWallet(opts = {}) {
293
+ return new Wallet({ ...opts, keypair: opts.keypair ?? loadKeypairFromEnv() });
294
+ }
295
+
296
+ // src/executor.ts
297
+ import { SOL_MINT } from "@circuit-llm/core";
298
+ var LAMPORTS_PER_SOL2 = 1e9;
299
+ function walletTradeExecutor(wallet) {
300
+ return {
301
+ async execute(intent) {
302
+ if (!intent.token) throw new Error("trade intent is missing a token mint");
303
+ const slippageBps = intent.maxSlippageBps;
304
+ if (intent.kind === "buy") {
305
+ const sol = intent.sizeSol;
306
+ if (!(typeof sol === "number" && sol > 0)) throw new Error("buy intent is missing a positive sizeSol");
307
+ const lamports = BigInt(Math.round(sol * LAMPORTS_PER_SOL2));
308
+ const { sig: sig2 } = await wallet.swap(SOL_MINT, intent.token, lamports, slippageBps);
309
+ return { signature: sig2, solValue: sol };
310
+ }
311
+ const amount = intent.amount;
312
+ if (!(typeof amount === "number" && amount > 0)) throw new Error("sell intent is missing a positive amount (token base units)");
313
+ const { sig } = await wallet.swap(intent.token, SOL_MINT, amount, slippageBps);
314
+ return { signature: sig };
315
+ }
316
+ };
317
+ }
318
+ export {
319
+ InsufficientFundsError,
320
+ Wallet,
321
+ generateKeypair,
322
+ isValidAddress,
323
+ keypairFromSecret,
324
+ loadKeypairFromEnv,
325
+ makeWallet,
326
+ secretKeyBase58,
327
+ walletTradeExecutor
328
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@circuit-llm/wallet",
3
+ "version": "0.2.1",
4
+ "description": "Circuit SDK wallet — SOL/CIRC balances, transfers, and Jupiter swaps. Implements @circuit-llm/x402's PaymentWallet.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "development": "./src/index.ts",
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "test": "node --experimental-strip-types --conditions=development --test test/*.test.ts",
16
+ "typecheck": "tsc -p tsconfig.json",
17
+ "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist",
18
+ "prepack": "tsup src/index.ts --format esm --dts --clean --out-dir dist"
19
+ },
20
+ "dependencies": {
21
+ "@circuit-llm/core": "0.2.1",
22
+ "@circuit-llm/x402": "0.2.1",
23
+ "@solana/web3.js": "^1.98.0",
24
+ "@solana/spl-token": "^0.4.14",
25
+ "bs58": "^6.0.0"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Circuit-LLM/circuit-sdk.git",
38
+ "directory": "packages/wallet"
39
+ },
40
+ "homepage": "https://github.com/Circuit-LLM/circuit-sdk/tree/main/packages/wallet#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/Circuit-LLM/circuit-sdk/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ }
47
+ }