@tallyforagents/sdk 0.1.0 → 0.1.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":["createHmac","timingSafeEqual"],"mappings":"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","file":"index.cjs","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":["createHmac","timingSafeEqual"],"mappings":"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","file":"index.cjs","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type AgentWallet = {\n /** Wallet address (lowercased EVM hex) the agent can spend from. */\n address: string;\n /** Friendly name the wallet was given in the dashboard. */\n display_name: string;\n /** Optional role label (e.g., \"main\", \"treasury\"); null if unset. */\n role_label: string | null;\n /** Max per-tx spend in USDC as a decimal string. */\n max_per_tx_usdc: string;\n /** Daily cap in USDC as a decimal string, null if uncapped. */\n daily_cap_usdc: string | null;\n};\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** The account slug this agent belongs to. Surfaced so callers can\n * build dashboard deep-links (e.g., when telling the user to grant\n * a permission). */\n account_slug: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n /** Wallets this agent has active permission to spend from, with the\n * per-wallet caps from the permission. Empty array if no active\n * grants. Pick one to pass as `wallet` to tally.payments.create(). */\n wallets: AgentWallet[];\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput, AgentWallet } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
package/dist/index.d.cts CHANGED
@@ -14,6 +14,18 @@ declare class TallyClient {
14
14
  request<T>(method: "GET" | "POST" | "DELETE", path: string, body?: unknown): Promise<T>;
15
15
  }
16
16
 
17
+ type AgentWallet = {
18
+ /** Wallet address (lowercased EVM hex) the agent can spend from. */
19
+ address: string;
20
+ /** Friendly name the wallet was given in the dashboard. */
21
+ display_name: string;
22
+ /** Optional role label (e.g., "main", "treasury"); null if unset. */
23
+ role_label: string | null;
24
+ /** Max per-tx spend in USDC as a decimal string. */
25
+ max_per_tx_usdc: string;
26
+ /** Daily cap in USDC as a decimal string, null if uncapped. */
27
+ daily_cap_usdc: string | null;
28
+ };
17
29
  type Agent = {
18
30
  /** Stable user-provided identifier (e.g., "research-bot"). */
19
31
  id: string;
@@ -24,10 +36,18 @@ type Agent = {
24
36
  mode: "test" | "live";
25
37
  /** ISO 8601 timestamp. */
26
38
  created_at: string;
39
+ /** The account slug this agent belongs to. Surfaced so callers can
40
+ * build dashboard deep-links (e.g., when telling the user to grant
41
+ * a permission). */
42
+ account_slug: string;
27
43
  /** Count of non-revoked, activated signers. */
28
44
  active_signers: number;
29
45
  /** Count of non-revoked, not-yet-activated signers. */
30
46
  pending_signers: number;
47
+ /** Wallets this agent has active permission to spend from, with the
48
+ * per-wallet caps from the permission. Empty array if no active
49
+ * grants. Pick one to pass as `wallet` to tally.payments.create(). */
50
+ wallets: AgentWallet[];
31
51
  };
32
52
  type AgentUpsertInput = {
33
53
  /** User-provided stable string identifier (e.g., "research-bot"). */
@@ -193,4 +213,4 @@ declare class Tally {
193
213
  constructor(opts: ClientOptions);
194
214
  }
195
215
 
196
- export { type Agent, type AgentUpsertInput, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
216
+ export { type Agent, type AgentUpsertInput, type AgentWallet, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
package/dist/index.d.ts CHANGED
@@ -14,6 +14,18 @@ declare class TallyClient {
14
14
  request<T>(method: "GET" | "POST" | "DELETE", path: string, body?: unknown): Promise<T>;
15
15
  }
16
16
 
17
+ type AgentWallet = {
18
+ /** Wallet address (lowercased EVM hex) the agent can spend from. */
19
+ address: string;
20
+ /** Friendly name the wallet was given in the dashboard. */
21
+ display_name: string;
22
+ /** Optional role label (e.g., "main", "treasury"); null if unset. */
23
+ role_label: string | null;
24
+ /** Max per-tx spend in USDC as a decimal string. */
25
+ max_per_tx_usdc: string;
26
+ /** Daily cap in USDC as a decimal string, null if uncapped. */
27
+ daily_cap_usdc: string | null;
28
+ };
17
29
  type Agent = {
18
30
  /** Stable user-provided identifier (e.g., "research-bot"). */
19
31
  id: string;
@@ -24,10 +36,18 @@ type Agent = {
24
36
  mode: "test" | "live";
25
37
  /** ISO 8601 timestamp. */
26
38
  created_at: string;
39
+ /** The account slug this agent belongs to. Surfaced so callers can
40
+ * build dashboard deep-links (e.g., when telling the user to grant
41
+ * a permission). */
42
+ account_slug: string;
27
43
  /** Count of non-revoked, activated signers. */
28
44
  active_signers: number;
29
45
  /** Count of non-revoked, not-yet-activated signers. */
30
46
  pending_signers: number;
47
+ /** Wallets this agent has active permission to spend from, with the
48
+ * per-wallet caps from the permission. Empty array if no active
49
+ * grants. Pick one to pass as `wallet` to tally.payments.create(). */
50
+ wallets: AgentWallet[];
31
51
  };
32
52
  type AgentUpsertInput = {
33
53
  /** User-provided stable string identifier (e.g., "research-bot"). */
@@ -193,4 +213,4 @@ declare class Tally {
193
213
  constructor(opts: ClientOptions);
194
214
  }
195
215
 
196
- export { type Agent, type AgentUpsertInput, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
216
+ export { type Agent, type AgentUpsertInput, type AgentWallet, AuthenticationError, type ClientOptions, ConflictError, NotFoundError, type Payment, type PaymentCreateInput, RateLimitError, Tally, TallyError, ValidationError, type WebhookVerifyInput, type WebhookVerifyResult, verifySignature };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/agents.ts","../src/resources/payments.ts","../src/resources/webhooks.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Tally SDK error types. The server returns a consistent shape on every\n// failure — see src/lib/api-errors.ts in the web app — and the SDK turns\n// it into typed exceptions consumers can catch and branch on.\n//\n// Usage in consumer code:\n//\n// try {\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"...\" });\n// } catch (e) {\n// if (e instanceof AuthenticationError) { /* rotate key */ }\n// if (e instanceof ValidationError) { /* fix input */ }\n// throw e;\n// }\n\nexport type ApiErrorPayload = {\n type: string;\n message: string;\n code?: string;\n details?: unknown;\n};\n\nexport class TallyError extends Error {\n readonly type: string;\n readonly code?: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(opts: {\n type: string;\n message: string;\n status: number;\n code?: string;\n details?: unknown;\n }) {\n super(opts.message);\n this.name = \"TallyError\";\n this.type = opts.type;\n this.code = opts.code;\n this.status = opts.status;\n this.details = opts.details;\n }\n}\n\nexport class AuthenticationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class NotFoundError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ValidationError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ValidationError\";\n }\n}\n\nexport class RateLimitError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"RateLimitError\";\n }\n}\n\nexport class ConflictError extends TallyError {\n constructor(payload: ApiErrorPayload, status: number) {\n super({ ...payload, status });\n this.name = \"ConflictError\";\n }\n}\n\n// Maps server error.type → typed exception class.\nexport function makeError(payload: ApiErrorPayload, status: number): TallyError {\n switch (payload.type) {\n case \"unauthenticated\":\n case \"forbidden\":\n return new AuthenticationError(payload, status);\n case \"not_found\":\n return new NotFoundError(payload, status);\n case \"validation_failed\":\n return new ValidationError(payload, status);\n case \"rate_limited\":\n return new RateLimitError(payload, status);\n case \"conflict\":\n return new ConflictError(payload, status);\n default:\n return new TallyError({ ...payload, status });\n }\n}\n","import { makeError, TallyError, type ApiErrorPayload } from \"./errors\";\n\n// Thin HTTP client used by every resource. Centralizes:\n// - Authorization header\n// - JSON request/response handling\n// - Error mapping (server error.type → typed exception)\n//\n// Uses Node 18+'s native fetch. Works in browsers too if you ever want to\n// hit the API from one — though the more common pattern is server-to-server\n// from the consumer's agent infrastructure.\n\nexport type ClientOptions = {\n /** Tally API key (begins with `tly_`). */\n apiKey: string;\n /** Base URL of the Tally API. Defaults to https://app.tallyforagents.com. Override for local dev or self-hosting. */\n baseUrl?: string;\n /** Custom fetch implementation. Useful for tests or non-Node runtimes. */\n fetch?: typeof fetch;\n};\n\nexport class TallyClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly #fetch: typeof fetch;\n\n constructor(opts: ClientOptions) {\n if (\n !opts.apiKey ||\n (!opts.apiKey.startsWith(\"tly_test_\") && !opts.apiKey.startsWith(\"tly_live_\"))\n ) {\n throw new TallyError({\n type: \"validation_failed\",\n message:\n \"Invalid API key. Tally keys start with 'tly_test_' (test mode) or 'tly_live_' (live mode).\",\n status: 0,\n });\n }\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? \"https://app.tallyforagents.com\").replace(/\\/$/, \"\");\n this.#fetch = opts.fetch ?? globalThis.fetch;\n if (typeof this.#fetch !== \"function\") {\n throw new TallyError({\n type: \"internal\",\n message: \"fetch is not available in this runtime. Provide one via `fetch` option.\",\n status: 0,\n });\n }\n }\n\n async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const res = await this.#fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!res.ok) {\n let payload: { error?: ApiErrorPayload } = {};\n try {\n payload = await res.json();\n } catch {\n // Non-JSON error body — fall back to a generic message.\n }\n const err = payload.error ?? {\n type: \"internal\",\n message: `Request failed with status ${res.status}.`,\n };\n throw makeError(err, res.status);\n }\n\n // 204 No Content (e.g., future delete endpoints)\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type AgentWallet = {\n /** Wallet address (lowercased EVM hex) the agent can spend from. */\n address: string;\n /** Friendly name the wallet was given in the dashboard. */\n display_name: string;\n /** Optional role label (e.g., \"main\", \"treasury\"); null if unset. */\n role_label: string | null;\n /** Max per-tx spend in USDC as a decimal string. */\n max_per_tx_usdc: string;\n /** Daily cap in USDC as a decimal string, null if uncapped. */\n daily_cap_usdc: string | null;\n};\n\nexport type Agent = {\n /** Stable user-provided identifier (e.g., \"research-bot\"). */\n id: string;\n /** \"active\" — at least one activated (non-revoked) signer is attached.\n * \"no_permissions\" — no activated signer (may still have pending\n * grants — see pending_signers). */\n status: \"no_permissions\" | \"active\";\n mode: \"test\" | \"live\";\n /** ISO 8601 timestamp. */\n created_at: string;\n /** The account slug this agent belongs to. Surfaced so callers can\n * build dashboard deep-links (e.g., when telling the user to grant\n * a permission). */\n account_slug: string;\n /** Count of non-revoked, activated signers. */\n active_signers: number;\n /** Count of non-revoked, not-yet-activated signers. */\n pending_signers: number;\n /** Wallets this agent has active permission to spend from, with the\n * per-wallet caps from the permission. Empty array if no active\n * grants. Pick one to pass as `wallet` to tally.payments.create(). */\n wallets: AgentWallet[];\n};\n\nexport type AgentUpsertInput = {\n /** User-provided stable string identifier (e.g., \"research-bot\"). */\n id: string;\n};\n\n// agents.upsert / list / get — Phase 2's full surface.\n// Future phases will add: agents.delete, agents.permissions.*, etc.\n\nexport class AgentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Creates the agent if it doesn't exist; no-op if it does.\n *\n * Scope: the active API key's account + mode. Same id can exist\n * independently in test and live mode for the same account.\n */\n async upsert(input: AgentUpsertInput): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"POST\",\n \"/v1/agents\",\n input,\n );\n return agent;\n }\n\n async list(): Promise<Agent[]> {\n const { agents } = await this.client.request<{ agents: Agent[] }>(\n \"GET\",\n \"/v1/agents\",\n );\n return agents;\n }\n\n async get(id: string): Promise<Agent> {\n const { agent } = await this.client.request<{ agent: Agent }>(\n \"GET\",\n `/v1/agents/${encodeURIComponent(id)}`,\n );\n return agent;\n }\n}\n","import type { TallyClient } from \"../client\";\n\nexport type Payment = {\n id: string;\n /** \"pending\" until the on-chain tx confirms; updated by a follow-up\n * GET (planned) or webhook (Phase 6). */\n status: \"pending\" | \"confirmed\" | \"failed\";\n /** Transaction hash on the underlying chain. Set as soon as Privy\n * accepts the signed RPC; the SDK does not wait for receipt. */\n tx_hash: string | null;\n amount_usdc: string;\n to: string;\n from: string;\n memo: string | null;\n idempotency_key: string | null;\n created_at: string;\n};\n\nexport type PaymentCreateInput = {\n /** Agent's user-provided id (the externalId), e.g. \"research-bot\". */\n agent_id: string;\n /** Sender wallet address. Must be in this API key's account+mode and\n * must have an active permission grant for the agent. */\n wallet: string;\n /** Recipient EVM address. */\n to: string;\n /** Decimal USDC amount as a string, e.g. \"10\" or \"2.50\". */\n amount_usdc: string;\n /** Optional memo (max 200 chars), stored alongside the transaction. */\n memo?: string;\n /** Optional idempotency key (max 64 chars). Scoped to (account, mode);\n * retries with the same key return the original Payment without\n * resubmitting on-chain. */\n idempotency_key?: string;\n};\n\n// payments.create — single endpoint for Phase 4.\n// Future phases will add: list, get (with chain status refresh), retry.\n\nexport class PaymentsResource {\n constructor(private readonly client: TallyClient) {}\n\n /**\n * Submits a USDC payment from one of the agent's granted wallets.\n *\n * Enforcement happens in two layers:\n * 1. Tally checks policy (per-tx max, recipient/contract allowlist,\n * expiry, daily cap) and rejects with a structured error before\n * any Privy call.\n * 2. Privy's secure enclave independently checks the per-tx max and\n * recipient/contract conditions encoded in the signer's policy.\n *\n * Returns immediately with `status: \"pending\"` and the on-chain\n * `tx_hash` once Privy accepts the signed RPC. The SDK does not wait\n * for block confirmation; the Transaction row is updated by a\n * separate confirmation flow (forthcoming).\n */\n async create(input: PaymentCreateInput): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"POST\",\n \"/v1/payments\",\n input,\n );\n return payment;\n }\n\n /**\n * Fetches the current state of a payment by id. If still pending on\n * Tally's side, this call lazily refreshes from the chain — so polling\n * this is the canonical way to wait for `confirmed` / `failed`.\n */\n async get(id: string): Promise<Payment> {\n const { payment } = await this.client.request<{ payment: Payment }>(\n \"GET\",\n `/v1/payments/${encodeURIComponent(id)}`,\n );\n return payment;\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n// Webhook helpers for SDK consumers. Tally signs every delivery with\n// HMAC-SHA256 over `${timestamp}.${body}` and sends the signature in\n// the `tally-signature` header:\n//\n// tally-signature: t=<unix_seconds>,v1=<hex_lowercase>\n//\n// Receivers MUST verify before trusting any payload — otherwise a\n// known URL is forge-able. Pass the RAW request body (not the\n// JSON-parsed object) so the bytes match what Tally signed.\n//\n// The verifier is intentionally duplicated from the server-side\n// implementation (src/lib/webhook-sign.ts) to keep the SDK\n// self-contained — external consumers shouldn't depend on Tally's\n// server code.\n\nconst SIGNATURE_VERSION = \"v1\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes\n\nexport type WebhookVerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing_header\"\n | \"malformed_header\"\n | \"no_v1_signature\"\n | \"timestamp_out_of_tolerance\"\n | \"signature_mismatch\";\n };\n\nexport type WebhookVerifyInput = {\n /** Raw request body the receiver got — string, exactly the bytes\n * Tally signed. Don't pass a JSON-parsed object. */\n body: string;\n /** Value of the `tally-signature` header on the incoming request. */\n header: string | null | undefined;\n /** The plaintext signing secret you saved at webhook creation\n * (`whsec_<mode>_<base64url>`). */\n secret: string;\n /** Unix seconds. Defaults to now. Pin for tests. */\n now?: number;\n /** How far the header timestamp may drift from `now`. Defaults to\n * 300s (5 min). Tighten or loosen to taste; the wider the window,\n * the longer a stolen signature stays replayable. */\n toleranceSeconds?: number;\n};\n\nexport class WebhooksResource {\n /**\n * Verify a `tally-signature` header against the request body. Use in\n * your webhook handler before processing the payload.\n *\n * @example\n * ```ts\n * const body = await req.text(); // raw, not JSON-parsed\n * const result = tally.webhooks.verifySignature({\n * body,\n * header: req.headers.get(\"tally-signature\"),\n * secret: process.env.TALLY_WEBHOOK_SECRET!,\n * });\n * if (!result.ok) return new Response(\"invalid signature\", { status: 400 });\n * const event = JSON.parse(body);\n * // ... handle event\n * ```\n */\n verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n return verifySignature(input);\n }\n}\n\nexport function verifySignature(input: WebhookVerifyInput): WebhookVerifyResult {\n const { body, header, secret } = input;\n const now = input.now ?? Math.floor(Date.now() / 1000);\n const tolerance = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n\n if (!header) return { ok: false, reason: \"missing_header\" };\n\n let timestamp: number | null = null;\n let v1: string | null = null;\n for (const part of header.split(\",\")) {\n const [k, v] = part.split(\"=\", 2);\n if (k === \"t\") {\n const n = Number(v);\n if (Number.isFinite(n)) timestamp = n;\n } else if (k === SIGNATURE_VERSION) {\n v1 = v;\n }\n }\n\n if (timestamp === null) return { ok: false, reason: \"malformed_header\" };\n if (!v1) return { ok: false, reason: \"no_v1_signature\" };\n\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: \"timestamp_out_of_tolerance\" };\n }\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestamp}.${body}`)\n .digest(\"hex\");\n\n if (expected.length !== v1.length) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(v1, \"utf8\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"signature_mismatch\" };\n }\n return { ok: true };\n}\n","// @tallyforagents/sdk — the financial OS for AI agents.\n//\n// Usage:\n// import { Tally } from \"@tallyforagents/sdk\";\n//\n// const tally = new Tally({ apiKey: process.env.TALLY_API_KEY! });\n// await tally.agents.upsert({ id: \"research-bot\", display_name: \"Research Bot\" });\n\nimport { TallyClient, type ClientOptions } from \"./client\";\nimport { AgentsResource } from \"./resources/agents\";\nimport { PaymentsResource } from \"./resources/payments\";\nimport { WebhooksResource } from \"./resources/webhooks\";\n\nexport class Tally {\n readonly agents: AgentsResource;\n readonly payments: PaymentsResource;\n readonly webhooks: WebhooksResource;\n\n // Expose the underlying client for advanced use (custom retries, etc.).\n // Internal callers go through the resource classes instead.\n private readonly client: TallyClient;\n\n constructor(opts: ClientOptions) {\n this.client = new TallyClient(opts);\n this.agents = new AgentsResource(this.client);\n this.payments = new PaymentsResource(this.client);\n this.webhooks = new WebhooksResource();\n }\n}\n\nexport type { ClientOptions } from \"./client\";\nexport type { Agent, AgentUpsertInput, AgentWallet } from \"./resources/agents\";\nexport type { Payment, PaymentCreateInput } from \"./resources/payments\";\nexport type {\n WebhookVerifyInput,\n WebhookVerifyResult,\n} from \"./resources/webhooks\";\nexport { verifySignature } from \"./resources/webhooks\";\nexport {\n TallyError,\n AuthenticationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ConflictError,\n} from \"./errors\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyforagents/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Tally TypeScript SDK — give AI agents USDC wallets and scoped programmable spending on Base.",
5
5
  "keywords": [
6
6
  "tally",