@envolvr/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ // envolvr: sign in, fund, run private inference, and verify its receipts.
3
+ //
4
+ // ENVOLVR_PRIVATE_KEY wallet key, for signin, deposit and testnet-mint
5
+ // ENVOLVR_API_KEY API key, for everything else (or ~/.envolvr/credentials.json)
6
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { closeAccount, depositUsdg, getPricing, mintTestUsdg, signIn } from "./api.js";
10
+ import { AUDIT_CHECKS, verifyGateway } from "./attest.js";
11
+ import { Envolvr, savedReceipts, verifySaved } from "./client.js";
12
+ import { fromMicros, TESTNET, toMicros } from "./network.js";
13
+ import { privateKeySigner } from "./wallet.js";
14
+ const HELP = `envolvr: private inference your agent can prove
15
+
16
+ envolvr signin [--save] sign in with ENVOLVR_PRIVATE_KEY, print a new API key
17
+ envolvr testnet-mint <usd> mint test USDG to the wallet (testnet)
18
+ envolvr deposit <usd> [--for 0x…] deposit USDG (net of the deposit fee) to your balance
19
+ envolvr account balance and today's staking allowance
20
+ envolvr pricing the deposit fee and how token prices are set
21
+ envolvr models models and per-token prices
22
+ envolvr chat <prompt> [--model m] [--provider '{"only":["near-ai"]}']
23
+ one request; prints the reply and saves its receipt
24
+ envolvr verify [<receipt dir>|--last] [--accept-compose <hash>]
25
+ signature, billing, payer and on-chain anchor of a receipt
26
+ envolvr verify-gateway [--accept-compose <hash>]
27
+ attestation of the live gateway (TDX quote, source, TLS key)
28
+ envolvr close --yes [--refund-to 0x…]
29
+ close the account: revoke every API key and refund the
30
+ balance to the wallet (or the address given)
31
+
32
+ Receipts are saved under ./envolvr-receipts (ENVOLVR_RECEIPTS to change).`;
33
+ const args = process.argv.slice(2);
34
+ const [command] = args;
35
+ const flag = (name) => {
36
+ const i = args.indexOf(`--${name}`);
37
+ return i > 0 ? args[i + 1] : undefined;
38
+ };
39
+ const network = TESTNET;
40
+ const receiptDir = process.env.ENVOLVR_RECEIPTS ?? 'envolvr-receipts';
41
+ const credentialsPath = join(homedir(), '.envolvr', 'credentials.json');
42
+ function wallet() {
43
+ const key = process.env.ENVOLVR_PRIVATE_KEY;
44
+ if (!key)
45
+ throw new Error('set ENVOLVR_PRIVATE_KEY to the wallet key');
46
+ return privateKeySigner(key, { rpcUrl: network.rpcUrl, chainId: network.chainId });
47
+ }
48
+ function apiKey() {
49
+ if (process.env.ENVOLVR_API_KEY)
50
+ return process.env.ENVOLVR_API_KEY;
51
+ if (existsSync(credentialsPath))
52
+ return JSON.parse(readFileSync(credentialsPath, 'utf8')).apiKey;
53
+ throw new Error('set ENVOLVR_API_KEY, or run envolvr signin --save');
54
+ }
55
+ const usd = (micros) => `$${fromMicros(micros)}`;
56
+ const check = (ok) => (ok ? '✓' : ok === false ? '✗' : '·');
57
+ async function main() {
58
+ switch (command) {
59
+ case 'signin': {
60
+ const { apiKey: key, wallet: w } = await signIn(wallet(), network);
61
+ if (args.includes('--save')) {
62
+ mkdirSync(join(homedir(), '.envolvr'), { recursive: true });
63
+ writeFileSync(credentialsPath, JSON.stringify({ apiKey: key, wallet: w }));
64
+ chmodSync(credentialsPath, 0o600);
65
+ console.log(`signed in as ${w}; API key saved to ${credentialsPath}`);
66
+ }
67
+ else {
68
+ console.log(`signed in as ${w}\nAPI key (shown once): ${key}`);
69
+ }
70
+ return;
71
+ }
72
+ case 'testnet-mint': {
73
+ const tx = await mintTestUsdg({ signer: wallet(), amountMicros: toMicros(args[1]), network });
74
+ console.log(`minted ${args[1]} test USDG: ${network.explorer}/tx/${tx}`);
75
+ return;
76
+ }
77
+ case 'deposit': {
78
+ const r = await depositUsdg({ signer: wallet(), amountMicros: toMicros(args[1]), account: flag('for'), network });
79
+ console.log(`deposited ${usd(r.amountMicros)} for ${r.account}: ${network.explorer}/tx/${r.depositTx}`);
80
+ console.log(`credit ${usd(r.expectedCreditMicros)} after the ${r.depositFeeBps / 100}% deposit fee, within seconds`);
81
+ return;
82
+ }
83
+ case 'account': {
84
+ const a = await new Envolvr({ apiKey: apiKey(), network }).account();
85
+ console.log(`${a.wallet}\nbalance ${usd(a.balanceMicros)} | allowance today ${usd(a.allowanceTodayMicros)}, left ${usd(a.allowanceLeftMicros)}`);
86
+ return;
87
+ }
88
+ case 'pricing': {
89
+ const p = await getPricing(network);
90
+ console.log(`tokens: ${p.tokenPricing}\ndeposit fee: ${p.depositFeeBps / 100}%`);
91
+ return;
92
+ }
93
+ case 'models':
94
+ console.log(JSON.stringify(await new Envolvr({ apiKey: apiKey(), network }).models(), null, 2));
95
+ return;
96
+ case 'chat': {
97
+ const client = new Envolvr({ apiKey: apiKey(), network, receiptDir });
98
+ const provider = flag('provider');
99
+ const { response, saved } = await client.chat({
100
+ model: flag('model') ?? 'z-ai/glm-5.3',
101
+ messages: [{ role: 'user', content: args[1] }],
102
+ ...(provider ? { provider: JSON.parse(provider) } : {}),
103
+ });
104
+ console.log(response.choices?.[0]?.message?.content ?? JSON.stringify(response));
105
+ console.log(`\ncost $${response.usage?.cost ?? '?'} | receipt ${saved?.id ?? 'none'}${saved?.dir ? ` saved to ${saved.dir}` : ''}`);
106
+ return;
107
+ }
108
+ case 'verify': {
109
+ const dir = args[1] && !args[1].startsWith('--') ? args[1] : savedReceipts(receiptDir).at(-1);
110
+ if (!dir)
111
+ throw new Error(`no saved receipt in ${receiptDir}`);
112
+ const key = process.env.ENVOLVR_API_KEY ?? (existsSync(credentialsPath) ? apiKey() : undefined);
113
+ const acceptCompose = flag('accept-compose') ? [flag('accept-compose')] : undefined;
114
+ const v = await verifySaved(dir, { network, apiKey: key, receiptDir, acceptCompose });
115
+ console.log(`receipt ${v.receiptId}`);
116
+ if (v.audit) {
117
+ for (const [id, label] of Object.entries(AUDIT_CHECKS)) {
118
+ const c = v.audit.checks[id];
119
+ console.log(`${c?.status === 'pass' ? '✓' : c?.status === 'fail' ? '✗' : '·'} ${label}`);
120
+ }
121
+ for (const c of v.audit.failed.filter((f) => !(f.id in AUDIT_CHECKS)))
122
+ console.log(`✗ ${c.title}: ${c.detail}`);
123
+ }
124
+ else {
125
+ console.log(`${check(false)} signature ${String(v.signature.transcript)}`);
126
+ }
127
+ if (v.billing) {
128
+ console.log(`${check(true)} billing ${v.billing.cost} ${v.billing.currency} for ${v.billing.tokens.prompt}+${v.billing.tokens.completion} tokens, billed ${usd(BigInt(v.billing.billedMicroUsd))}`);
129
+ console.log(`${check(v.chargedToKey)} charged to this key ${v.chargedToKey === undefined ? 'no API key given' : v.chargedToKey ? 'yes' : 'no'}`);
130
+ }
131
+ else {
132
+ console.log(`${check(undefined)} billing not priced (no billing event)`);
133
+ }
134
+ const a = v.anchor;
135
+ console.log(`${check(a.status === 'anchored')} anchor ${a.status === 'anchored'
136
+ ? `batch ${a.batchIndex}, leaf ${a.leafIndex} of ${a.count}, ${new Date(a.anchoredAt * 1000).toISOString()}, tx ${a.txHash}`
137
+ : a.status === 'pending' ? 'pending: batches close every 10 minutes (UTC)' : 'unknown to the proof service'}`);
138
+ const signed = v.audit?.signed ?? false;
139
+ const ok = signed && a.status === 'anchored';
140
+ console.log(`\nverdict: ${ok ? 'VERIFIED' : signed && a.status === 'pending' ? 'SIGNED, ANCHOR PENDING' : 'NOT VERIFIED'}`);
141
+ if (!signed && v.audit)
142
+ console.log('pap transcript:', JSON.stringify(v.signature.transcript, null, 2).slice(0, 4000));
143
+ process.exit(ok ? 0 : signed && a.status === 'pending' ? 2 : 1);
144
+ }
145
+ case 'close': {
146
+ if (!args.includes('--yes'))
147
+ throw new Error('closing revokes every API key of this wallet; run again with --yes');
148
+ const r = await closeAccount(wallet(), { refundTo: flag('refund-to'), network });
149
+ console.log(`account closed; ${r.revokedKeys} API key(s) revoked`);
150
+ console.log(r.refund
151
+ ? `refund #${r.refund.id}: ${usd(r.refund.amountMicros)} to ${r.refund.refundTo} (${r.refund.status === 'held' ? 'held for review' : 'paid from the credit vault shortly'})`
152
+ : 'no balance to refund');
153
+ return;
154
+ }
155
+ case 'verify-gateway': {
156
+ const r = await verifyGateway(network, { acceptCompose: flag('accept-compose') ? [flag('accept-compose')] : undefined });
157
+ console.log(typeof r.transcript === 'string' ? r.transcript : JSON.stringify(r.transcript, null, 2));
158
+ process.exit(r.verified ? 0 : 1);
159
+ }
160
+ default:
161
+ console.log(HELP);
162
+ process.exit(command && command !== 'help' && command !== '--help' ? 2 : 0);
163
+ }
164
+ }
165
+ main().catch((err) => {
166
+ const message = err.message;
167
+ console.error(message === 'fetch failed'
168
+ ? `error: could not reach envolvr (${network.gateway}, ${network.control}); try again shortly`
169
+ : `error: ${message}`);
170
+ process.exit(1);
171
+ });
@@ -0,0 +1,84 @@
1
+ import { type AuditSummary, type PapResult } from './attest.ts';
2
+ import { type Network } from './network.ts';
3
+ import { type Anchor, type Billing, type Receipt } from './receipts.ts';
4
+ export interface SavedReceipt {
5
+ id: string;
6
+ receipt: Receipt;
7
+ /** Directory holding receipt.json, request.json, response.json. */
8
+ dir?: string;
9
+ }
10
+ export interface Verification {
11
+ receiptId: string;
12
+ /** pap's offline audit: attestation report, receipt signature, commitments, cited session. */
13
+ signature: PapResult | {
14
+ verified: false;
15
+ transcript: string;
16
+ };
17
+ /** The audit read check by check; undefined when it did not run. */
18
+ audit?: AuditSummary;
19
+ billing?: Billing;
20
+ /** Whether the receipt's payer commitment matches the API key given. */
21
+ chargedToKey?: boolean;
22
+ anchor: Anchor;
23
+ }
24
+ export declare class Envolvr {
25
+ readonly network: Network;
26
+ private apiKey;
27
+ private receiptDir;
28
+ constructor(opts: {
29
+ apiKey: string;
30
+ network?: Network;
31
+ receiptDir?: string;
32
+ });
33
+ /** Settings for the OpenAI SDK: `new OpenAI(envolvr.openai)`. Receipts are then yours to fetch. */
34
+ get openai(): {
35
+ baseURL: string;
36
+ apiKey: string;
37
+ };
38
+ account(): Promise<import("./api.ts").Account>;
39
+ pricing(): Promise<import("./api.ts").Pricing>;
40
+ /** GET /v1/models: every model with its per-token prices and routes. */
41
+ models(): Promise<unknown>;
42
+ /**
43
+ * A chat completion (non-streaming), with its receipt fetched and saved.
44
+ * `body` is an OpenAI chat completion request; add `provider: { only: [...] }`
45
+ * to pin providers.
46
+ */
47
+ chat(body: Record<string, unknown>): Promise<{
48
+ response: any;
49
+ receiptId: string | null;
50
+ saved?: SavedReceipt;
51
+ }>;
52
+ /**
53
+ * Fetch a receipt (within an hour of the response) and save it. With
54
+ * `receiptDir`, the request and response bytes and the signing keyset's
55
+ * attestation report are saved beside it.
56
+ */
57
+ receipt(id: string, bodies?: {
58
+ request?: string;
59
+ response?: string;
60
+ }): Promise<SavedReceipt>;
61
+ /** Keep the provider session the receipt cites (sessions, like receipts, expire). */
62
+ private saveSession;
63
+ /** Keep one attestation report per keyset, fetched while that keyset is live. */
64
+ private saveReport;
65
+ /**
66
+ * Verify a saved receipt: signature and commitments (pap, against the saved
67
+ * attestation report), what it billed and whether to this key, and its anchor
68
+ * on Robinhood Chain.
69
+ */
70
+ verify(dir: string, opts?: {
71
+ acceptCompose?: string[];
72
+ }): Promise<Verification>;
73
+ }
74
+ /** Verify a receipt saved by Envolvr.receipt (a directory with receipt.json). */
75
+ export declare function verifySaved(dir: string, opts: {
76
+ network?: Network;
77
+ apiKey?: string;
78
+ receiptDir?: string;
79
+ acceptCompose?: string[];
80
+ /** Leave the signature to a separate `pap audit` run (for example where npx is unavailable). */
81
+ skipSignature?: boolean;
82
+ }): Promise<Verification>;
83
+ /** Saved receipt directories under `receiptDir`, oldest first. */
84
+ export declare function savedReceipts(receiptDir: string): string[];
package/dist/client.js ADDED
@@ -0,0 +1,156 @@
1
+ // The inference client. The gateway speaks the OpenAI API; this client adds
2
+ // what an agent needs to keep the proof: every response's receipt is fetched
3
+ // right away (the gateway keeps receipts for an hour) and saved with the exact
4
+ // request and response bytes and the attestation report of the keyset that
5
+ // signed it, so the receipt can be verified later, offline and on chain.
6
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { getAccount, getPricing } from "./api.js";
9
+ import { auditReceipt, fetchAttestationReport, summarizeAudit } from "./attest.js";
10
+ import { TESTNET } from "./network.js";
11
+ import { billingOf, chargedTo, verifyAnchor } from "./receipts.js";
12
+ export class Envolvr {
13
+ network;
14
+ apiKey;
15
+ receiptDir;
16
+ constructor(opts) {
17
+ this.apiKey = opts.apiKey;
18
+ this.network = opts.network ?? TESTNET;
19
+ this.receiptDir = opts.receiptDir;
20
+ }
21
+ /** Settings for the OpenAI SDK: `new OpenAI(envolvr.openai)`. Receipts are then yours to fetch. */
22
+ get openai() {
23
+ return { baseURL: `${this.network.gateway}/v1`, apiKey: this.apiKey };
24
+ }
25
+ account() {
26
+ return getAccount(this.apiKey, this.network);
27
+ }
28
+ pricing() {
29
+ return getPricing(this.network);
30
+ }
31
+ /** GET /v1/models: every model with its per-token prices and routes. */
32
+ async models() {
33
+ const res = await fetch(`${this.network.gateway}/v1/models`, { headers: { authorization: `Bearer ${this.apiKey}` } });
34
+ if (!res.ok)
35
+ throw new Error(`/v1/models: HTTP ${res.status}`);
36
+ return res.json();
37
+ }
38
+ /**
39
+ * A chat completion (non-streaming), with its receipt fetched and saved.
40
+ * `body` is an OpenAI chat completion request; add `provider: { only: [...] }`
41
+ * to pin providers.
42
+ */
43
+ async chat(body) {
44
+ const request = JSON.stringify({ ...body, stream: false });
45
+ const res = await fetch(`${this.network.gateway}/v1/chat/completions`, {
46
+ method: 'POST', headers: { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }, body: request,
47
+ });
48
+ const responseText = await res.text();
49
+ const receiptId = res.headers.get('x-receipt-id');
50
+ let response;
51
+ try {
52
+ response = JSON.parse(responseText);
53
+ }
54
+ catch {
55
+ response = responseText;
56
+ }
57
+ if (!res.ok) {
58
+ const message = typeof response === 'object' ? response?.error?.message : responseText;
59
+ throw Object.assign(new Error(`chat completion: HTTP ${res.status} ${message ?? ''}`.trim()), { status: res.status, receiptId });
60
+ }
61
+ const saved = receiptId ? await this.receipt(receiptId, { request, response: responseText }) : undefined;
62
+ return { response, receiptId, saved };
63
+ }
64
+ /**
65
+ * Fetch a receipt (within an hour of the response) and save it. With
66
+ * `receiptDir`, the request and response bytes and the signing keyset's
67
+ * attestation report are saved beside it.
68
+ */
69
+ async receipt(id, bodies = {}) {
70
+ const res = await fetch(`${this.network.gateway}/v1/aci/receipts/${encodeURIComponent(id)}`, {
71
+ headers: { authorization: `Bearer ${this.apiKey}` },
72
+ });
73
+ if (!res.ok)
74
+ throw new Error(`receipt ${id}: HTTP ${res.status}${res.status === 404 ? ' (receipts are kept for an hour)' : ''}`);
75
+ const raw = await res.text();
76
+ const receipt = JSON.parse(raw);
77
+ if (!this.receiptDir)
78
+ return { id, receipt };
79
+ const dir = join(this.receiptDir, id);
80
+ mkdirSync(dir, { recursive: true });
81
+ writeFileSync(join(dir, 'receipt.json'), raw);
82
+ if (bodies.request !== undefined)
83
+ writeFileSync(join(dir, 'request.json'), bodies.request);
84
+ if (bodies.response !== undefined)
85
+ writeFileSync(join(dir, 'response.json'), bodies.response);
86
+ await this.saveReport(String(receipt.workload_keyset_digest));
87
+ await this.saveSession(receipt, dir);
88
+ return { id, receipt, dir };
89
+ }
90
+ /** Keep the provider session the receipt cites (sessions, like receipts, expire). */
91
+ async saveSession(receipt, dir) {
92
+ const cited = receipt.event_log.find((e) => e.type === 'upstream.verified' && typeof e.session_id === 'string');
93
+ if (!cited)
94
+ return;
95
+ const res = await fetch(`${this.network.gateway}/v1/aci/sessions/${cited.session_id}`, {
96
+ headers: { authorization: `Bearer ${this.apiKey}` },
97
+ });
98
+ if (res.ok)
99
+ writeFileSync(join(dir, 'session.json'), await res.text());
100
+ }
101
+ /** Keep one attestation report per keyset, fetched while that keyset is live. */
102
+ async saveReport(keysetDigest) {
103
+ const reports = join(this.receiptDir, 'attestation');
104
+ const file = join(reports, `${keysetDigest.replace(':', '-')}.json`);
105
+ if (existsSync(file))
106
+ return;
107
+ const { nonce, report } = await fetchAttestationReport(this.network);
108
+ if (report.workload_keyset_digest !== keysetDigest)
109
+ return; // the gateway restarted since; nothing to pair with
110
+ mkdirSync(reports, { recursive: true });
111
+ writeFileSync(file, JSON.stringify(report));
112
+ writeFileSync(file.replace(/\.json$/, '.nonce'), nonce);
113
+ }
114
+ /**
115
+ * Verify a saved receipt: signature and commitments (pap, against the saved
116
+ * attestation report), what it billed and whether to this key, and its anchor
117
+ * on Robinhood Chain.
118
+ */
119
+ async verify(dir, opts = {}) {
120
+ return verifySaved(dir, { network: this.network, apiKey: this.apiKey, receiptDir: this.receiptDir, ...opts });
121
+ }
122
+ }
123
+ /** Verify a receipt saved by Envolvr.receipt (a directory with receipt.json). */
124
+ export async function verifySaved(dir, opts) {
125
+ const network = opts.network ?? TESTNET;
126
+ const receiptPath = join(dir, 'receipt.json');
127
+ const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'));
128
+ const reports = join(opts.receiptDir ?? join(dir, '..'), 'attestation');
129
+ const reportPath = join(reports, `${String(receipt.workload_keyset_digest).replace(':', '-')}.json`);
130
+ const signature = opts.skipSignature
131
+ ? { verified: false, transcript: 'skipped' }
132
+ : existsSync(reportPath)
133
+ ? await auditReceipt({
134
+ reportPath, receiptPath, acceptCompose: opts.acceptCompose,
135
+ nonce: existsSync(reportPath.replace(/\.json$/, '.nonce')) ? readFileSync(reportPath.replace(/\.json$/, '.nonce'), 'utf8') : undefined,
136
+ requestBodyPath: existsSync(join(dir, 'request.json')) ? join(dir, 'request.json') : undefined,
137
+ responseBodyPath: existsSync(join(dir, 'response.json')) ? join(dir, 'response.json') : undefined,
138
+ sessionPath: existsSync(join(dir, 'session.json')) ? join(dir, 'session.json') : undefined,
139
+ })
140
+ : { verified: false, transcript: `no attestation report for keyset ${String(receipt.workload_keyset_digest)} in ${reports}` };
141
+ return {
142
+ receiptId: receipt.receipt_id,
143
+ signature,
144
+ audit: typeof signature.transcript === 'object' ? summarizeAudit(signature) : undefined,
145
+ billing: billingOf(receipt),
146
+ chargedToKey: opts.apiKey ? chargedTo(receipt, opts.apiKey) : undefined,
147
+ anchor: await verifyAnchor(receipt, network),
148
+ };
149
+ }
150
+ /** Saved receipt directories under `receiptDir`, oldest first. */
151
+ export function savedReceipts(receiptDir) {
152
+ if (!existsSync(receiptDir))
153
+ return [];
154
+ return readdirSync(receiptDir).filter((d) => d !== 'attestation' && existsSync(join(receiptDir, d, 'receipt.json')))
155
+ .map((d) => join(receiptDir, d)).sort();
156
+ }
package/dist/evm.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export type Hex = `0x${string}`;
2
+ export declare const hex: (b: Uint8Array) => Hex;
3
+ export declare function unhex(h: string): Uint8Array;
4
+ /** Minimal big-endian bytes of a non-negative integer; zero is empty. */
5
+ export declare function uintBytes(n: bigint): Uint8Array;
6
+ type RlpItem = Uint8Array | RlpItem[];
7
+ export declare function rlp(item: RlpItem): Uint8Array;
8
+ /** EIP-191 personal_sign: a 65-byte signature as 0x hex (v = 27 or 28). */
9
+ export declare function personalSign(message: string, privateKey: Uint8Array): string;
10
+ export declare function addressOfKey(privateKey: Uint8Array): string;
11
+ export interface Tx {
12
+ chainId: bigint;
13
+ nonce: bigint;
14
+ maxPriorityFeePerGas: bigint;
15
+ maxFeePerGas: bigint;
16
+ gas: bigint;
17
+ to: string;
18
+ value: bigint;
19
+ data: string;
20
+ }
21
+ /** Sign an EIP-1559 (type 2) transaction. Returns the raw bytes and the hash. */
22
+ export declare function signTx(tx: Tx, privateKey: Uint8Array): {
23
+ raw: Hex;
24
+ hash: Hex;
25
+ };
26
+ export declare function selector(signature: string): string;
27
+ /** ABI-encode a call whose arguments are all static words (bytes32, uintN, address). */
28
+ export declare function encodeCall(signature: string, args: (bigint | string)[]): Hex;
29
+ /** The 32-byte words of an ABI return value. */
30
+ export declare function decodeWords(data: string): string[];
31
+ export declare class RpcError extends Error {
32
+ readonly data: string | undefined;
33
+ constructor(message: string, data?: string);
34
+ }
35
+ export declare class Rpc {
36
+ private url;
37
+ private timeoutMs;
38
+ constructor(url: string, timeoutMs?: number);
39
+ call<T>(method: string, params: unknown[]): Promise<T>;
40
+ }
41
+ export {};
package/dist/evm.js ADDED
@@ -0,0 +1,102 @@
1
+ // Minimal EVM client: JSON-RPC, RLP, EIP-1559 transactions signed with a
2
+ // secp256k1 key, EIP-191 personal_sign, and ABI encoding of static arguments.
3
+ // No dependencies beyond @noble. The same encoders as the control plane
4
+ // (control/src/evm.ts), pinned to Foundry's cast in the tests.
5
+ import { secp256k1 } from '@noble/curves/secp256k1';
6
+ import { keccak_256 } from '@noble/hashes/sha3';
7
+ import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
8
+ export const hex = (b) => `0x${bytesToHex(b)}`;
9
+ export function unhex(h) {
10
+ const s = h.replace(/^0x/, '');
11
+ return hexToBytes(s.length % 2 ? `0${s}` : s);
12
+ }
13
+ /** Minimal big-endian bytes of a non-negative integer; zero is empty. */
14
+ export function uintBytes(n) {
15
+ if (n < 0n)
16
+ throw new Error('negative integer');
17
+ if (n === 0n)
18
+ return new Uint8Array();
19
+ const h = n.toString(16);
20
+ return hexToBytes(h.length % 2 ? `0${h}` : h);
21
+ }
22
+ export function rlp(item) {
23
+ const length = (len, offset) => {
24
+ if (len < 56)
25
+ return Uint8Array.of(offset + len);
26
+ const l = uintBytes(BigInt(len));
27
+ return concatBytes(Uint8Array.of(offset + 55 + l.length), l);
28
+ };
29
+ if (item instanceof Uint8Array) {
30
+ if (item.length === 1 && item[0] < 0x80)
31
+ return item;
32
+ return concatBytes(length(item.length, 0x80), item);
33
+ }
34
+ const body = concatBytes(...item.map(rlp));
35
+ return concatBytes(length(body.length, 0xc0), body);
36
+ }
37
+ /** EIP-191 personal_sign: a 65-byte signature as 0x hex (v = 27 or 28). */
38
+ export function personalSign(message, privateKey) {
39
+ const bytes = utf8ToBytes(message);
40
+ const digest = keccak_256(concatBytes(utf8ToBytes(`\x19Ethereum Signed Message:\n${bytes.length}`), bytes));
41
+ const sig = secp256k1.sign(digest, privateKey);
42
+ return `0x${bytesToHex(sig.toCompactRawBytes())}${(27 + sig.recovery).toString(16)}`;
43
+ }
44
+ export function addressOfKey(privateKey) {
45
+ const pub = secp256k1.getPublicKey(privateKey, false);
46
+ return hex(keccak_256(pub.slice(1)).slice(-20));
47
+ }
48
+ /** Sign an EIP-1559 (type 2) transaction. Returns the raw bytes and the hash. */
49
+ export function signTx(tx, privateKey) {
50
+ const fields = [
51
+ uintBytes(tx.chainId), uintBytes(tx.nonce), uintBytes(tx.maxPriorityFeePerGas), uintBytes(tx.maxFeePerGas),
52
+ uintBytes(tx.gas), unhex(tx.to), uintBytes(tx.value), unhex(tx.data), [],
53
+ ];
54
+ const digest = keccak_256(concatBytes(Uint8Array.of(2), rlp(fields)));
55
+ const sig = secp256k1.sign(digest, privateKey);
56
+ const raw = concatBytes(Uint8Array.of(2), rlp([...fields, uintBytes(BigInt(sig.recovery)), uintBytes(sig.r), uintBytes(sig.s)]));
57
+ return { raw: hex(raw), hash: hex(keccak_256(raw)) };
58
+ }
59
+ export function selector(signature) {
60
+ return bytesToHex(keccak_256(utf8ToBytes(signature)).slice(0, 4));
61
+ }
62
+ /** ABI-encode a call whose arguments are all static words (bytes32, uintN, address). */
63
+ export function encodeCall(signature, args) {
64
+ const words = args.map((a) => (typeof a === 'bigint' ? a.toString(16) : a.replace(/^0x/, '').toLowerCase()).padStart(64, '0'));
65
+ if (words.some((w) => w.length !== 64 || !/^[0-9a-f]+$/.test(w)))
66
+ throw new Error(`bad argument for ${signature}`);
67
+ return `0x${selector(signature)}${words.join('')}`;
68
+ }
69
+ /** The 32-byte words of an ABI return value. */
70
+ export function decodeWords(data) {
71
+ const h = data.replace(/^0x/, '');
72
+ return Array.from({ length: Math.floor(h.length / 64) }, (_, i) => `0x${h.slice(i * 64, i * 64 + 64)}`);
73
+ }
74
+ export class RpcError extends Error {
75
+ data;
76
+ constructor(message, data) {
77
+ super(message);
78
+ this.data = data;
79
+ }
80
+ }
81
+ export class Rpc {
82
+ url;
83
+ timeoutMs;
84
+ constructor(url, timeoutMs = 20_000) {
85
+ this.url = url;
86
+ this.timeoutMs = timeoutMs;
87
+ }
88
+ async call(method, params) {
89
+ const res = await fetch(this.url, {
90
+ method: 'POST',
91
+ headers: { 'content-type': 'application/json' },
92
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
93
+ signal: AbortSignal.timeout(this.timeoutMs),
94
+ });
95
+ const body = (await res.json());
96
+ if (body.error)
97
+ throw new RpcError(`${method}: ${body.error.message}`, body.error.data);
98
+ if (!res.ok || !('result' in body))
99
+ throw new RpcError(`${method} failed: HTTP ${res.status}`);
100
+ return body.result;
101
+ }
102
+ }
@@ -0,0 +1,12 @@
1
+ export { closeAccount, closeAccountMessage, depositUsdg, getAccount, getPricing, mintTestUsdg, netCredit, signIn, signInMessage, } from './api.ts';
2
+ export type { Account, Pricing } from './api.ts';
3
+ export { auditReceipt, fetchAttestationReport, runPap, verifyGateway } from './attest.ts';
4
+ export type { PapResult } from './attest.ts';
5
+ export { Envolvr, savedReceipts, verifySaved } from './client.ts';
6
+ export type { SavedReceipt, Verification } from './client.ts';
7
+ export { fromMicros, TESTNET, toMicros } from './network.ts';
8
+ export type { Network } from './network.ts';
9
+ export { billingOf, chargedTo, payerCommitment, proofLeadsToRoot, receiptDigest, verifyAnchor } from './receipts.ts';
10
+ export type { Anchor, Billing, Receipt } from './receipts.ts';
11
+ export { privateKeySigner } from './wallet.ts';
12
+ export type { Signer } from './wallet.ts';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @envolvr/sdk: private, verifiable inference for agents on Robinhood Chain.
2
+ export { closeAccount, closeAccountMessage, depositUsdg, getAccount, getPricing, mintTestUsdg, netCredit, signIn, signInMessage, } from "./api.js";
3
+ export { auditReceipt, fetchAttestationReport, runPap, verifyGateway } from "./attest.js";
4
+ export { Envolvr, savedReceipts, verifySaved } from "./client.js";
5
+ export { fromMicros, TESTNET, toMicros } from "./network.js";
6
+ export { billingOf, chargedTo, payerCommitment, proofLeadsToRoot, receiptDigest, verifyAnchor } from "./receipts.js";
7
+ export { privateKeySigner } from "./wallet.js";
@@ -0,0 +1,19 @@
1
+ export interface Network {
2
+ /** OpenAI-compatible inference gateway (attested TDX VM). */
3
+ gateway: string;
4
+ /** Sign-in, account, pricing and receipt proofs. */
5
+ control: string;
6
+ chainId: number;
7
+ rpcUrl: string;
8
+ explorer: string;
9
+ /** USDG token (a test stand-in on testnet, with an open mint). */
10
+ usdg: string;
11
+ creditVault: string;
12
+ receiptAnchor: string;
13
+ /** This gateway's log in ReceiptAnchor: keccak256("envolvr.provider:<dstack app id>"). */
14
+ providerId: string;
15
+ }
16
+ export declare const TESTNET: Network;
17
+ /** USDG and envolvr balances have 6 decimals: "1.5" -> 1500000n. */
18
+ export declare function toMicros(amount: string | number): bigint;
19
+ export declare function fromMicros(micros: bigint | string): string;
@@ -0,0 +1,27 @@
1
+ // Where envolvr runs. Robinhood Chain testnet until mainnet.
2
+ export const TESTNET = {
3
+ gateway: 'https://api.envolvr.xyz',
4
+ control: 'https://auth.envolvr.xyz',
5
+ chainId: 46630,
6
+ rpcUrl: 'https://rpc.testnet.chain.robinhood.com/rpc',
7
+ explorer: 'https://explorer.testnet.chain.robinhood.com',
8
+ usdg: '0xF7326a325aED45a1BF5D1268982f519BeCe941b6',
9
+ creditVault: '0x2aaB804f3eB6B436CB542d56D171d020bC2d7c51',
10
+ receiptAnchor: '0xff179938C830134D8E2922dAe7e16Ee4F0E33853',
11
+ providerId: '0x86b99987cd8f8ebddc2dd0efa61ed52b5db594430f12446db424da0d9c4b831f',
12
+ };
13
+ /** USDG and envolvr balances have 6 decimals: "1.5" -> 1500000n. */
14
+ export function toMicros(amount) {
15
+ const s = String(amount).trim();
16
+ const m = /^(\d+)(?:\.(\d{0,6}))?$/.exec(s);
17
+ if (!m)
18
+ throw new Error(`not a USD amount with at most 6 decimals: ${s}`);
19
+ return BigInt(m[1]) * 1000000n + BigInt((m[2] ?? '').padEnd(6, '0'));
20
+ }
21
+ export function fromMicros(micros) {
22
+ const v = BigInt(micros);
23
+ const sign = v < 0n ? '-' : '';
24
+ const a = v < 0n ? -v : v;
25
+ const frac = (a % 1000000n).toString().padStart(6, '0').replace(/0+$/, '');
26
+ return `${sign}${a / 1000000n}${frac ? `.${frac}` : ''}`;
27
+ }