diff --git a/sdk/js/.npmignore b/sdk/js/.npmignore deleted file mode 100644 index d50d9a760..000000000 --- a/sdk/js/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -README.md -bun.lockb -src/__tests__ diff --git a/sdk/js/README.md b/sdk/js/README.md index aebe7e3b1..ebd13de68 100644 --- a/sdk/js/README.md +++ b/sdk/js/README.md @@ -1,508 +1,291 @@ -# dstack SDK for JavaScript/TypeScript +# @phala/dstack-sdk -Access TEE features from your JavaScript/TypeScript application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security. +JavaScript / TypeScript client for the dstack guest agent. Derive deterministic keys, generate TDX attestation quotes, issue TLS certificates, sign / verify payloads, and encrypt environment variables for KMS-managed deployments — all against the guest agent socket inside a confidential VM (CVM). ## Installation ```bash -npm install @phala/dstack-sdk +npm install @phala/dstack-sdk @noble/hashes ``` -## Quick Start +`@noble/hashes` is the only required peer dependency (used by the core for sha256 / sha384). Install the matching peer when you import a submodule: -```typescript -import { DstackClient } from '@phala/dstack-sdk'; - -const client = new DstackClient(); +| Import path | Extra peer dependency | +| --- | --- | +| `@phala/dstack-sdk/viem` | `viem` | +| `@phala/dstack-sdk/solana` | `@solana/web3.js` | +| `@phala/dstack-sdk/encrypt-env-vars` | `@noble/curves` | +| `@phala/dstack-sdk/verify-env-encrypt-public-key` | `@noble/curves` | -// Derive a deterministic key for your wallet -const key = await client.getKey('wallet/eth'); -console.log(key.key); // Same path always returns the same key +> **Breaking change in 0.5.8.** Prior releases listed `@solana/web3.js`, `viem`, and `@noble/curves` under `optionalDependencies`, so npm installed them automatically. They are now opt-in peers — install them yourself when you use the corresponding submodule. -// Generate an attestation quote -const quote = await client.getQuote('my-app-state'); -console.log(quote.quote); -``` +Node 18+ supported. Tested through Node 24. -The client automatically connects to `/var/run/dstack.sock`. For local development with the simulator: +## Quick start ```typescript -const client = new DstackClient('http://localhost:8090'); -``` - -## Core API +import { DstackClient } from '@phala/dstack-sdk' -### Derive Keys +const client = new DstackClient() -`getKey()` derives deterministic keys bound to your application's identity (`app_id`). The same path always produces the same key for your app, but different apps get different keys even with the same path. +const key = await client.getKey('wallet/eth') +console.log(Buffer.from(key.key).toString('hex')) -```typescript -// Derive keys by path -const ethKey = await client.getKey('wallet/ethereum'); -const btcKey = await client.getKey('wallet/bitcoin'); - -// Use path to separate keys -const mainnetKey = await client.getKey('wallet/eth/mainnet'); -const testnetKey = await client.getKey('wallet/eth/testnet'); +const quote = await client.getQuote('app-state-snapshot') +console.log(quote.quote) +console.log(quote.replayRtmrs()) ``` -**Parameters:** -- `path`: Key derivation path (determines the key) -- `purpose` (optional): Included in signature chain message, does not affect the derived key - -**Returns:** `GetKeyResponse` -- `key`: Hex-encoded private key -- `signature_chain`: Signatures proving the key was derived in a genuine TEE - -### Generate Attestation Quotes - -`getQuote()` creates a TDX quote proving your code runs in a genuine TEE. +The constructor probes `/var/run/dstack.sock`, then `/run/dstack.sock`, then the `/var/run/dstack/` and `/run/dstack/` variants. Pass an explicit endpoint for HTTP or for a non-default socket: ```typescript -const quote = await client.getQuote('user:alice:nonce123'); - -// Replay RTMRs from the event log -const rtmrs = quote.replayRtmrs(); -console.log(rtmrs); +const client = new DstackClient('http://localhost:8090') // simulator +const client = new DstackClient('/run/dstack/dstack.sock') // custom path ``` -**Parameters:** -- `reportData`: Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256). +`DSTACK_SIMULATOR_ENDPOINT` overrides the default when set. -**Returns:** `GetQuoteResponse` -- `quote`: Hex-encoded TDX quote -- `event_log`: JSON string of measured events -- `replayRtmrs()`: Method to compute RTMR values from event log +## Keys -### Get Instance Info +### `getKey(path?, purpose?, algorithm?)` -```typescript -const info = await client.info(); -console.log(info.app_id); -console.log(info.instance_id); -console.log(info.tcb_info); -``` - -**Returns:** `InfoResponse` -- `app_id`: Application identifier -- `instance_id`: Instance identifier -- `app_name`: Application name -- `tcb_info`: TCB measurements (MRTD, RTMRs, event log) -- `compose_hash`: Hash of the app configuration -- `app_cert`: Application certificate (PEM) - -### Generate TLS Certificates - -`getTlsKey()` creates fresh TLS certificates. Unlike `getKey()`, each call generates a new random key. +Derive a deterministic key. Same `(app_id, path, purpose, algorithm)` always returns the same key; different apps deriving on the same path get different keys. ```typescript -const tls = await client.getTlsKey({ - subject: 'api.example.com', - altNames: ['localhost'], - usageRaTls: true // Embed attestation in certificate -}); - -console.log(tls.key); // PEM private key -console.log(tls.certificate_chain); // Certificate chain +const eth = await client.getKey('wallet/ethereum') // secp256k1 (default) +const sol = await client.getKey('wallet/solana', 'mainnet', 'ed25519') // ed25519 ``` -**Parameters:** -- `subject` (optional): Certificate common name (e.g., domain name) -- `altNames` (optional): List of subject alternative names -- `usageRaTls` (optional): Embed TDX quote in certificate extension -- `usageServerAuth` (optional): Enable for server authentication (default: `true`) -- `usageClientAuth` (optional): Enable for client authentication (default: `false`) +Returns `{ key: Uint8Array, signature_chain: Uint8Array[] }`. The signature chain proves the key was derived inside a genuine TEE. -**Returns:** `GetTlsKeyResponse` -- `key`: PEM-encoded private key -- `certificate_chain`: List of PEM certificates +`algorithm`: `'secp256k1'` (default), `'k256'` (alias), or `'ed25519'`. ed25519 requires guest agent ≥ 0.5.7. -### Sign and Verify +### `getTlsKey(options?)` -Sign data using TEE-derived keys (not yet released): +Generate a fresh random TLS keypair plus certificate chain. Every call returns a new key — use `getKey` for deterministic material. ```typescript -const result = await client.sign('ed25519', 'message to sign'); -console.log(result.signature); -console.log(result.public_key); - -// Verify the signature -const valid = await client.verify('ed25519', 'message to sign', result.signature, result.public_key); -console.log(valid.valid); // true +const tls = await client.getTlsKey({ + subject: 'api.example.com', + altNames: ['localhost', '127.0.0.1'], + usageRaTls: true, // embed TDX quote in cert extension +}) ``` -**`sign()` Parameters:** -- `algorithm`: `'ed25519'`, `'secp256k1'`, or `'secp256k1_prehashed'` -- `data`: Data to sign (string, Buffer, or Uint8Array) - -**`sign()` Returns:** `SignResponse` -- `signature`: Hex-encoded signature -- `public_key`: Hex-encoded public key -- `signature_chain`: Signatures proving TEE origin +Options: `subject`, `altNames`, `usageRaTls`, `usageServerAuth` (default `true`), `usageClientAuth` (default `false`), and — on guest agent ≥ 0.5.7 — `notBefore`, `notAfter` (Unix seconds), `withAppInfo`. The client probes `version()` before sending the new options and throws a clear error on older agents instead of silently dropping them. -**`verify()` Parameters:** -- `algorithm`: Algorithm used for signing -- `data`: Original data -- `signature`: Signature to verify -- `public_key`: Public key to verify against +Returns `{ key: string, certificate_chain: string[], asUint8Array(maxLength?) }`. `key` is PEM-encoded. -**`verify()` Returns:** `VerifyResponse` -- `valid`: Boolean indicating if signature is valid +## Attestation -### Emit Events +### `getQuote(reportData)` -Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record. +Generate a raw TDX quote. `reportData` is up to 64 bytes (string, Buffer, or Uint8Array). ```typescript -await client.emitEvent('config_loaded', 'production'); -await client.emitEvent('plugin_initialized', 'auth-v2'); +const quote = await client.getQuote('user:alice:nonce123') +quote.quote // hex-encoded TDX quote +quote.event_log // JSON string of measured events +quote.replayRtmrs() // recompute RTMR[0..3] from the event log ``` -**Parameters:** -- `event`: Event name (string identifier) -- `payload`: Event value (string, Buffer, or Uint8Array) - -## Blockchain Integration +### `attest(reportData)` -### Ethereum with Viem +Versioned dstack attestation that works across TDX / GCP / Nitro providers. Preferred for cross-platform verifiers. ```typescript -import { toViemAccount } from '@phala/dstack-sdk/viem'; -import { createWalletClient, http } from 'viem'; -import { mainnet } from 'viem/chains'; - -const key = await client.getKey('wallet/ethereum'); -const account = toViemAccount(key); - -const wallet = createWalletClient({ - account, - chain: mainnet, - transport: http() -}); +const { attestation } = await client.attest('app-state-snapshot') ``` -### Solana +### `info()` -```typescript -import { toKeypair } from '@phala/dstack-sdk/solana'; +App identity and TCB metadata. -const key = await client.getKey('wallet/solana'); -const keypair = toKeypair(key); -console.log(keypair.publicKey.toBase58()); -``` - -## Development - -For local development without TDX hardware, use the simulator: - -```bash -git clone https://github.com/Dstack-TEE/dstack.git -cd dstack/sdk/simulator -./build.sh -./dstack-simulator -``` - -Then set the endpoint: - -```bash -export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 +```typescript +const info = await client.info() +info.app_id // application identifier +info.instance_id // CVM instance identifier +info.tcb_info // parsed { mrtd, rtmr0..3, event_log, ... } +info.compose_hash +info.cloud_vendor // e.g. "Google" (guest agent ≥ 0.5.7) +info.cloud_product // e.g. "Google Compute Engine" (guest agent ≥ 0.5.7) ``` ---- +### `version()` -## Deployment Utilities +Returns `{ version, rev }` of the guest agent. Throws on agents older than 0.5.7 (the RPC didn't exist). -These utilities are for deployment scripts, not runtime SDK operations. +## Sign and verify -### Encrypt Environment Variables +### `sign(algorithm, data)` -Encrypt secrets before deploying to dstack: +Sign data with a derived key. The SDK rejects mismatched input early — `secp256k1_prehashed` requires a 32-byte digest. ```typescript -import { encryptEnvVars, verifyEnvEncryptPublicKey, verifyEnvEncryptPublicKeyLegacy, type EnvVar } from '@phala/dstack-sdk'; - -// Get and verify the KMS public key -// (obtain public_key, signature_v1, and timestamp from KMS API) - -// Prefer signature_v1 with timestamp (prevents replay attacks) -const kmsIdentity = verifyEnvEncryptPublicKey(publicKeyBytes, signatureV1Bytes, appId, timestamp); -if (!kmsIdentity) { - // Fall back to legacy signature for backward compatibility with older KMS - const legacyIdentity = verifyEnvEncryptPublicKeyLegacy(publicKeyBytes, signatureBytes, appId); - if (!legacyIdentity) { - throw new Error('Invalid KMS key'); - } - console.warn('Using legacy signature without timestamp protection'); -} - -// Encrypt variables -const envVars: EnvVar[] = [ - { key: 'DATABASE_URL', value: 'postgresql://...' }, - { key: 'API_KEY', value: 'secret' } -]; -const encrypted = await encryptEnvVars(envVars, publicKey); +const res = await client.sign('ed25519', 'hello dstack') +res.signature // Uint8Array +res.public_key // Uint8Array +res.signature_chain // Uint8Array[] — proves the signing key came from this TEE ``` -## API Reference - -### DstackClient +Algorithms: `ed25519`, `secp256k1`, `secp256k1_prehashed`. Requires guest agent ≥ 0.5.7. -#### Constructor +### `verify(algorithm, data, signature, publicKey)` ```typescript -new DstackClient(endpoint?: string) +const ok = await client.verify('ed25519', 'hello dstack', res.signature, res.public_key) +ok.valid // boolean ``` -**Parameters:** -- `endpoint` (optional): Connection endpoint - - Unix socket path (production): `/var/run/dstack.sock` - - HTTP/HTTPS URL (development): `http://localhost:8090` - - Environment variable: `DSTACK_SIMULATOR_ENDPOINT` - -**Production App Configuration:** - -The Docker Compose configuration is embedded in `app-compose.json`: - -```json -{ - "manifest_version": 1, - "name": "production-app", - "runner": "docker-compose", - "docker_compose_file": "services:\n app:\n image: your-app\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - NODE_ENV=production", - "public_tcbinfo": true -} -``` - -**Important**: The `docker_compose_file` contains YAML content as a string, ensuring the volume binding for `/var/run/dstack.sock` is included. - -#### Methods - -##### `info(): Promise` - -Retrieves comprehensive information about the TEE instance. +### `emitEvent(event, payload)` -**Returns:** `InfoResponse` -- `app_id`: Unique application identifier -- `instance_id`: Unique instance identifier -- `app_name`: Application name from configuration -- `device_id`: TEE device identifier -- `tcb_info`: Trusted Computing Base information - - `mrtd`: Measurement of TEE domain - - `rtmr0-3`: Runtime Measurement Registers - - `event_log`: Boot and runtime events - - `os_image_hash`: Operating system measurement - - `compose_hash`: Application configuration hash -- `app_cert`: Application certificate in PEM format -- `key_provider_info`: Key management configuration - -##### `getKey(path: string, purpose?: string): Promise` - -Derives a deterministic secp256k1/K256 private key for blockchain and Web3 applications. This is the primary method for obtaining cryptographic keys for wallets, signing, and other deterministic key scenarios. - -**Parameters:** -- `path`: Unique identifier for key derivation (e.g., `"wallet/ethereum"`, `"signing/solana"`) -- `purpose` (optional): Additional context for key usage (default: `""`) - -**Returns:** `GetKeyResponse` -- `key`: 32-byte secp256k1 private key as `Uint8Array` (suitable for Ethereum, Bitcoin, Solana, etc.) -- `signature_chain`: Array of cryptographic signatures proving key authenticity - -**Key Characteristics:** -- **Deterministic**: Same path + purpose always generates identical key -- **Isolated**: Different paths produce cryptographically independent keys -- **Blockchain-Ready**: Compatible with secp256k1 curve (Ethereum, Bitcoin, Solana) -- **Verifiable**: Signature chain proves key was derived inside genuine TEE - -**Use Cases:** -- Cryptocurrency wallets -- Transaction signing -- DeFi protocol interactions -- NFT operations -- Any scenario requiring consistent, reproducible keys +Extends RTMR3 with a custom event. The event becomes part of the next quote's event log and cannot be removed. ```typescript -// Examples of deterministic key derivation -const ethWallet = await client.getKey('wallet/ethereum', 'mainnet'); -const btcWallet = await client.getKey('wallet/bitcoin', 'mainnet'); -const solWallet = await client.getKey('wallet/solana', 'mainnet'); - -// Same path always returns same key -const key1 = await client.getKey('my-app/signing'); -const key2 = await client.getKey('my-app/signing'); -// key1.key === key2.key (guaranteed identical) - -// Different paths return different keys -const userA = await client.getKey('user/alice/wallet'); -const userB = await client.getKey('user/bob/wallet'); -// userA.key !== userB.key (guaranteed different) +await client.emitEvent('config_loaded', 'v1.0.0') ``` -##### `getQuote(reportData: string | Buffer | Uint8Array): Promise` +Requires guest agent ≥ 0.5.0. -Generates a TDX attestation quote containing the provided report data. +## Diagnostics -**Parameters:** -- `reportData`: Data to include in quote (max 64 bytes) +### `isReachable()` -**Returns:** `GetQuoteResponse` -- `quote`: TDX quote as hex string -- `event_log`: JSON string of system events -- `replayRtmrs()`: Function returning computed RTMR values +Sub-500ms probe against `/Info`. Returns a boolean and never throws — useful for liveness checks. -**Use Cases:** -- Remote attestation of application state -- Cryptographic proof of execution environment -- Audit trail generation +## Blockchain helpers -##### `attest(reportData: string | Buffer | Uint8Array): Promise` +### Ethereum -Generates a versioned attestation containing the provided report data. +```typescript +import { toViemAccountSecure } from '@phala/dstack-sdk/viem' +import { createWalletClient, http } from 'viem' +import { mainnet } from 'viem/chains' -**Parameters:** -- `reportData`: Data to include in attestation (max 64 bytes) +const key = await client.getKey('wallet/ethereum') +const account = toViemAccountSecure(key) -**Returns:** `AttestResponse` -- `attestation`: Hex-encoded attestation payload +const wallet = createWalletClient({ account, chain: mainnet, transport: http() }) +``` -**Use Cases:** -- Remote attestation across multiple platform types -- Verifier APIs that accept versioned attestations +`toViemAccountSecure` hashes the derived key with SHA-256 before passing it to viem's `privateKeyToAccount`. The unhashed alternative `toViemAccount` is kept for migration only and emits a warning. -##### `getTlsKey(options?: TlsKeyOptions): Promise` +### Solana -Generates a fresh, random TLS key pair with X.509 certificate for TLS/SSL connections. **Important**: This method generates different keys on each call - use `getKey()` for deterministic keys. +```typescript +import { toKeypairSecure } from '@phala/dstack-sdk/solana' -**Parameters:** `TlsKeyOptions` -- `subject` (optional): Certificate subject (Common Name) - typically the domain name (default: `""`) -- `altNames` (optional): Subject Alternative Names - additional domains/IPs for the certificate (default: `[]`) -- `usageRaTls` (optional): Include TDX attestation quote in certificate extension for remote verification (default: `false`) -- `usageServerAuth` (optional): Enable server authentication - allows certificate to authenticate servers (default: `true`) -- `usageClientAuth` (optional): Enable client authentication - allows certificate to authenticate clients (default: `false`) +const key = await client.getKey('wallet/solana') +const keypair = toKeypairSecure(key) +console.log(keypair.publicKey.toBase58()) +``` -**Returns:** `GetTlsKeyResponse` -- `key`: Private key in PEM format (X.509/PKCS#8) -- `certificate_chain`: Certificate chain array +Same pattern as the Ethereum helper. `toKeypair` is the unhashed legacy variant. -**Key Characteristics:** -- **Random Generation**: Each call produces a completely different key -- **TLS-Optimized**: Keys and certificates designed for TLS/SSL scenarios -- **RA-TLS Support**: Optional remote attestation extension in certificates -- **TEE-Signed**: Certificates signed by TEE-resident Certificate Authority +## Compose hash -**Certificate Usage Scenarios:** +```typescript +import { getComposeHash, type AppCompose } from '@phala/dstack-sdk/get-compose-hash' + +const compose: AppCompose = { + manifest_version: 2, + name: 'my-app', + runner: 'docker-compose', + docker_compose_file: '...', + kms_enabled: true, +} -1. **Standard HTTPS Server** (`usageServerAuth: true`, `usageClientAuth: false`) - - Web servers, API endpoints - - Server authenticates to clients - - Most common TLS use case +const hash = getComposeHash(compose) +const normalized = getComposeHash(compose, true) // strip bash_script/docker_compose_file overlap +``` -2. **Remote Attestation Server** (`usageRaTls: true`) - - TEE-based services requiring proof of execution environment - - Clients can verify the server runs in genuine TEE - - Combines TLS with hardware attestation +Pure function — no TEE call required. Produces the canonical SHA-256 used by the on-chain KMS allowlist. -3. **mTLS Client Certificate** (`usageServerAuth: false`, `usageClientAuth: true`) - - Client authentication in mutual TLS - - API clients, service-to-service communication - - Client proves identity to server +## Encrypted environment variables -4. **Dual-Purpose Certificate** (`usageServerAuth: true`, `usageClientAuth: true`) - - Services that act as both client and server - - Microservices architectures - - Maximum flexibility for TLS roles +The full deployment flow mirrors `vmm-cli.py`: fetch the env-encrypt public key from KMS, verify its signature locally, then ECIES-encrypt the env vars against it. ```typescript -// Example 1: Standard HTTPS server certificate -const serverCert = await client.getTlsKey({ - subject: 'api.example.com', - altNames: ['api.example.com', 'www.api.example.com', '10.0.0.1'] - // usageServerAuth: true (default) - allows server authentication - // usageClientAuth: false (default) - no client authentication -}); - -// Example 2: Certificate with remote attestation (RA-TLS) -const attestedCert = await client.getTlsKey({ - subject: 'secure-api.example.com', - usageRaTls: true // Include TDX quote for remote verification - // Clients can verify the TEE environment through the certificate -}); - -// Example 3: Mutual TLS (mTLS) certificate for client authentication -const clientCert = await client.getTlsKey({ - subject: 'client.example.com', - usageServerAuth: false, // This certificate won't authenticate servers - usageClientAuth: true // Enable client authentication -}); - -// Example 4: Certificate for both server and client authentication -const dualUseCert = await client.getTlsKey({ - subject: 'dual.example.com', - usageServerAuth: true, // Can authenticate as server - usageClientAuth: true // Can authenticate as client -}); - -// ⚠️ Each call generates different keys (unlike getKey) -const cert1 = await client.getTlsKey(); -const cert2 = await client.getTlsKey(); -// cert1.key !== cert2.key (always different) - -// Use with Node.js HTTPS server -import https from 'https'; -const server = https.createServer({ - key: serverCert.key, - cert: serverCert.certificate_chain.join('\n') -}, app); -``` - -##### `emitEvent(event: string, payload: string | Buffer | Uint8Array): Promise` - -Extends RTMR3 with a custom event for audit logging. +import { + verifyEnvEncryptPublicKey, + verifyEnvEncryptPublicKeyLegacy, +} from '@phala/dstack-sdk' +import { encryptEnvVars, type EnvVar } from '@phala/dstack-sdk/encrypt-env-vars' + +const response = await fetch(`${kmsUrl}/prpc/GetAppEnvEncryptPubKey?json`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ app_id: appId }), +}).then(r => r.json()) + +const publicKey = Buffer.from(response.public_key, 'hex') + +// Prefer v1 (timestamp-protected against replay) +let signer = response.signature_v1 + ? verifyEnvEncryptPublicKey( + publicKey, + Buffer.from(response.signature_v1, 'hex'), + appId, + BigInt(response.timestamp), + ) + : null + +// Fall back to legacy signature on older KMS +if (!signer && response.signature) { + signer = verifyEnvEncryptPublicKeyLegacy( + publicKey, + Buffer.from(response.signature, 'hex'), + appId, + ) +} -**Parameters:** -- `event`: Event identifier string -- `payload`: Event data +if (!signer) throw new Error('KMS signature did not verify') -**Requirements:** -- dstack OS version 0.5.0 or later -- Events are permanently recorded in TEE measurements +const envs: EnvVar[] = [ + { key: 'DATABASE_URL', value: 'postgresql://…' }, + { key: 'API_KEY', value: 'sk-test-1234' }, +] +const encrypted = await encryptEnvVars(envs, response.public_key) +``` -##### `isReachable(): Promise` +Verify functions return the signer's compressed public key (hex) on success, or `null` on failure. Check the signer against your trusted-signer whitelist before encrypting. -Tests connectivity to the dstack service. +## Compatibility -**Returns:** `boolean` indicating service availability +| Feature | Minimum guest agent | +| --- | --- | +| `getKey`, `getTlsKey`, `getQuote`, `info` | 0.3.x | +| `emitEvent` | 0.5.0 | +| `attest`, `sign`, `verify`, `version`, ed25519 keys, `info.cloud_vendor` / `cloud_product`, `getTlsKey` `notBefore` / `notAfter` / `withAppInfo` | 0.5.7 | -## Utility Functions +The SDK's release versions track guest agent versions — `0.5.8-x` targets dstack 0.5.7+. -### Compose Hash Calculation +## Development -```typescript -import { getComposeHash } from '@phala/dstack-sdk'; +Run the standalone simulator instead of a real TDX host: -const hash = getComposeHash(appComposeObject); +```bash +cd dstack/sdk/simulator +./build.sh +./dstack-simulator +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 ``` ---- +Then point `new DstackClient()` at the simulator (it picks up `DSTACK_SIMULATOR_ENDPOINT` automatically). ## Migration from TappdClient -Replace `TappdClient` with `DstackClient`: +`TappdClient` and its `deriveKey` / `tdxQuote` methods are deprecated but still exported. Replace them with `DstackClient` and the new methods: -```typescript -// Before -import { TappdClient } from '@phala/dstack-sdk'; -const client = new TappdClient(); - -// After -import { DstackClient } from '@phala/dstack-sdk'; -const client = new DstackClient(); -``` +| Old | New | +| --- | --- | +| `new TappdClient()` | `new DstackClient()` | +| `client.deriveKey(path, subject)` | `client.getTlsKey({ subject })` | +| `client.tdxQuote(data)` | `client.getQuote(data)` | +| `/var/run/tappd.sock` | `/var/run/dstack.sock` | -Method changes: -- `deriveKey()` → `getTlsKey()` for TLS certificates -- `tdxQuote()` → `getQuote()` (raw data only, no hash algorithms) -- Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` +`toViemAccount` and `toKeypair` are kept for the same reason; prefer their `Secure` variants in new code. ## License -Apache License 2.0 +Apache-2.0 diff --git a/sdk/js/bun.lock b/sdk/js/bun.lock new file mode 100644 index 000000000..8a9836ad3 --- /dev/null +++ b/sdk/js/bun.lock @@ -0,0 +1,423 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@phala/dstack-sdk", + "devDependencies": { + "@noble/curves": "^1.8.1", + "@noble/hashes": "^1.6.1", + "@solana/web3.js": "^1.98.4", + "@types/node": "latest", + "tsup": "^8.5.1", + "typescript": "^5.7.0", + "viem": "^2.43.3", + "vitest": "^3.2.4", + }, + "peerDependencies": { + "@noble/curves": "^1.8.1", + "@noble/hashes": "^1.6.1", + "@solana/web3.js": "^1.98.4", + "viem": "^2.43.3", + }, + "optionalPeers": [ + "@noble/curves", + "@solana/web3.js", + "viem", + ], + }, + }, + "packages": { + "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.4", "", { "os": "none", "cpu": "arm64" }, "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], + + "@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + + "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + + "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], + + "@solana/codecs-core": ["@solana/codecs-core@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw=="], + + "@solana/codecs-numbers": ["@solana/codecs-numbers@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg=="], + + "@solana/errors": ["@solana/errors@2.3.0", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0" }, "peerDependencies": { "typescript": ">=5.3.3" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ=="], + + "@solana/web3.js": ["@solana/web3.js@1.98.4", "", { "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", "jayson": "^4.1.1", "node-fetch": "^2.7.0", "rpc-websockets": "^9.0.2", "superstruct": "^2.0.2" } }, "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw=="], + + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@25.9.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ=="], + + "@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], + + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="], + + "borsh": ["borsh@0.7.0", "", { "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", "text-encoding-utf-8": "^1.0.2" } }, "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA=="], + + "bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "bufferutil": ["bufferutil@4.1.0", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "delay": ["delay@5.0.0", "", {}, "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "es6-promise": ["es6-promise@4.2.8", "", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="], + + "es6-promisify": ["es6-promisify@5.0.0", "", { "dependencies": { "es6-promise": "^4.0.3" } }, "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + + "fast-stable-stringify": ["fast-stable-stringify@1.0.0", "", {}, "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + + "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], + + "jayson": ["jayson@4.3.0", "", { "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", "@types/ws": "^7.4.4", "commander": "^2.20.3", "delay": "^5.0.0", "es6-promisify": "^5.0.0", "eyes": "^0.1.8", "isomorphic-ws": "^4.0.1", "json-stringify-safe": "^5.0.1", "stream-json": "^1.9.1", "uuid": "^8.3.2", "ws": "^7.5.10" }, "bin": { "jayson": "bin/jayson.js" } }, "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "ox": ["ox@0.14.22", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-nb5msL8qWbPglhIfZbGJAfw3cqiJjFMiWmACt7kgyWtLib12tcctbHufMT9Hb0Lr6Pt4k9I3dbpueTpbhvbqvA=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], + + "rpc-websockets": ["rpc-websockets@9.3.10", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/ws": "^8.2.2", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "ws": "^8.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^6.0.0" } }, "sha512-QT5PQ6LiWhA5RCS93oWwgxU4XzQltkYm8C3aTmmKEgj0HolGRo3VbdzELw7CEV35l9T7Amha8Vnr4rCfSjVP+w=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], + + "text-encoding-utf-8": ["text-encoding-utf-8@1.0.2", "", {}, "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "utf-8-validate": ["utf-8-validate@6.0.6", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "viem": ["viem@2.50.4", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.22", "ws": "8.20.1" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-rf98F4s3Vlb+uJZEKfay3IbBw3CNCbVtx5Y3UIljlO2tSX420g/J0WQSYsjzBSasUFgxgsXabji14O9kGbiqgg=="], + + "vite": ["vite@7.3.3", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA=="], + + "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], + + "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + + "@solana/errors/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "jayson/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], + + "jayson/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + + "ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "rpc-websockets/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + } +} diff --git a/sdk/js/bun.lockb b/sdk/js/bun.lockb deleted file mode 100755 index 63682ed79..000000000 Binary files a/sdk/js/bun.lockb and /dev/null differ diff --git a/sdk/js/package.json b/sdk/js/package.json index cfc8d8c49..ff95e5b4c 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -1,87 +1,68 @@ { "name": "@phala/dstack-sdk", - "version": "0.5.8-beta.0", + "version": "0.5.8-beta.2", "description": "dstack SDK", - "main": "dist/node/index.js", - "types": "dist/node/index.d.ts", - "browser": { - "crypto": "crypto-browserify" - }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": "./dist/node/index.js", - "require": "./dist/node/index.js", - "types": "./dist/node/index.d.ts" + "types": { + "import": "./dist/index.d.mts", + "require": "./dist/index.d.ts" + }, + "import": "./dist/index.mjs", + "require": "./dist/index.js" }, "./viem": { - "import": "./dist/node/viem.js", - "require": "./dist/node/viem.js", - "types": "./dist/node/viem.d.ts" - }, - "./encrypt-env-vars": { - "node": { - "import": "./dist/node/encrypt-env-vars.js", - "require": "./dist/node/encrypt-env-vars.js", - "types": "./dist/node/encrypt-env-vars.d.ts" + "types": { + "import": "./dist/viem.d.mts", + "require": "./dist/viem.d.ts" }, - "browser": { - "import": "./dist/browser/encrypt-env-vars.browser.js", - "require": "./dist/browser/encrypt-env-vars.browser.js", - "types": "./dist/browser/encrypt-env-vars.browser.d.ts" - }, - "default": { - "import": "./dist/browser/encrypt-env-vars.browser.js", - "require": "./dist/browser/encrypt-env-vars.browser.js", - "types": "./dist/browser/encrypt-env-vars.browser.d.ts" - } + "import": "./dist/viem.mjs", + "require": "./dist/viem.js" }, "./solana": { - "import": "./dist/node/solana.js", - "require": "./dist/node/solana.js", - "types": "./dist/node/solana.d.ts" + "types": { + "import": "./dist/solana.d.mts", + "require": "./dist/solana.d.ts" + }, + "import": "./dist/solana.mjs", + "require": "./dist/solana.js" }, - "./get-compose-hash": { - "node": { - "import": "./dist/node/get-compose-hash.js", - "require": "./dist/node/get-compose-hash.js", - "types": "./dist/node/get-compose-hash.d.ts" + "./encrypt-env-vars": { + "types": { + "import": "./dist/encrypt-env-vars.d.mts", + "require": "./dist/encrypt-env-vars.d.ts" }, - "browser": { - "import": "./dist/browser/get-compose-hash.browser.js", - "require": "./dist/browser/get-compose-hash.browser.js", - "types": "./dist/browser/get-compose-hash.browser.d.ts" + "import": "./dist/encrypt-env-vars.mjs", + "require": "./dist/encrypt-env-vars.js" + }, + "./get-compose-hash": { + "types": { + "import": "./dist/get-compose-hash.d.mts", + "require": "./dist/get-compose-hash.d.ts" }, - "default": { - "import": "./dist/browser/get-compose-hash.browser.js", - "require": "./dist/browser/get-compose-hash.browser.js", - "types": "./dist/browser/get-compose-hash.browser.d.ts" - } + "import": "./dist/get-compose-hash.mjs", + "require": "./dist/get-compose-hash.js" }, "./verify-env-encrypt-public-key": { - "node": { - "import": "./dist/node/verify-env-encrypt-public-key.js", - "require": "./dist/node/verify-env-encrypt-public-key.js", - "types": "./dist/node/verify-env-encrypt-public-key.d.ts" + "types": { + "import": "./dist/verify-env-encrypt-public-key.d.mts", + "require": "./dist/verify-env-encrypt-public-key.d.ts" }, - "browser": { - "import": "./dist/browser/verify-env-encrypt-public-key.browser.js", - "require": "./dist/browser/verify-env-encrypt-public-key.browser.js", - "types": "./dist/browser/verify-env-encrypt-public-key.browser.d.ts" - }, - "default": { - "import": "./dist/browser/verify-env-encrypt-public-key.browser.js", - "require": "./dist/browser/verify-env-encrypt-public-key.browser.js", - "types": "./dist/browser/verify-env-encrypt-public-key.browser.d.ts" - } + "import": "./dist/verify-env-encrypt-public-key.mjs", + "require": "./dist/verify-env-encrypt-public-key.js" } }, "engines": { "node": ">=18.0.0" }, + "files": [ + "dist" + ], "scripts": { - "build": "npm run build:node && npm run build:browser", - "build:node": "tsc -p tsconfig.node.json", - "build:browser": "tsc -p tsconfig.browser.json", + "build": "tsup", "clean": "rm -rf dist", "test": "vitest", "test:ci": "vitest --run", @@ -99,32 +80,31 @@ }, "author": "Leechael Yim", "license": "Apache-2.0", - "dependencies": { - "crypto-browserify": "^3.12.0" - }, "devDependencies": { + "@noble/curves": "^1.8.1", + "@noble/hashes": "^1.6.1", + "@solana/web3.js": "^1.98.4", "@types/node": "latest", + "tsup": "^8.5.1", "typescript": "^5.7.0", + "viem": "^2.43.3", "vitest": "^3.2.4" }, - "optionalDependencies": { + "peerDependencies": { "@noble/curves": "^1.8.1", + "@noble/hashes": "^1.6.1", "@solana/web3.js": "^1.98.4", "viem": "^2.43.3" }, - "peerDependencies": { - "@noble/curves": "^1.8.1", - "@noble/hashes": "^1.6.1" - }, "peerDependenciesMeta": { - "viem": { - "optional": true - }, "@noble/curves": { "optional": true }, "@solana/web3.js": { "optional": true + }, + "viem": { + "optional": true } } } diff --git a/sdk/js/src/encrypt-env-vars.browser.ts b/sdk/js/src/encrypt-env-vars.browser.ts deleted file mode 100644 index 0484b1b6b..000000000 --- a/sdk/js/src/encrypt-env-vars.browser.ts +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: © 2025 Phala Network -// -// SPDX-License-Identifier: Apache-2.0 - -import { x25519 } from "@noble/curves/ed25519" - -// Convert hex string to Uint8Array -function hexToUint8Array(hex: string) { - hex = hex.startsWith("0x") ? hex.slice(2) : hex; - return new Uint8Array( - hex.match(/.{1,2}/g)?.map((byte: string) => parseInt(byte, 16)) ?? [], - ); -} - -function uint8ArrayToHex(buffer: Uint8Array) { - return Array.from(buffer) - .map((byte: number) => byte.toString(16).padStart(2, "0")) - .join(""); -} - -export interface EnvVar { - key: string - value: string -} - -// Encrypt environment variables - using the same implementation as Node.js version -export async function encryptEnvVars(envs: EnvVar[], publicKeyHex: string) { - // Prepare environment data - const envsJson = JSON.stringify({ env: envs }); - - // Generate private key and derive public key - const privateKey = x25519.utils.randomPrivateKey(); - const publicKey = x25519.getPublicKey(privateKey); - - // Generate shared key - const remotePubkey = hexToUint8Array(publicKeyHex); - const shared = x25519.getSharedSecret(privateKey, remotePubkey); - - // Import shared key for AES-GCM - const importedShared = await crypto.subtle.importKey( - "raw", - new Uint8Array(shared), - { name: "AES-GCM", length: 256 }, - true, - ["encrypt"], - ); - - // Encrypt the data - const iv = crypto.getRandomValues(new Uint8Array(12)); - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - importedShared, - new TextEncoder().encode(envsJson), - ); - - // Combine all components - const result = new Uint8Array( - publicKey.length + iv.length + encrypted.byteLength, - ); - - result.set(publicKey); - result.set(iv, publicKey.length); - result.set(new Uint8Array(encrypted), publicKey.length + iv.length); - - return uint8ArrayToHex(result); -} \ No newline at end of file diff --git a/sdk/js/src/encrypt-env-vars.ts b/sdk/js/src/encrypt-env-vars.ts index cef27b9f1..b57f2001a 100644 --- a/sdk/js/src/encrypt-env-vars.ts +++ b/sdk/js/src/encrypt-env-vars.ts @@ -3,20 +3,18 @@ // SPDX-License-Identifier: Apache-2.0 import { x25519 } from "@noble/curves/ed25519" -import crypto from 'crypto' -// Convert hex string to Uint8Array -function hexToUint8Array(hex: string) { - hex = hex.startsWith("0x") ? hex.slice(2) : hex; +function hexToUint8Array(hex: string): Uint8Array { + hex = hex.startsWith("0x") ? hex.slice(2) : hex return new Uint8Array( - hex.match(/.{1,2}/g)?.map((byte: string) => parseInt(byte, 16)) ?? [], - ); + hex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [], + ) } -function uint8ArrayToHex(buffer: Uint8Array) { +function uint8ArrayToHex(buffer: Uint8Array): string { return Array.from(buffer) - .map((byte: number) => byte.toString(16).padStart(2, "0")) - .join(""); + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") } export interface EnvVar { @@ -24,44 +22,44 @@ export interface EnvVar { value: string } -// Encrypt environment variables -export async function encryptEnvVars(envs: EnvVar[], publicKeyHex: string) { - // Prepare environment data - const envsJson = JSON.stringify({ env: envs }); +/** + * ECIES-encrypt a set of environment variables against a recipient's x25519 + * public key. Works on Node 18+ and modern browsers — uses `globalThis.crypto` + * (Web Crypto API) and @noble/curves. + */ +export async function encryptEnvVars( + envs: EnvVar[], + publicKeyHex: string, +): Promise { + const envsJson = JSON.stringify({ env: envs }) - // Generate private key and derive public key - const privateKey = x25519.utils.randomPrivateKey(); - const publicKey = x25519.getPublicKey(privateKey); + const privateKey = x25519.utils.randomPrivateKey() + const publicKey = x25519.getPublicKey(privateKey) - // Generate shared key - const remotePubkey = hexToUint8Array(publicKeyHex); - const shared = x25519.getSharedSecret(privateKey, remotePubkey); + const remotePubkey = hexToUint8Array(publicKeyHex) + const shared = x25519.getSharedSecret(privateKey, remotePubkey) - // Import shared key for AES-GCM const importedShared = await crypto.subtle.importKey( "raw", new Uint8Array(shared), { name: "AES-GCM", length: 256 }, true, ["encrypt"], - ); + ) - // Encrypt the data - const iv = crypto.getRandomValues(new Uint8Array(12)); + const iv = crypto.getRandomValues(new Uint8Array(12)) const encrypted = await crypto.subtle.encrypt( { name: "AES-GCM", iv }, importedShared, new TextEncoder().encode(envsJson), - ); + ) - // Combine all components const result = new Uint8Array( publicKey.length + iv.length + encrypted.byteLength, - ); + ) + result.set(publicKey) + result.set(iv, publicKey.length) + result.set(new Uint8Array(encrypted), publicKey.length + iv.length) - result.set(publicKey); - result.set(iv, publicKey.length); - result.set(new Uint8Array(encrypted), publicKey.length + iv.length); - - return uint8ArrayToHex(result); + return uint8ArrayToHex(result) } diff --git a/sdk/js/src/get-compose-hash.browser.ts b/sdk/js/src/get-compose-hash.browser.ts deleted file mode 100644 index b99bdfff6..000000000 --- a/sdk/js/src/get-compose-hash.browser.ts +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: © 2025 Phala Network -// -// SPDX-License-Identifier: Apache-2.0 - -import crypto from 'crypto'; - -type SortableValue = string | number | boolean | null | undefined | SortableObject | SortableArray; -interface SortableObject { - [key: string]: SortableValue; -} -interface SortableArray extends Array {} - -/** - * Recursively sorts object keys lexicographically. - * @param obj - The object to sort - * @returns A new object with sorted keys - */ -function sortObjectKeys(obj: SortableValue): SortableValue { - if (obj === null || obj === undefined) return obj; - if (typeof obj !== 'object') return obj; - if (Array.isArray(obj)) return obj.map(sortObjectKeys); - - const sortedObj: SortableObject = {}; - Object.keys(obj).sort().forEach(key => { - sortedObj[key] = sortObjectKeys((obj as SortableObject)[key]); - }); - return sortedObj; -} - -/** - * Browser-compatible SHA-256 hash using crypto-browserify - * @param data - Data to hash - * @returns Promise resolving to hex-encoded hash - */ -async function sha256Hash(data: string): Promise { - const hash = crypto.createHash('sha256'); - hash.update(data, 'utf8'); - return hash.digest('hex'); -} - -/** - * Get the hash of a docker-compose configuration - * @param compose - The docker-compose configuration object - * @returns Promise resolving to hex-encoded hash - */ -export async function getComposeHash(compose: Record): Promise { - // Sort the object keys to ensure deterministic hashing - const sortedCompose = sortObjectKeys(compose); - - // Convert to JSON string with no extra whitespace - const jsonString = JSON.stringify(sortedCompose); - - // Return SHA-256 hash - return sha256Hash(jsonString); -} \ No newline at end of file diff --git a/sdk/js/src/get-compose-hash.ts b/sdk/js/src/get-compose-hash.ts index 3f3a44ef4..2eb357b1e 100644 --- a/sdk/js/src/get-compose-hash.ts +++ b/sdk/js/src/get-compose-hash.ts @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: Apache-2.0 -import crypto from "crypto"; +import { sha256 } from "@noble/hashes/sha256"; +import { bytesToHex } from "@noble/hashes/utils"; type SortableValue = string | number | boolean | null | undefined | SortableObject | SortableArray; interface SortableObject { @@ -104,5 +105,5 @@ export function getComposeHash(app_compose: AppCompose, normalize: boolean = fal app_compose = preprocessAppCompose(app_compose); } const manifest_str = toDeterministicJson(app_compose); - return crypto.createHash("sha256").update(manifest_str, "utf8").digest("hex"); + return bytesToHex(sha256(new TextEncoder().encode(manifest_str))); } diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 72d472550..b582dec60 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 import fs from 'fs' -import crypto from 'crypto' +import { sha384 } from '@noble/hashes/sha512' import { send_rpc_request } from './send-rpc-request' export { getComposeHash } from './get-compose-hash' export { verifyEnvEncryptPublicKey, verifyEnvEncryptPublicKeyLegacy } from './verify-env-encrypt-public-key' @@ -87,6 +87,10 @@ export interface InfoResponse { key_provider_info: string compose_hash: string vm_config?: string + // Cloud provider sys_vendor (e.g. "Google"). Available on dstack OS >= 0.5.7. + cloud_vendor?: string + // Cloud provider product_name (e.g. "Google Compute Engine"). Available on dstack OS >= 0.5.7. + cloud_product?: string } export interface GetQuoteResponse { @@ -150,9 +154,7 @@ function replay_rtmr(history: string[]): string { const padding = Buffer.alloc(48 - contentBuffer.length, 0) contentBuffer = Buffer.concat([contentBuffer, padding]) } - mr = Buffer.from(crypto.createHash('sha384') - .update(Buffer.concat([mr, contentBuffer])) - .digest()) + mr = Buffer.from(sha384(Buffer.concat([mr, contentBuffer]))) } return mr.toString('hex') } @@ -175,6 +177,12 @@ export interface TlsKeyOptions { usageRaTls?: boolean; usageServerAuth?: boolean; usageClientAuth?: boolean; + // Certificate validity start (seconds since UNIX epoch). Requires dstack OS >= 0.5.7. + notBefore?: number; + // Certificate validity end (seconds since UNIX epoch). Requires dstack OS >= 0.5.7. + notAfter?: number; + // Embed app info into the certificate. Requires dstack OS >= 0.5.7. + withAppInfo?: boolean; } const SECP256K1_ALGORITHMS = new Set(['secp256k1', 'k256', '']) @@ -213,7 +221,15 @@ export class DstackClient { } } - async getKey(path: string, purpose: string = '', algorithm: string = 'secp256k1'): Promise { + private async ensureTlsKeyOptionsSupported(featureNames: string[]): Promise { + try { + await this.version() + } catch { + throw new Error(`TLS key options [${featureNames.join(', ')}] are not supported: OS version too old (Version RPC unavailable)`) + } + } + + async getKey(path: string = '', purpose: string = '', algorithm: string = 'secp256k1'): Promise { await this.ensureAlgorithmSupported(algorithm) const payload = JSON.stringify({ path: path, @@ -235,8 +251,19 @@ export class DstackClient { usageRaTls = false, usageServerAuth = true, usageClientAuth = false, + notBefore, + notAfter, + withAppInfo, } = options; + const newFeatures: string[] = [] + if (notBefore !== undefined) newFeatures.push('notBefore') + if (notAfter !== undefined) newFeatures.push('notAfter') + if (withAppInfo !== undefined) newFeatures.push('withAppInfo') + if (newFeatures.length > 0) { + await this.ensureTlsKeyOptionsSupported(newFeatures) + } + let raw: Record = { subject, usage_ra_tls: usageRaTls, @@ -246,6 +273,15 @@ export class DstackClient { if (altNames && altNames.length) { raw['alt_names'] = altNames } + if (notBefore !== undefined) { + raw['not_before'] = notBefore + } + if (notAfter !== undefined) { + raw['not_after'] = notAfter + } + if (withAppInfo !== undefined) { + raw['with_app_info'] = withAppInfo + } const payload = JSON.stringify(raw) const result = await send_rpc_request(this.endpoint, '/GetTlsKey', payload) const asUint8Array = (length?: number) => x509key_to_uint8array(result.key, length) diff --git a/sdk/js/src/solana.ts b/sdk/js/src/solana.ts index dffc86b54..7891c0e86 100644 --- a/sdk/js/src/solana.ts +++ b/sdk/js/src/solana.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -import crypto from 'crypto' +import { sha256 } from '@noble/hashes/sha256' import { type GetKeyResponse, type GetTlsKeyResponse } from './index' import { Keypair } from '@solana/web3.js' @@ -28,14 +28,9 @@ export function toKeypair(keyResponse: GetTlsKeyResponse | GetKeyResponse) { export function toKeypairSecure(keyResponse: GetTlsKeyResponse | GetKeyResponse) { // Keep legacy behavior for GetTlsKeyResponse, but with warning. if (keyResponse.__name__ === 'GetTlsKeyResponse') { - try { - console.warn('toKeypairSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.') - // Get supported hash algorithm by `openssl list -digest-algorithms`, but it's not guaranteed to be supported by node.js - const buf = crypto.createHash('sha256').update(keyResponse.asUint8Array()).digest() - return Keypair.fromSeed(buf) - } catch (err) { - throw new Error('toKeypairSecure: missing sha256 support, please upgrade your openssl and node.js') - } + console.warn('toKeypairSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.') + const buf = sha256(keyResponse.asUint8Array()) + return Keypair.fromSeed(buf) } return Keypair.fromSeed(keyResponse.key) } \ No newline at end of file diff --git a/sdk/js/src/verify-env-encrypt-public-key.browser.ts b/sdk/js/src/verify-env-encrypt-public-key.browser.ts deleted file mode 100644 index 523b52b4e..000000000 --- a/sdk/js/src/verify-env-encrypt-public-key.browser.ts +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-FileCopyrightText: © 2025 Phala Network -// -// SPDX-License-Identifier: Apache-2.0 - -import { keccak_256 } from "@noble/hashes/sha3"; -import { secp256k1 } from "@noble/curves/secp256k1"; - -/** Default maximum age for timestamp verification (5 minutes) */ -const DEFAULT_MAX_AGE_SECONDS = 300; - -/** - * Options for verifying env encrypt public key - */ -export interface VerifyOptions { - /** - * Maximum age of the response in seconds. - * If the timestamp is older than this, verification fails. - * Default: 300 (5 minutes) - */ - maxAgeSeconds?: number; -} - -/** - * Convert a bigint to big-endian bytes - */ -function bigintToBeBytes(value: bigint, length: number): Uint8Array { - const bytes = new Uint8Array(length); - for (let i = length - 1; i >= 0; i--) { - bytes[i] = Number(value & 0xffn); - value >>= 8n; - } - return bytes; -} - -/** - * Convert hex string to Uint8Array - */ -function hexToBytes(hex: string): Uint8Array { - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - bytes[i / 2] = parseInt(hex.substr(i, 2), 16); - } - return bytes; -} - -/** - * Verify the signature of a public key with timestamp validation. - * - * @param publicKey - The public key bytes to verify (32 bytes) - * @param signature - The signature bytes (65 bytes) - * @param appId - The application ID - * @param timestamp - Unix timestamp in seconds when the response was generated - * @param options - Optional verification options - * @returns The compressed public key if valid, null otherwise - * - * @example - * ```typescript - * const publicKey = new Uint8Array([...]); - * const signature = new Uint8Array([...]); - * const appId = '00'.repeat(20); - * const timestamp = 1700000000n; - * const compressedPubkey = await verifyEnvEncryptPublicKey(publicKey, signature, appId, timestamp); - * ``` - */ -export async function verifyEnvEncryptPublicKey( - publicKey: Uint8Array, - signature: Uint8Array, - appId: string, - timestamp: bigint | number, - options?: VerifyOptions -): Promise { - if (signature.length !== 65) { - return null; - } - - // Convert timestamp to bigint for consistent handling - const ts = typeof timestamp === 'bigint' ? timestamp : BigInt(timestamp); - - // Validate timestamp freshness - const maxAge = options?.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS; - const now = BigInt(Math.floor(Date.now() / 1000)); - const age = now - ts; - - if (age < 0n) { - // Timestamp is in the future - allow small clock skew (60 seconds) - if (age < -60n) { - console.error('timestamp is too far in the future'); - return null; - } - } else if (age > BigInt(maxAge)) { - console.error(`timestamp is too old: ${age}s > ${maxAge}s`); - return null; - } - - // Create the message to verify - const prefix = new TextEncoder().encode("dstack-env-encrypt-pubkey"); - - // Remove 0x prefix if present - let cleanAppId = appId; - if (appId.startsWith("0x")) { - cleanAppId = appId.slice(2); - } - - const appIdBytes = hexToBytes(cleanAppId); - const separator = new TextEncoder().encode(":"); - - // Convert timestamp to big-endian bytes (8 bytes) - const timestampBytes = bigintToBeBytes(ts, 8); - - // Construct message: prefix + ":" + app_id + timestamp_be_bytes + public_key - const message = new Uint8Array(prefix.length + separator.length + appIdBytes.length + timestampBytes.length + publicKey.length); - let offset = 0; - message.set(prefix, offset); offset += prefix.length; - message.set(separator, offset); offset += separator.length; - message.set(appIdBytes, offset); offset += appIdBytes.length; - message.set(timestampBytes, offset); offset += timestampBytes.length; - message.set(publicKey, offset); - - // Hash the message with Keccak-256 - const messageHash = keccak_256(message); - - try { - // Extract r, s, v from signature (last byte is recovery id) - const r = signature.slice(0, 32); - const s = signature.slice(32, 64); - const recovery = signature[64]; - - // Create signature in DER format for secp256k1 - const sigBytes = new Uint8Array(64); - sigBytes.set(r, 0); - sigBytes.set(s, 32); - - // Recover the public key from the signature - const recoveredPubKey = secp256k1.Signature.fromCompact(sigBytes) - .addRecoveryBit(recovery) - .recoverPublicKey(messageHash); - - // Return compressed public key with 0x prefix - const compressedBytes = recoveredPubKey.toRawBytes(true); - return '0x' + Array.from(compressedBytes, b => b.toString(16).padStart(2, '0')).join(''); - } catch (error) { - console.error('signature verification failed:', error); - return null; - } -} - -/** - * @deprecated Use verifyEnvEncryptPublicKey with timestamp parameter instead. - * This function is kept for backward compatibility but does not protect against replay attacks. - */ -export async function verifyEnvEncryptPublicKeyLegacy( - publicKey: Uint8Array, - signature: Uint8Array, - appId: string -): Promise { - if (signature.length !== 65) { - return null; - } - - // Create the message to verify - const prefix = new TextEncoder().encode("dstack-env-encrypt-pubkey"); - - // Remove 0x prefix if present - let cleanAppId = appId; - if (appId.startsWith("0x")) { - cleanAppId = appId.slice(2); - } - - const appIdBytes = hexToBytes(cleanAppId); - const separator = new TextEncoder().encode(":"); - - // Construct message: prefix + ":" + app_id + public_key - const message = new Uint8Array(prefix.length + separator.length + appIdBytes.length + publicKey.length); - message.set(prefix, 0); - message.set(separator, prefix.length); - message.set(appIdBytes, prefix.length + separator.length); - message.set(publicKey, prefix.length + separator.length + appIdBytes.length); - - // Hash the message with Keccak-256 - const messageHash = keccak_256(message); - - try { - // Extract r, s, v from signature (last byte is recovery id) - const r = signature.slice(0, 32); - const s = signature.slice(32, 64); - const recovery = signature[64]; - - // Create signature in DER format for secp256k1 - const sigBytes = new Uint8Array(64); - sigBytes.set(r, 0); - sigBytes.set(s, 32); - - // Recover the public key from the signature - const recoveredPubKey = secp256k1.Signature.fromCompact(sigBytes) - .addRecoveryBit(recovery) - .recoverPublicKey(messageHash); - - // Return compressed public key with 0x prefix - const compressedBytes = recoveredPubKey.toRawBytes(true); - return '0x' + Array.from(compressedBytes, b => b.toString(16).padStart(2, '0')).join(''); - } catch (error) { - console.error('signature verification failed:', error); - return null; - } -} diff --git a/sdk/js/src/verify-env-encrypt-public-key.ts b/sdk/js/src/verify-env-encrypt-public-key.ts index 556fb6259..7284af7e6 100644 --- a/sdk/js/src/verify-env-encrypt-public-key.ts +++ b/sdk/js/src/verify-env-encrypt-public-key.ts @@ -2,170 +2,122 @@ // // SPDX-License-Identifier: Apache-2.0 -import { keccak_256 } from "@noble/hashes/sha3"; -import { secp256k1 } from "@noble/curves/secp256k1"; +import { keccak_256 } from "@noble/hashes/sha3" +import { secp256k1 } from "@noble/curves/secp256k1" -/** Default maximum age for timestamp verification (5 minutes) */ -const DEFAULT_MAX_AGE_SECONDS = 300; +const DEFAULT_MAX_AGE_SECONDS = 300 -/** - * Options for verifying env encrypt public key - */ export interface VerifyOptions { - /** - * Maximum age of the response in seconds. - * If the timestamp is older than this, verification fails. - * Default: 300 (5 minutes) - */ - maxAgeSeconds?: number; + /** Maximum age of the signed response in seconds. Default: 300 (5 minutes). */ + maxAgeSeconds?: number } -/** - * Verify the signature of a public key with timestamp validation. - * - * @param publicKey - The public key bytes to verify (32 bytes) - * @param signature - The signature bytes (65 bytes) - * @param appId - The application ID - * @param timestamp - Unix timestamp in seconds when the response was generated - * @param options - Optional verification options - * @returns The compressed public key if valid, null otherwise - * - * @example - * ```typescript - * const publicKey = new Uint8Array(Buffer.from('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a', 'hex')); - * const signature = new Uint8Array(Buffer.from('...', 'hex')); - * const appId = '00'.repeat(20); - * const timestamp = 1700000000n; - * const compressedPubkey = verifyEnvEncryptPublicKey(publicKey, signature, appId, timestamp); - * ``` - */ -export function verifyEnvEncryptPublicKey( - publicKey: Uint8Array, - signature: Uint8Array, - appId: string, - timestamp: bigint | number, - options?: VerifyOptions -): string | null { - if (signature.length !== 65) { - return null; +function bigintToBeBytes(value: bigint, length: number): Uint8Array { + const bytes = new Uint8Array(length) + for (let i = length - 1; i >= 0; i--) { + bytes[i] = Number(value & 0xffn) + value >>= 8n } + return bytes +} - // Convert timestamp to bigint for consistent handling - const ts = typeof timestamp === 'bigint' ? timestamp : BigInt(timestamp); - - // Validate timestamp freshness - const maxAge = options?.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS; - const now = BigInt(Math.floor(Date.now() / 1000)); - const age = now - ts; - - if (age < 0n) { - // Timestamp is in the future - allow small clock skew (60 seconds) - if (age < -60n) { - console.error('timestamp is too far in the future'); - return null; - } - } else if (age > BigInt(maxAge)) { - console.error(`timestamp is too old: ${age}s > ${maxAge}s`); - return null; +function hexToBytes(hex: string): Uint8Array { + if (hex.startsWith("0x")) hex = hex.slice(2) + const bytes = new Uint8Array(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.substr(i, 2), 16) } + return bytes +} - // Create the message to verify - const prefix = Buffer.from("dstack-env-encrypt-pubkey", "utf8"); +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("") +} - // Remove 0x prefix if present - let cleanAppId = appId; - if (appId.startsWith("0x")) { - cleanAppId = appId.slice(2); +function concat(...parts: Uint8Array[]): Uint8Array { + const total = parts.reduce((n, p) => n + p.length, 0) + const out = new Uint8Array(total) + let offset = 0 + for (const part of parts) { + out.set(part, offset) + offset += part.length } + return out +} - const appIdBytes = Buffer.from(cleanAppId, "hex"); - const separator = Buffer.from(":", "utf8"); - - // Convert timestamp to big-endian bytes (8 bytes) - const timestampBytes = Buffer.alloc(8); - timestampBytes.writeBigUInt64BE(ts); - - // Construct message: prefix + ":" + app_id + timestamp_be_bytes + public_key - const message = Buffer.concat([prefix, separator, appIdBytes, timestampBytes, Buffer.from(publicKey)]); - - // Hash the message with Keccak-256 - const messageHash = keccak_256(message); - +function recoverSigner( + messageHash: Uint8Array, + signature: Uint8Array, +): string | null { try { - // Extract r, s, v from signature (last byte is recovery id) - const r = signature.slice(0, 32); - const s = signature.slice(32, 64); - const recovery = signature[64]; - - // Create signature in DER format for secp256k1 - const sigBytes = new Uint8Array(64); - sigBytes.set(r, 0); - sigBytes.set(s, 32); - - // Recover the public key from the signature + const sigBytes = signature.slice(0, 64) + const recovery = signature[64] const recoveredPubKey = secp256k1.Signature.fromCompact(sigBytes) .addRecoveryBit(recovery) - .recoverPublicKey(messageHash); - - // Return compressed public key with 0x prefix - return '0x' + Buffer.from(recoveredPubKey.toRawBytes(true)).toString('hex'); + .recoverPublicKey(messageHash) + return "0x" + bytesToHex(recoveredPubKey.toRawBytes(true)) } catch (error) { - console.error('signature verification failed:', error); - return null; + console.error("signature verification failed:", error) + return null } } /** - * @deprecated Use verifyEnvEncryptPublicKey with timestamp parameter instead. - * This function is kept for backward compatibility but does not protect against replay attacks. + * Verify a timestamp-protected KMS env-encrypt public key signature. + * + * Returns the signer's compressed secp256k1 public key on success, or `null` + * on failure (bad length, expired timestamp, invalid signature). */ -export function verifyEnvEncryptPublicKeyLegacy( +export function verifyEnvEncryptPublicKey( publicKey: Uint8Array, signature: Uint8Array, - appId: string + appId: string, + timestamp: bigint | number, + options?: VerifyOptions, ): string | null { - if (signature.length !== 65) { - return null; + if (signature.length !== 65) return null + + const ts = typeof timestamp === "bigint" ? timestamp : BigInt(timestamp) + const maxAge = options?.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS + const now = BigInt(Math.floor(Date.now() / 1000)) + const age = now - ts + if (age < -60n) { + console.error("timestamp is too far in the future") + return null } - - // Create the message to verify - const prefix = Buffer.from("dstack-env-encrypt-pubkey", "utf8"); - - // Remove 0x prefix if present - let cleanAppId = appId; - if (appId.startsWith("0x")) { - cleanAppId = appId.slice(2); + if (age > BigInt(maxAge)) { + console.error(`timestamp is too old: ${age}s > ${maxAge}s`) + return null } - const appIdBytes = Buffer.from(cleanAppId, "hex"); - const separator = Buffer.from(":", "utf8"); - - // Construct message: prefix + ":" + app_id + public_key - const message = Buffer.concat([prefix, separator, appIdBytes, Buffer.from(publicKey)]); - - // Hash the message with Keccak-256 - const messageHash = keccak_256(message); - - try { - // Extract r, s, v from signature (last byte is recovery id) - const r = signature.slice(0, 32); - const s = signature.slice(32, 64); - const recovery = signature[64]; - - // Create signature in DER format for secp256k1 - const sigBytes = new Uint8Array(64); - sigBytes.set(r, 0); - sigBytes.set(s, 32); + const prefix = new TextEncoder().encode("dstack-env-encrypt-pubkey") + const separator = new TextEncoder().encode(":") + const appIdBytes = hexToBytes(appId) + const timestampBytes = bigintToBeBytes(ts, 8) + const message = concat( + prefix, + separator, + appIdBytes, + timestampBytes, + publicKey, + ) + return recoverSigner(keccak_256(message), signature) +} - // Recover the public key from the signature - const recoveredPubKey = secp256k1.Signature.fromCompact(sigBytes) - .addRecoveryBit(recovery) - .recoverPublicKey(messageHash); +/** + * @deprecated Use {@link verifyEnvEncryptPublicKey} with timestamp. Legacy + * signatures do not protect against replay attacks. + */ +export function verifyEnvEncryptPublicKeyLegacy( + publicKey: Uint8Array, + signature: Uint8Array, + appId: string, +): string | null { + if (signature.length !== 65) return null - // Return compressed public key with 0x prefix - return '0x' + Buffer.from(recoveredPubKey.toRawBytes(true)).toString('hex'); - } catch (error) { - console.error('signature verification failed:', error); - return null; - } + const prefix = new TextEncoder().encode("dstack-env-encrypt-pubkey") + const separator = new TextEncoder().encode(":") + const appIdBytes = hexToBytes(appId) + const message = concat(prefix, separator, appIdBytes, publicKey) + return recoverSigner(keccak_256(message), signature) } diff --git a/sdk/js/src/viem.ts b/sdk/js/src/viem.ts index b0b4652f5..208fcc786 100644 --- a/sdk/js/src/viem.ts +++ b/sdk/js/src/viem.ts @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: Apache-2.0 -import crypto from 'crypto' +import { sha256 } from '@noble/hashes/sha256' +import { bytesToHex } from '@noble/hashes/utils' import { type GetKeyResponse, type GetTlsKeyResponse } from './index' import { privateKeyToAccount } from 'viem/accounts' @@ -29,13 +30,8 @@ export function toViemAccountSecure(keyResponse: GetKeyResponse | GetTlsKeyRespo // Keep legacy behavior for GetTlsKeyResponse, but with warning. if (keyResponse.__name__ === 'GetTlsKeyResponse') { console.warn('toViemAccountSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.') - try { - // Get supported hash algorithm by `openssl list -digest-algorithms`, but it's not guaranteed to be supported by node.js - const hex = crypto.createHash('sha256').update(keyResponse.asUint8Array()).digest('hex') - return privateKeyToAccount(`0x${hex}`) - } catch (err) { - throw new Error('toViemAccountSecure: missing sha256 support, please upgrade your openssl and node.js') - } + const hex = bytesToHex(sha256(keyResponse.asUint8Array())) + return privateKeyToAccount(`0x${hex}`) } const hex = Array.from(keyResponse.key).map(b => b.toString(16).padStart(2, '0')).join('') return privateKeyToAccount(`0x${hex}`) diff --git a/sdk/js/tsconfig.browser.json b/sdk/js/tsconfig.browser.json deleted file mode 100644 index cd759b2da..000000000 --- a/sdk/js/tsconfig.browser.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "es2020", - "module": "es2015", - "lib": ["es2018", "dom"], - "outDir": "./dist/browser", - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": [ - "src/encrypt-env-vars.browser.ts", - "src/get-compose-hash.browser.ts", - "src/verify-env-encrypt-public-key.browser.ts" - ], - "exclude": ["node_modules", "**/*.test.ts"] -} diff --git a/sdk/js/tsconfig.node.json b/sdk/js/tsconfig.node.json deleted file mode 100644 index a8004eebf..000000000 --- a/sdk/js/tsconfig.node.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "lib": ["es2018"], - "outDir": "./dist/node", - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.test.ts", "src/*.browser.ts"] -} diff --git a/sdk/js/tsup.config.ts b/sdk/js/tsup.config.ts new file mode 100644 index 000000000..0aafa6913 --- /dev/null +++ b/sdk/js/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "tsup" + +export default defineConfig({ + entry: [ + "src/index.ts", + "src/viem.ts", + "src/solana.ts", + "src/encrypt-env-vars.ts", + "src/get-compose-hash.ts", + "src/verify-env-encrypt-public-key.ts", + ], + format: ["cjs", "esm"], + dts: true, + clean: true, + sourcemap: false, + splitting: false, + treeshake: true, + target: "es2020", +})