ncrypted 0.1.1 → 0.1.2

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/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
+ import { privateKeyToAccount } from "viem/accounts";
3
4
  import { Ncrypted } from "./client.js";
5
+ import { createWallet, normalizeKey, readWalletKey, usdcBalance, walletPath } from "./wallet.js";
4
6
  const HELP = `ncrypted - one-time, end-to-end encrypted secrets for agents (0.005 USDC per secret, reads free)
5
7
 
6
8
  Usage:
@@ -9,8 +11,12 @@ Usage:
9
11
  ncrypted read <link> [--passphrase X]
10
12
  ncrypted status <id> --receipt <token>
11
13
  ncrypted burn <id> --receipt <token>
14
+ ncrypted wallet new create a payer wallet at ~/.config/ncrypted/wallet.key
15
+ ncrypted wallet show the payer address and its USDC balance on Base
12
16
 
13
- Env: NCRYPTED_WALLET_PRIVATE_KEY (Base wallet with USDC, needed for create), NCRYPTED_URL (optional)`;
17
+ Creating a secret needs a Base wallet holding a little USDC (no ETH needed). The key is read from
18
+ NCRYPTED_WALLET_PRIVATE_KEY, else NCRYPTED_WALLET_FILE, else ~/.config/ncrypted/wallet.key.
19
+ NCRYPTED_URL overrides the API origin.`;
14
20
  function parseTtl(v) {
15
21
  if (!v)
16
22
  return undefined;
@@ -68,6 +74,24 @@ async function main() {
68
74
  await client.burn(arg, values.receipt);
69
75
  out({ burned: true });
70
76
  break;
77
+ case "wallet": {
78
+ if (arg === "new") {
79
+ const { address, path } = await createWallet().catch((e) => {
80
+ if (e.code === "EEXIST")
81
+ throw new Error(`A wallet already exists at ${walletPath()}`);
82
+ throw e;
83
+ });
84
+ out({ address, path, next: "Send a little USDC on Base to this address. Each secret costs 0.005 USDC; no ETH needed." });
85
+ break;
86
+ }
87
+ const key = normalizeKey(process.env.NCRYPTED_WALLET_PRIVATE_KEY) ?? (await readWalletKey());
88
+ if (!key)
89
+ throw new Error("No wallet configured. Run `npx ncrypted wallet new`.");
90
+ const address = privateKeyToAccount(key).address;
91
+ const usdc = await usdcBalance(address);
92
+ out({ address, usdc, secretsAvailable: Math.floor(usdc / 0.005) });
93
+ break;
94
+ }
71
95
  default:
72
96
  console.log(HELP);
73
97
  }
package/dist/client.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- export declare const DEFAULT_BASE_URL = "https://ncrypted-ai.vercel.app";
1
+ export declare const DEFAULT_BASE_URL = "https://ncrypted.ai";
2
2
  /** Hard cap on what the SDK will ever pay for one secret, whatever the server asks. */
3
3
  export declare const MAX_PRICE = "$0.01";
4
4
  export interface NcryptedOptions {
5
5
  /** API origin. Defaults to NCRYPTED_URL or the hosted service. */
6
6
  baseUrl?: string;
7
- /** 0x-prefixed EVM private key of a wallet holding USDC on Base. Defaults to NCRYPTED_WALLET_PRIVATE_KEY. */
7
+ /**
8
+ * 0x-prefixed EVM private key of a wallet holding USDC on Base. Defaults to NCRYPTED_WALLET_PRIVATE_KEY,
9
+ * then the key file at NCRYPTED_WALLET_FILE or ~/.config/ncrypted/wallet.key (create one with `npx ncrypted wallet new`).
10
+ */
8
11
  privateKey?: string;
9
12
  /** Bring your own x402-paying fetch instead of a private key. */
10
13
  paymentFetch?: typeof fetch;
@@ -36,9 +39,12 @@ export declare class NcryptedError extends Error {
36
39
  constructor(message: string, status: number);
37
40
  }
38
41
  export declare class Ncrypted {
42
+ private readonly opts;
39
43
  readonly baseUrl: string;
40
44
  private payFetch?;
41
45
  constructor(opts?: NcryptedOptions);
46
+ /** Resolves the paying fetch lazily, so a missing or bad wallet only affects create(), never reads. */
47
+ private payer;
42
48
  /** Encrypts locally, pays 0.005 USDC via x402, stores the ciphertext and returns a one-time link. */
43
49
  create(secret: string, opts?: CreateOptions): Promise<CreatedSecret>;
44
50
  /** Sends the link by email once. Allowed only for the creator and only once per secret. */
package/dist/client.js CHANGED
@@ -2,7 +2,8 @@ import { wrapFetchWithPaymentFromConfig, decodePaymentResponseHeader } from "@x4
2
2
  import { ExactEvmScheme } from "@x402/evm";
3
3
  import { privateKeyToAccount } from "viem/accounts";
4
4
  import { encrypt, decrypt, parseLink } from "./crypto.js";
5
- export const DEFAULT_BASE_URL = "https://ncrypted-ai.vercel.app";
5
+ import { normalizeKey, readWalletKey, walletPath } from "./wallet.js";
6
+ export const DEFAULT_BASE_URL = "https://ncrypted.ai";
6
7
  /** Hard cap on what the SDK will ever pay for one secret, whatever the server asks. */
7
8
  export const MAX_PRICE = "$0.01";
8
9
  export class NcryptedError extends Error {
@@ -42,33 +43,45 @@ async function errorFrom(res) {
42
43
  /** Settlement attempts per create. A failed settlement never moves funds, so retrying is safe. */
43
44
  const SETTLE_ATTEMPTS = 3;
44
45
  export class Ncrypted {
46
+ opts;
45
47
  baseUrl;
46
48
  payFetch;
47
49
  constructor(opts = {}) {
50
+ this.opts = opts;
48
51
  const env = typeof process !== "undefined" ? process.env : {};
49
52
  this.baseUrl = (opts.baseUrl ?? env.NCRYPTED_URL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
50
- const pk = opts.privateKey ?? env.NCRYPTED_WALLET_PRIVATE_KEY;
51
- if (opts.paymentFetch)
52
- this.payFetch = opts.paymentFetch;
53
- else if (pk) {
54
- const account = privateKeyToAccount((pk.startsWith("0x") ? pk : `0x${pk}`));
55
- this.payFetch = wrapFetchWithPaymentFromConfig(fetch, {
56
- schemes: [{ network: "eip155:*", client: new ExactEvmScheme(account) }],
53
+ }
54
+ /** Resolves the paying fetch lazily, so a missing or bad wallet only affects create(), never reads. */
55
+ payer() {
56
+ return (this.payFetch ??= (async () => {
57
+ if (this.opts.paymentFetch)
58
+ return this.opts.paymentFetch;
59
+ const env = typeof process !== "undefined" ? process.env : {};
60
+ const pk = normalizeKey(this.opts.privateKey ?? env.NCRYPTED_WALLET_PRIVATE_KEY) ?? (await readWalletKey());
61
+ if (!pk)
62
+ return undefined;
63
+ return wrapFetchWithPaymentFromConfig(fetch, {
64
+ schemes: [{ network: "eip155:*", client: new ExactEvmScheme(privateKeyToAccount(pk)) }],
57
65
  spendControls: { maxAmountPerPayment: MAX_PRICE },
58
66
  });
59
- }
67
+ })().catch((e) => {
68
+ this.payFetch = undefined;
69
+ throw e;
70
+ }));
60
71
  }
61
72
  /** Encrypts locally, pays 0.005 USDC via x402, stores the ciphertext and returns a one-time link. */
62
73
  async create(secret, opts = {}) {
63
- if (!this.payFetch) {
64
- throw new Error("No wallet configured. Set NCRYPTED_WALLET_PRIVATE_KEY (a Base wallet with USDC) or pass privateKey.");
74
+ const payFetch = await this.payer();
75
+ if (!payFetch) {
76
+ throw new Error(`No wallet configured. Run \`npx ncrypted wallet new\` to create one at ${walletPath()} and send it a little USDC on Base, ` +
77
+ "or set NCRYPTED_WALLET_PRIVATE_KEY.");
65
78
  }
66
79
  const { payload, key } = await encrypt(secret, opts.passphrase);
67
80
  const body = { ...payload, ttl: opts.ttl ?? 86400 };
68
81
  // The link is only knowable after the server assigns an id, so email goes in a second, free call.
69
82
  let res;
70
83
  for (let attempt = 1;; attempt++) {
71
- res = await this.payFetch(`${this.baseUrl}/api/v1/secrets`, {
84
+ res = await payFetch(`${this.baseUrl}/api/v1/secrets`, {
72
85
  method: "POST",
73
86
  headers: { "content-type": "application/json" },
74
87
  body: JSON.stringify(body),
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./client.js";
2
2
  export { encrypt, decrypt, parseLink, MAX_PLAINTEXT_BYTES, type EncryptedPayload } from "./crypto.js";
3
+ export { walletPath, createWallet, usdcBalance } from "./wallet.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./client.js";
2
2
  export { encrypt, decrypt, parseLink, MAX_PLAINTEXT_BYTES } from "./crypto.js";
3
+ export { walletPath, createWallet, usdcBalance } from "./wallet.js";
@@ -0,0 +1,13 @@
1
+ export declare const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
2
+ /** NCRYPTED_WALLET_FILE, or ~/.config/ncrypted/wallet.key */
3
+ export declare function walletPath(): string;
4
+ /** Normalizes a private key, or returns undefined for empty or unexpanded "${VAR}" placeholders. */
5
+ export declare function normalizeKey(raw: string | undefined): `0x${string}` | undefined;
6
+ export declare function readWalletKey(): Promise<`0x${string}` | undefined>;
7
+ /** Creates a new wallet key file (mode 0600). Refuses to overwrite an existing one. */
8
+ export declare function createWallet(): Promise<{
9
+ address: string;
10
+ path: string;
11
+ }>;
12
+ /** USDC balance on Base mainnet, via a public RPC. */
13
+ export declare function usdcBalance(address: string, rpc?: string): Promise<number>;
package/dist/wallet.js ADDED
@@ -0,0 +1,49 @@
1
+ // Node-only helpers for the local payer wallet used to pay for secrets.
2
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
6
+ export const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
7
+ /** NCRYPTED_WALLET_FILE, or ~/.config/ncrypted/wallet.key */
8
+ export function walletPath() {
9
+ return process.env.NCRYPTED_WALLET_FILE || join(homedir(), ".config", "ncrypted", "wallet.key");
10
+ }
11
+ /** Normalizes a private key, or returns undefined for empty or unexpanded "${VAR}" placeholders. */
12
+ export function normalizeKey(raw) {
13
+ const v = raw?.trim();
14
+ if (!v || v.startsWith("${") || v.startsWith("$"))
15
+ return undefined;
16
+ const hex = v.startsWith("0x") ? v : `0x${v}`;
17
+ if (!/^0x[0-9a-fA-F]{64}$/.test(hex))
18
+ throw new Error("Wallet private key must be 32 bytes of hex (0x + 64 hex characters)");
19
+ return hex;
20
+ }
21
+ export async function readWalletKey() {
22
+ try {
23
+ return normalizeKey(await readFile(walletPath(), "utf8"));
24
+ }
25
+ catch (e) {
26
+ if (e.code === "ENOENT")
27
+ return undefined;
28
+ throw e;
29
+ }
30
+ }
31
+ /** Creates a new wallet key file (mode 0600). Refuses to overwrite an existing one. */
32
+ export async function createWallet() {
33
+ const path = walletPath();
34
+ const key = generatePrivateKey();
35
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
36
+ await writeFile(path, `${key}\n`, { mode: 0o600, flag: "wx" });
37
+ return { address: privateKeyToAccount(key).address, path };
38
+ }
39
+ /** USDC balance on Base mainnet, via a public RPC. */
40
+ export async function usdcBalance(address, rpc = "https://mainnet.base.org") {
41
+ const data = `0x70a08231${address.slice(2).toLowerCase().padStart(64, "0")}`;
42
+ const res = await fetch(rpc, {
43
+ method: "POST",
44
+ headers: { "content-type": "application/json" },
45
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_call", params: [{ to: USDC_BASE, data }, "latest"] }),
46
+ });
47
+ const { result } = (await res.json());
48
+ return Number(BigInt(result)) / 1e6;
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ncrypted",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "One-time, end-to-end encrypted secrets for AI agents. Pay 0.005 USDC per secret via x402; the note self-destructs after one read.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,10 @@
14
14
  "./crypto": {
15
15
  "types": "./dist/crypto.d.ts",
16
16
  "default": "./dist/crypto.js"
17
+ },
18
+ "./wallet": {
19
+ "types": "./dist/wallet.d.ts",
20
+ "default": "./dist/wallet.js"
17
21
  }
18
22
  },
19
23
  "bin": {