@opendatalabs/vana-sdk 3.22.0 → 3.23.0-pr.211.341f55d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/crypto/envelope/job.cjs +355 -0
  2. package/dist/crypto/envelope/job.cjs.map +1 -0
  3. package/dist/crypto/envelope/job.d.ts +123 -0
  4. package/dist/crypto/envelope/job.js +333 -0
  5. package/dist/crypto/envelope/job.js.map +1 -0
  6. package/dist/direct/access-request-client.cjs +4 -0
  7. package/dist/direct/access-request-client.cjs.map +1 -1
  8. package/dist/direct/access-request-client.js +4 -0
  9. package/dist/direct/access-request-client.js.map +1 -1
  10. package/dist/direct/connect-flow.cjs +43 -1
  11. package/dist/direct/connect-flow.cjs.map +1 -1
  12. package/dist/direct/connect-flow.d.ts +12 -0
  13. package/dist/direct/connect-flow.js +43 -1
  14. package/dist/direct/connect-flow.js.map +1 -1
  15. package/dist/direct/types.cjs.map +1 -1
  16. package/dist/direct/types.d.ts +7 -0
  17. package/dist/direct/types.js.map +1 -1
  18. package/dist/direct/use-direct-vana-connect.cjs +2 -1
  19. package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
  20. package/dist/direct/use-direct-vana-connect.d.ts +3 -1
  21. package/dist/direct/use-direct-vana-connect.js +2 -1
  22. package/dist/direct/use-direct-vana-connect.js.map +1 -1
  23. package/dist/errors.cjs +84 -0
  24. package/dist/errors.cjs.map +1 -1
  25. package/dist/errors.d.ts +133 -0
  26. package/dist/errors.js +73 -0
  27. package/dist/errors.js.map +1 -1
  28. package/dist/index.browser.d.ts +3 -0
  29. package/dist/index.browser.js +631 -24
  30. package/dist/index.browser.js.map +4 -4
  31. package/dist/index.node.cjs +1259 -111
  32. package/dist/index.node.cjs.map +4 -4
  33. package/dist/index.node.d.ts +4 -0
  34. package/dist/index.node.js +1223 -101
  35. package/dist/index.node.js.map +4 -4
  36. package/dist/protocol/identity.cjs +219 -0
  37. package/dist/protocol/identity.cjs.map +1 -0
  38. package/dist/protocol/identity.d.ts +193 -0
  39. package/dist/protocol/identity.js +183 -0
  40. package/dist/protocol/identity.js.map +1 -0
  41. package/dist/protocol/identity.test.d.ts +1 -0
  42. package/dist/protocol/identity.vector.test.d.ts +1 -0
  43. package/dist/protocol/jobs-client.cjs +540 -0
  44. package/dist/protocol/jobs-client.cjs.map +1 -0
  45. package/dist/protocol/jobs-client.d.ts +169 -0
  46. package/dist/protocol/jobs-client.js +547 -0
  47. package/dist/protocol/jobs-client.js.map +1 -0
  48. package/dist/protocol/jobs-client.test.d.ts +1 -0
  49. package/dist/protocol/jobs.cjs +64 -0
  50. package/dist/protocol/jobs.cjs.map +1 -0
  51. package/dist/protocol/jobs.d.ts +193 -0
  52. package/dist/protocol/jobs.js +31 -0
  53. package/dist/protocol/jobs.js.map +1 -0
  54. package/dist/protocol/jobs.test.d.ts +1 -0
  55. package/dist/react.cjs.map +1 -1
  56. package/dist/react.d.ts +1 -1
  57. package/dist/react.js.map +1 -1
  58. package/package.json +7 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/identity.ts"],"sourcesContent":["/**\n * Enclave identity and consent primitives for Personal Servers.\n *\n * Owner/Web -> Gateway -> Agent(CVM): derive wallet and quote\n * Web verifies evidence; Account authenticates and encrypts the EIP-191 master\n * signature to the enclave key; Gateway relays it blind; Agent decrypts/seals.\n *\n * @category Protocol\n */\n\nimport * as secp256k1 from \"@noble/secp256k1\";\nimport {\n concat,\n encodePacked,\n fromHex,\n getAddress,\n isAddressEqual,\n keccak256,\n recoverPublicKey,\n toBytes,\n toHex,\n type Address,\n type Hex,\n} from \"viem\";\nimport { publicKeyToAddress } from \"viem/accounts\";\nimport { serializeECIES, type ECIESProvider } from \"../crypto/ecies/interface\";\nimport { deriveMasterKey, recoverServerOwner } from \"../crypto/keys/derive\";\nimport type { ServerRegistrationMessage } from \"./eip712\";\n\nexport const ENCLAVE_IDENTITY_EVIDENCE_VERSION = 1;\nexport const USER_PS_ID_DOMAIN = \"vana.ps-enclave.v1\";\nexport const ENCLAVE_WALLET_PURPOSE = \"vana.ps-enclave.wallet.v1\";\nexport const MASTER_SIGNATURE_DELIVERY_VERSION = \"vana.ps-enclave.delivery.v1\";\n/** Agent rejects deliveries when `|now - issuedAt|` exceeds this value. */\nexport const MASTER_SIGNATURE_DELIVERY_MAX_AGE_SECONDS = 600;\nexport const SEALED_ENVELOPE_VERSION = 1;\n\nconst VANA_MAINNET_CHAIN_ID = 1480;\nconst MOKSHA_CHAIN_ID = 14800;\nconst KMS_ISSUED_PREFIX = \"dstack-kms-issued\";\nconst PREIMAGE_SEPARATOR = \":\";\nconst UNCOMPRESSED_PUBLIC_KEY_BYTES = 65;\nconst UNCOMPRESSED_PUBLIC_KEY_PREFIX = \"04\";\nconst EMPTY_HEX = \"0x\";\n\n/** `keccak256(encodePacked([string,uint256,address], [USER_PS_ID_DOMAIN,chainId,owner]))`. */\nexport type UserPsId = Hex;\n\n/** Attested identity evidence returned by the enclave agent. */\nexport interface EnclaveIdentityEvidence {\n v: typeof ENCLAVE_IDENTITY_EVIDENCE_VERSION;\n userPsId: UserPsId;\n chainId: number;\n ownerAddress: Address;\n /** Path suffix `users/{id}/wallet/ethereum/secp256k1/v{epoch}`. */\n epoch: number;\n address: Address;\n /** 65-byte uncompressed `0x04..`; `publicKeyToAddress(publicKey) == address`. */\n publicKey: Hex;\n /** 20-byte dstack `app_id`. */\n appId: Hex;\n /** 32-byte compose hash. */\n composeHash: Hex;\n /** Omitted when the OS does not expose its image hash. */\n osImageHash?: Hex;\n /** Must equal `ENCLAVE_WALLET_PURPOSE`. */\n purpose: string;\n /** `[appRoot over link 0, kmsRoot over link 1]`; see `appRootPreimage` and `kmsIssuedPreimage`. */\n signatureChain: [Hex, Hex];\n /** Raw TDX quote with report_data `keccak256(userPsId || address)`; not parsed. */\n quote: Hex;\n eventLog?: string;\n /** `keccak256` of the uncompressed KMS root public key. */\n kmsRootFingerprint: Hex;\n}\n\n/** Identity values the caller expects the evidence to bind. */\nexport interface ExpectedIdentity {\n ownerAddress: Address;\n chainId: number;\n epoch: number;\n}\n\n/** Request for an owner-scoped enclave identity. */\nexport interface IdentityRequest {\n ownerAddress: Address;\n chainId: number;\n}\n\n/** Lifecycle state of an enclave identity. */\nexport type IdentityState = \"prepared\" | \"registered\" | \"sealed\" | \"retired\";\n\n/** Gateway response containing enclave identity state and registration data. */\nexport interface IdentityResponse {\n identity: EnclaveIdentityEvidence;\n state: IdentityState;\n created: boolean;\n /** Exact `ServerRegistration.serverUrl` the owner signs. */\n serverUrl: string;\n serverId?: Hex;\n serverStatus?:\n | \"pending\"\n | \"submitting\"\n | \"confirmed\"\n | \"finalized\"\n | \"failed\";\n sealed: boolean;\n}\n\n/** Versioned server-registration request for an enclave identity. */\nexport type IdentityRegistrationRequest =\n | { version: \"v2\"; message: ServerRegistrationMessage }\n | {\n version: \"v3\";\n message: ServerRegistrationMessage & { nonce: string; deadline: string };\n };\n\n/** Accepted identity-registration response. */\nexport interface IdentityRegistrationResponse {\n serverId: Hex;\n state: \"registered\";\n serverStatus: \"pending\";\n}\n\n/** Inner plaintext of the ECIES box. */\nexport interface MasterSignatureDelivery {\n v: typeof MASTER_SIGNATURE_DELIVERY_VERSION;\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n ownerAddress: Address;\n /** 65-byte EIP-191 signature over `MASTER_KEY_MESSAGE`. */\n masterSignature: Hex;\n /** Unix seconds; see `MASTER_SIGNATURE_DELIVERY_MAX_AGE_SECONDS`. */\n issuedAt: number;\n}\n\n/** Enclave-bound ciphertext submitted for sealing. */\nexport interface SealedSecretSubmission {\n userPsId: UserPsId;\n epoch: number;\n enclaveAddress: Address;\n /** `iv(16) || ephemPub(65) || ct || mac(32)` to `evidence.publicKey`. */\n ciphertext: Hex;\n}\n\n/** Confirmation that the enclave sealed a submitted secret. */\nexport interface SealedSecretResponse {\n sealed: true;\n /** `sha256(ciphertext)`. */\n secretHash: Hex;\n sealedAt: string;\n}\n\n/** AES-GCM fields encoded as base64 strings. */\nexport interface AesGcmBox {\n /** Base64 initialization vector. */\n iv: string;\n /** Base64 ciphertext. */\n ciphertext: string;\n /** Base64 authentication tag. */\n tag: string;\n}\n\n/** Persisted enclave envelope; the Gateway treats it as opaque text. */\nexport interface SealedEnvelope extends AesGcmBox {\n v: typeof SEALED_ENVELOPE_VERSION;\n wrappedContentKey: AesGcmBox;\n}\n\n/** Fleet-pinned KMS root and allowed dstack application IDs. */\nexport interface EnclaveTrustAnchors {\n kmsRootPubkey: Hex;\n appIds: readonly Hex[];\n}\n\nfunction emptyAnchor(): Readonly<EnclaveTrustAnchors> {\n return Object.freeze({\n kmsRootPubkey: EMPTY_HEX,\n appIds: Object.freeze([] as Hex[]),\n });\n}\n\n/** Fleet-provisioned trust anchors keyed by Vana chain ID. */\nexport const ENCLAVE_TRUST_ANCHORS: Readonly<\n Record<number, Readonly<EnclaveTrustAnchors>>\n> = Object.freeze({\n // filled at fleet provisioning; verify fails closed while empty\n [VANA_MAINNET_CHAIN_ID]: emptyAnchor(),\n // filled at fleet provisioning; verify fails closed while empty\n [MOKSHA_CHAIN_ID]: emptyAnchor(),\n});\n\n/**\n * Derives the deterministic Personal Server ID used by enclave paths.\n *\n * @param chainId - Vana chain ID.\n * @param ownerAddress - Checksummed owner address.\n * @returns The path-compatible keccak256 digest.\n */\nexport function userPsId(chainId: number, ownerAddress: Address): UserPsId {\n const packed = encodePacked(\n [\"string\", \"uint256\", \"address\"],\n [USER_PS_ID_DOMAIN, BigInt(chainId), getAddress(ownerAddress)],\n );\n\n return keccak256(packed);\n}\n\nfunction compressPublicKey(publicKey: Hex): Uint8Array {\n return secp256k1.ProjectivePoint.fromHex(\n fromHex(publicKey, \"bytes\"),\n ).toRawBytes(true);\n}\n\nfunction sameKey(a: Hex, b: Hex): boolean {\n return toHex(compressPublicKey(a)) === toHex(compressPublicKey(b));\n}\n\nfunction assertUncompressedKey(publicKey: Hex): void {\n const hex = publicKey.slice(2);\n if (\n hex.length !== UNCOMPRESSED_PUBLIC_KEY_BYTES * 2 ||\n !hex.startsWith(UNCOMPRESSED_PUBLIC_KEY_PREFIX) ||\n !/^[0-9a-fA-F]+$/.test(hex)\n ) {\n throw new Error(\"Public key must be a 65-byte uncompressed secp256k1 key\");\n }\n}\n\n/**\n * Builds keccak256(utf8(purpose || \":\" || lowercase hex(compressed pubkey))).\n *\n * Matches `dstack/guest-agent/src/rpc_service.rs:612-628`.\n */\nexport function appRootPreimage(purpose: string, publicKey: Hex): Hex {\n const keyHex = toHex(compressPublicKey(publicKey)).slice(2);\n return keccak256(toBytes(`${purpose}${PREIMAGE_SEPARATOR}${keyHex}`));\n}\n\n/**\n * Hashes the KMS prefix, raw app ID bytes, and compressed app-root public key.\n *\n * Matches `dstack/kms/src/crypto.rs:23-40`; there is no separator between the\n * raw 20-byte app ID and the compressed key.\n */\nexport function kmsIssuedPreimage(appId: Hex, appRootPublicKey: Hex): Hex {\n const prefix = concat([\n toBytes(`${KMS_ISSUED_PREFIX}${PREIMAGE_SEPARATOR}`),\n fromHex(appId, \"bytes\"),\n ]);\n const compressed = compressPublicKey(appRootPublicKey);\n\n return keccak256(concat([prefix, compressed]));\n}\n\nasync function recoverChainKey(\n hash: Hex,\n signature: Hex,\n link: number,\n): Promise<Hex> {\n try {\n return (await recoverPublicKey({ hash, signature })).toLowerCase() as Hex;\n } catch {\n throw new Error(`Invalid enclave signature chain link ${link}`);\n }\n}\n\n/**\n * Verifies the enclave wallet binding against fleet-provisioned trust anchors.\n *\n * @param evidence - Identity evidence returned by the enclave agent.\n * @param anchors - Pinned KMS root and allowed dstack app IDs.\n * @param expected - Owner, chain, and epoch requested by the caller.\n *\n * @remarks\n * The dstack chain signs only `purpose || publicKey` and `appId || appRootPub`;\n * owner, chain and epoch are bound to the key by the TDX quote report_data\n * (`keccak256(userPsId || address)`), which v1 stores but does not verify\n * (DCAP is step 4). Until then the agent's derivation check is the backstop.\n * @throws When any identity binding or trust check fails.\n */\nexport async function verifyEnclaveIdentityEvidence(\n evidence: EnclaveIdentityEvidence,\n anchors: EnclaveTrustAnchors,\n expected: ExpectedIdentity,\n): Promise<void> {\n if (evidence.v !== ENCLAVE_IDENTITY_EVIDENCE_VERSION) {\n throw new Error(\"Unsupported enclave identity evidence version\");\n }\n\n if (!Number.isInteger(evidence.epoch) || evidence.epoch < 1) {\n throw new Error(\"Invalid enclave identity epoch\");\n }\n\n if (evidence.chainId !== expected.chainId) {\n throw new Error(\n \"Enclave identity chain ID does not match expected chain ID\",\n );\n }\n\n if (!isAddressEqual(evidence.ownerAddress, expected.ownerAddress)) {\n throw new Error(\"Enclave identity owner does not match expected owner\");\n }\n\n if (evidence.epoch !== expected.epoch) {\n throw new Error(\"Enclave identity epoch does not match expected epoch\");\n }\n\n const expectedUserPsId = userPsId(expected.chainId, expected.ownerAddress);\n if (evidence.userPsId.toLowerCase() !== expectedUserPsId.toLowerCase()) {\n throw new Error(\"Enclave userPsId does not match expected identity\");\n }\n\n if (evidence.purpose !== ENCLAVE_WALLET_PURPOSE) {\n throw new Error(\"Unexpected enclave wallet purpose\");\n }\n\n if (anchors.kmsRootPubkey === EMPTY_HEX) {\n throw new Error(\"KMS root trust anchor is not provisioned\");\n }\n\n assertUncompressedKey(evidence.publicKey);\n const derivedAddress = publicKeyToAddress(evidence.publicKey);\n\n if (getAddress(derivedAddress) !== getAddress(evidence.address)) {\n throw new Error(\"Enclave public key does not match its address\");\n }\n\n const appRootPublicKey = await recoverChainKey(\n appRootPreimage(evidence.purpose, evidence.publicKey),\n evidence.signatureChain[0],\n 0,\n );\n const kmsRootPublicKey = await recoverChainKey(\n kmsIssuedPreimage(evidence.appId, appRootPublicKey),\n evidence.signatureChain[1],\n 1,\n );\n\n let matchesAnchor: boolean;\n try {\n matchesAnchor = sameKey(kmsRootPublicKey, anchors.kmsRootPubkey);\n } catch {\n throw new Error(\"KMS root trust anchor is malformed\");\n }\n\n if (!matchesAnchor) {\n throw new Error(\"KMS root public key does not match the trust anchor\");\n }\n\n if (\n keccak256(kmsRootPublicKey) !== evidence.kmsRootFingerprint.toLowerCase()\n ) {\n throw new Error(\"KMS root fingerprint does not match the evidence\");\n }\n\n const appId = evidence.appId.toLowerCase();\n if (\n !anchors.appIds.some((allowedAppId) => allowedAppId.toLowerCase() === appId)\n ) {\n throw new Error(\"Enclave app ID is not trusted\");\n }\n}\n\n/**\n * Builds the authenticated master-signature plaintext delivered to an enclave.\n *\n * @param evidence - Verified enclave identity evidence.\n * @param masterSignature - EIP-191 signature over the master-key message.\n * @param now - Issuance time in Unix seconds.\n * @returns The authenticated plaintext delivery object.\n * @throws When the signature is malformed or does not belong to the owner.\n */\nexport async function buildMasterSignatureDelivery(\n evidence: EnclaveIdentityEvidence,\n masterSignature: Hex,\n now = Math.floor(Date.now() / 1000),\n): Promise<MasterSignatureDelivery> {\n // Validate signature length and hex encoding before recovery.\n deriveMasterKey(masterSignature);\n const signerAddress = await recoverServerOwner(masterSignature);\n\n if (!isAddressEqual(signerAddress, evidence.ownerAddress)) {\n throw new Error(\"Master signature signer does not match evidence owner\");\n }\n\n return {\n v: MASTER_SIGNATURE_DELIVERY_VERSION,\n userPsId: evidence.userPsId,\n epoch: evidence.epoch,\n enclaveAddress: evidence.address,\n ownerAddress: evidence.ownerAddress,\n masterSignature,\n issuedAt: now,\n };\n}\n\n/**\n * Encrypts a master-signature delivery in the SDK's ECIES wire format.\n *\n * @param delivery - Plaintext delivery object.\n * @param publicKey - Enclave 65-byte uncompressed public key.\n * @param ecies - Platform-specific ECIES provider.\n * @returns The 0x-prefixed serialized ciphertext.\n * @throws When the public key is malformed or belongs to another enclave.\n */\nexport async function encryptMasterSignatureDelivery(\n delivery: MasterSignatureDelivery,\n publicKey: Hex,\n ecies: ECIESProvider,\n): Promise<Hex> {\n assertUncompressedKey(publicKey);\n\n if (!isAddressEqual(publicKeyToAddress(publicKey), delivery.enclaveAddress)) {\n throw new Error(\n \"Public key does not belong to the delivery's enclave address\",\n );\n }\n\n const plaintext = toBytes(JSON.stringify(delivery));\n const encrypted = await ecies.encrypt(fromHex(publicKey, \"bytes\"), plaintext);\n\n return `0x${serializeECIES(encrypted)}`;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,540 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var jobs_client_exports = {};
20
+ __export(jobs_client_exports, {
21
+ createJobsClient: () => createJobsClient
22
+ });
23
+ module.exports = __toCommonJS(jobs_client_exports);
24
+ var import_node_crypto = require("node:crypto");
25
+ var import_sha2 = require("@noble/hashes/sha2");
26
+ var import_viem = require("viem");
27
+ var import_accounts = require("viem/accounts");
28
+ var import_web3_signed_builder = require("../auth/web3-signed-builder");
29
+ var import_node = require("../crypto/ecies/node");
30
+ var import_job = require("../crypto/envelope/job");
31
+ var import_errors = require("../errors");
32
+ var import_identity = require("./identity");
33
+ var import_jobs = require("./jobs");
34
+ var import_write_signer = require("./write-signer");
35
+ const IDENTITY_PATH = "/v1/identity";
36
+ const JOBS_PATH = "/v1/jobs";
37
+ const JOB_EXECUTE_PATH = "/v1/jobs/execute";
38
+ const AUTHORIZATION_HEADER = "Authorization";
39
+ const CONTENT_TYPE_HEADER = "Content-Type";
40
+ const JSON_CONTENT_TYPE = "application/json";
41
+ const GET_METHOD = "GET";
42
+ const POST_METHOD = "POST";
43
+ const RAW_READ_OPERATION = "raw_read";
44
+ const SEALED_IDENTITY_STATE = "sealed";
45
+ const HTTP_OK = 200;
46
+ const HTTP_ACCEPTED = 202;
47
+ const HTTP_UNAUTHORIZED = 401;
48
+ const HTTP_FORBIDDEN = 403;
49
+ const HTTP_NOT_FOUND = 404;
50
+ const HTTP_CONFLICT = 409;
51
+ const HTTP_PAYLOAD_TOO_LARGE = 413;
52
+ const MILLISECONDS_PER_SECOND = 1e3;
53
+ const DEFAULT_JOB_TIMEOUT_MS = 12e4;
54
+ const DEFAULT_JOB_POLL_MS = import_jobs.CLAIM_POLL_FLOOR_MS;
55
+ const TERMINAL_JOB_STATES = /* @__PURE__ */ new Set([
56
+ "completed",
57
+ "failed",
58
+ "expired",
59
+ "cancelled"
60
+ ]);
61
+ function isRecord(value) {
62
+ return value !== null && typeof value === "object" && !Array.isArray(value);
63
+ }
64
+ function normalizeGateway(gatewayUrl) {
65
+ try {
66
+ const url = new URL(gatewayUrl);
67
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") {
68
+ throw new import_errors.JobRejectedError(
69
+ "gatewayUrl must be an origin without a path, query, or fragment"
70
+ );
71
+ }
72
+ return {
73
+ baseUrl: url.origin,
74
+ audience: url.origin
75
+ };
76
+ } catch (error) {
77
+ if (error instanceof import_errors.JobRejectedError) throw error;
78
+ throw new import_errors.JobRejectedError(
79
+ "gatewayUrl must be an absolute URL",
80
+ void 0,
81
+ null,
82
+ {
83
+ cause: error instanceof Error ? error.message : String(error)
84
+ }
85
+ );
86
+ }
87
+ }
88
+ function resolveFetch(fetchFn) {
89
+ const resolved = fetchFn ?? globalThis.fetch;
90
+ if (resolved === void 0) {
91
+ throw new import_errors.JobRejectedError("No fetch implementation available");
92
+ }
93
+ return resolved;
94
+ }
95
+ function sourcePublicKey(source) {
96
+ if (isRecord(source) && typeof source["publicKey"] === "string") {
97
+ return source["publicKey"];
98
+ }
99
+ return void 0;
100
+ }
101
+ function resolveBuilder(options) {
102
+ if (options.builderPrivateKey && options.builderAccount) {
103
+ throw new import_errors.JobRejectedError(
104
+ "Provide builderPrivateKey or builderAccount, not both"
105
+ );
106
+ }
107
+ let source;
108
+ try {
109
+ source = options.builderPrivateKey ? (0, import_accounts.privateKeyToAccount)(options.builderPrivateKey) : options.builderAccount;
110
+ } catch (error) {
111
+ throw new import_errors.JobRejectedError("Invalid builder private key", void 0, null, {
112
+ cause: error instanceof Error ? error.message : String(error)
113
+ });
114
+ }
115
+ if (!source) {
116
+ throw new import_errors.JobRejectedError(
117
+ "builderPrivateKey or builderAccount is required"
118
+ );
119
+ }
120
+ let signer;
121
+ try {
122
+ signer = (0, import_write_signer.resolveWriteSigner)(source);
123
+ } catch (error) {
124
+ throw new import_errors.JobRejectedError("Invalid builder signer", void 0, null, {
125
+ cause: error instanceof Error ? error.message : String(error)
126
+ });
127
+ }
128
+ const publicKey = sourcePublicKey(source);
129
+ if (!signer.address || !publicKey) {
130
+ throw new import_errors.JobRejectedError(
131
+ "Builder signer must expose both address and publicKey"
132
+ );
133
+ }
134
+ return {
135
+ builder: {
136
+ address: signer.address,
137
+ publicKey,
138
+ signMessage: signer.signMessage
139
+ },
140
+ ...options.builderPrivateKey ? { privateKey: options.builderPrivateKey } : {}
141
+ };
142
+ }
143
+ function clampInteger(value, fallback, minimum, maximum, field) {
144
+ const resolved = value ?? fallback;
145
+ if (!Number.isFinite(resolved)) {
146
+ throw new import_errors.JobRejectedError(`${field} must be a finite number`);
147
+ }
148
+ return Math.min(maximum, Math.max(minimum, Math.trunc(resolved)));
149
+ }
150
+ async function responseBody(response) {
151
+ try {
152
+ return await response.json();
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function gatewayErrorBody(body, fallback) {
158
+ if (!isRecord(body)) return { code: null, message: fallback };
159
+ const code = typeof body["code"] === "string" ? body["code"] : null;
160
+ const message = typeof body["error"] === "string" ? body["error"] : typeof body["message"] === "string" ? body["message"] : fallback;
161
+ const reason = typeof body["reason"] === "string" ? body["reason"] : void 0;
162
+ const details = isRecord(body["details"]) ? body["details"] : void 0;
163
+ return {
164
+ code,
165
+ message,
166
+ ...reason ? { reason } : {},
167
+ ...details ? { details } : {}
168
+ };
169
+ }
170
+ async function rejectedResponse(response, fallback) {
171
+ const parsed = gatewayErrorBody(await responseBody(response), fallback);
172
+ const details = response.status === HTTP_UNAUTHORIZED && parsed.reason ? { ...parsed.details, reason: parsed.reason } : parsed.details;
173
+ const message = response.status === HTTP_UNAUTHORIZED && parsed.reason ? `${parsed.message}: ${parsed.reason}` : parsed.message;
174
+ if (response.status === HTTP_FORBIDDEN) {
175
+ if (parsed.code === "BUILDER_UNKNOWN") {
176
+ return new import_errors.BuilderUnknownError(parsed.message, details);
177
+ }
178
+ if (parsed.code === "GRANT_INVALID") {
179
+ return new import_errors.GrantInvalidError(parsed.message, details);
180
+ }
181
+ if (parsed.code === "OWNER_NOT_READY") {
182
+ return new import_errors.OwnerNotReadyError(parsed.message, details);
183
+ }
184
+ }
185
+ if (response.status === HTTP_NOT_FOUND) {
186
+ return new import_errors.JobNotFoundError(parsed.message, details);
187
+ }
188
+ if (response.status === HTTP_CONFLICT && parsed.code === "JOB_ID_TAKEN") {
189
+ return new import_errors.JobIdTakenError(parsed.message, details);
190
+ }
191
+ if (response.status === HTTP_PAYLOAD_TOO_LARGE) {
192
+ return new import_errors.JobRequestTooLargeError(parsed.message, parsed.code, details);
193
+ }
194
+ return new import_errors.JobRejectedError(message, response.status, parsed.code, details);
195
+ }
196
+ function requireStatus(value, status) {
197
+ if (!isRecord(value) || !isNonEmptyString(value["jobId"]) || typeof value["state"] !== "string" || !import_jobs.JOB_STATES.includes(value["state"]) || !isNonEmptyString(value["owner"]) || !isNonEmptyString(value["grantId"]) || !isNonEmptyString(value["scope"]) || !isNonEmptyString(value["operation"]) || !import_jobs.JOB_OPERATIONS.includes(value["operation"])) {
198
+ throw new import_errors.JobRejectedError(
199
+ "Gateway response did not contain a job status",
200
+ status
201
+ );
202
+ }
203
+ return value;
204
+ }
205
+ function isNonEmptyString(value) {
206
+ return typeof value === "string" && value.length > 0;
207
+ }
208
+ function hasMatchingIdentityKey(value) {
209
+ const address = value["address"];
210
+ const publicKey = value["publicKey"];
211
+ if (typeof address !== "string" || !(0, import_viem.isAddress)(address) || typeof publicKey !== "string" || !(0, import_viem.isHex)(publicKey)) {
212
+ return false;
213
+ }
214
+ try {
215
+ return (0, import_accounts.publicKeyToAddress)(publicKey).toLowerCase() === address.toLowerCase();
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ function hasExpectedIdentity(value, owner, chainId) {
221
+ const identityOwner = value["ownerAddress"];
222
+ const identityChainId = value["chainId"];
223
+ const identityUserPsId = value["userPsId"];
224
+ if (!hasMatchingIdentityKey(value) || typeof identityOwner !== "string" || !(0, import_viem.isAddress)(identityOwner) || typeof identityChainId !== "number" || typeof identityUserPsId !== "string" || !(0, import_viem.isHex)(identityUserPsId)) {
225
+ return false;
226
+ }
227
+ return (0, import_viem.isAddressEqual)(identityOwner, owner) && identityChainId === chainId && identityUserPsId.toLowerCase() === (0, import_identity.userPsId)(chainId, owner).toLowerCase();
228
+ }
229
+ function sleep(ms) {
230
+ return new Promise((resolve) => setTimeout(resolve, ms));
231
+ }
232
+ function requireFiniteDuration(value, field) {
233
+ if (!Number.isFinite(value)) {
234
+ throw new import_errors.JobRejectedError(`${field} must be a finite number`);
235
+ }
236
+ return Math.max(0, value);
237
+ }
238
+ function createJobsClient(options) {
239
+ const { baseUrl, audience } = normalizeGateway(options.gatewayUrl);
240
+ const fetchFn = resolveFetch(options.fetch);
241
+ const { builder, privateKey } = resolveBuilder(options);
242
+ const ecies = options.ecies ?? new import_node.NodeECIESUint8Provider();
243
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
244
+ async function request(url, init, description) {
245
+ try {
246
+ return await fetchFn(url, init);
247
+ } catch (error) {
248
+ throw new import_errors.JobTransportError(
249
+ `${description} failed: ${error instanceof Error ? error.message : String(error)}`,
250
+ error
251
+ );
252
+ }
253
+ }
254
+ async function getIdentity(owner) {
255
+ const query = new URLSearchParams({
256
+ owner,
257
+ chainId: String(options.chainId)
258
+ });
259
+ const response = await request(
260
+ `${baseUrl}${IDENTITY_PATH}?${query.toString()}`,
261
+ { method: GET_METHOD },
262
+ "Owner identity lookup"
263
+ );
264
+ if (response.status === HTTP_NOT_FOUND) {
265
+ const parsed = gatewayErrorBody(
266
+ await responseBody(response),
267
+ "Owner identity not found"
268
+ );
269
+ throw new import_errors.OwnerNotReadyError(parsed.message, parsed.details);
270
+ }
271
+ if (!response.ok) {
272
+ throw await rejectedResponse(response, "Owner identity lookup rejected");
273
+ }
274
+ const body = await responseBody(response);
275
+ if (!isRecord(body) || body["state"] !== SEALED_IDENTITY_STATE || !isRecord(body["identity"])) {
276
+ throw new import_errors.OwnerNotReadyError("Owner identity is not sealed", {
277
+ state: isRecord(body) ? body["state"] : void 0
278
+ });
279
+ }
280
+ if (!hasExpectedIdentity(body["identity"], owner, options.chainId)) {
281
+ throw new import_errors.JobRejectedError(
282
+ "Gateway returned a malformed or mismatched owner identity",
283
+ response.status
284
+ );
285
+ }
286
+ return body["identity"];
287
+ }
288
+ const client = {
289
+ async submitRawRead(params) {
290
+ const identity = await getIdentity(params.owner);
291
+ const deadlineSeconds = clampInteger(
292
+ params.deadlineSeconds,
293
+ import_jobs.DEFAULT_JOB_DEADLINE_SECONDS,
294
+ 1,
295
+ import_jobs.MAX_JOB_DEADLINE_SECONDS,
296
+ "deadlineSeconds"
297
+ );
298
+ const wait = clampInteger(params.wait, 0, 0, import_jobs.MAX_WAIT_SECONDS, "wait");
299
+ const jobId = (0, import_node_crypto.randomUUID)();
300
+ const nowMs = now().getTime();
301
+ if (!Number.isFinite(nowMs)) {
302
+ throw new import_errors.JobRejectedError("now must return a valid Date");
303
+ }
304
+ const deadline = new Date(
305
+ nowMs + deadlineSeconds * MILLISECONDS_PER_SECOND
306
+ ).toISOString();
307
+ const requestBody = {
308
+ v: import_jobs.JOB_PROTOCOL_VERSION,
309
+ jobId,
310
+ owner: params.owner,
311
+ builder: builder.address,
312
+ builderPublicKey: builder.publicKey,
313
+ grantId: params.grantId,
314
+ scope: params.scope,
315
+ operation: RAW_READ_OPERATION,
316
+ pinnedVersion: params.pinnedVersion ?? null,
317
+ deadline
318
+ };
319
+ const auth = await (0, import_web3_signed_builder.buildWeb3SignedHeader)({
320
+ signMessage: builder.signMessage,
321
+ aud: audience,
322
+ method: POST_METHOD,
323
+ uri: JOB_EXECUTE_PATH,
324
+ bodyHash: (0, import_web3_signed_builder.computeBodyHash)((0, import_job.canonicalJobRequestBytes)(requestBody)),
325
+ nonce: (0, import_node_crypto.randomUUID)()
326
+ });
327
+ const requestCiphertext = await (0, import_job.sealJobRequest)(
328
+ { request: requestBody, auth },
329
+ identity.publicKey,
330
+ ecies
331
+ );
332
+ const submission = {
333
+ owner: requestBody.owner,
334
+ grantId: requestBody.grantId,
335
+ scope: requestBody.scope,
336
+ operation: requestBody.operation,
337
+ idempotencyKey: (0, import_node_crypto.randomUUID)(),
338
+ jobId,
339
+ deadline,
340
+ requestCiphertext
341
+ };
342
+ const bodyBytes = new TextEncoder().encode(JSON.stringify(submission));
343
+ const authorization = await (0, import_web3_signed_builder.buildWeb3SignedHeader)({
344
+ signMessage: builder.signMessage,
345
+ aud: audience,
346
+ method: POST_METHOD,
347
+ uri: JOBS_PATH,
348
+ body: bodyBytes,
349
+ nonce: (0, import_node_crypto.randomUUID)()
350
+ });
351
+ const response = await request(
352
+ `${baseUrl}${JOBS_PATH}?wait=${wait}`,
353
+ {
354
+ method: POST_METHOD,
355
+ headers: {
356
+ [AUTHORIZATION_HEADER]: authorization,
357
+ [CONTENT_TYPE_HEADER]: JSON_CONTENT_TYPE
358
+ },
359
+ body: bodyBytes
360
+ },
361
+ "Job submission"
362
+ );
363
+ if (!response.ok) {
364
+ throw await rejectedResponse(response, "Job submission rejected");
365
+ }
366
+ const body = await responseBody(response);
367
+ if (response.status === HTTP_OK && isRecord(body)) {
368
+ const job = requireStatus(body["job"], response.status);
369
+ if (job.jobId !== jobId) {
370
+ throw new import_errors.JobRejectedError(
371
+ "Gateway returned a job id that does not match the submission",
372
+ response.status
373
+ );
374
+ }
375
+ return { jobId, state: job.state, job };
376
+ }
377
+ if (response.status === HTTP_ACCEPTED && isRecord(body) && body["jobId"] === jobId && typeof body["state"] === "string" && import_jobs.JOB_STATES.includes(body["state"])) {
378
+ return { jobId, state: body["state"] };
379
+ }
380
+ throw new import_errors.JobRejectedError(
381
+ "Gateway returned an undocumented job submission response",
382
+ response.status
383
+ );
384
+ },
385
+ async getJob(jobId) {
386
+ const path = `${JOBS_PATH}/${encodeURIComponent(jobId)}`;
387
+ const authorization = await (0, import_web3_signed_builder.buildWeb3SignedHeader)({
388
+ signMessage: builder.signMessage,
389
+ aud: audience,
390
+ method: GET_METHOD,
391
+ uri: path,
392
+ nonce: (0, import_node_crypto.randomUUID)()
393
+ });
394
+ const response = await request(
395
+ `${baseUrl}${path}`,
396
+ {
397
+ method: GET_METHOD,
398
+ headers: { [AUTHORIZATION_HEADER]: authorization }
399
+ },
400
+ "Job status read"
401
+ );
402
+ if (!response.ok) {
403
+ throw await rejectedResponse(response, "Job status read rejected");
404
+ }
405
+ const body = await responseBody(response);
406
+ return requireStatus(
407
+ isRecord(body) ? body["job"] : void 0,
408
+ response.status
409
+ );
410
+ },
411
+ async waitForJob(jobId, waitOptions = {}) {
412
+ const timeoutMs = requireFiniteDuration(
413
+ waitOptions.timeoutMs ?? DEFAULT_JOB_TIMEOUT_MS,
414
+ "timeoutMs"
415
+ );
416
+ const pollMs = Math.max(
417
+ import_jobs.CLAIM_POLL_FLOOR_MS,
418
+ requireFiniteDuration(
419
+ waitOptions.pollMs ?? DEFAULT_JOB_POLL_MS,
420
+ "pollMs"
421
+ )
422
+ );
423
+ const startedAt = Date.now();
424
+ const callerDeadline = startedAt + timeoutMs;
425
+ let latest;
426
+ for (; ; ) {
427
+ if (Date.now() >= callerDeadline) {
428
+ throw new import_errors.JobTimeoutError(
429
+ `Timed out waiting for terminal job ${jobId}`,
430
+ {
431
+ jobId,
432
+ timeoutMs,
433
+ state: latest?.state
434
+ }
435
+ );
436
+ }
437
+ latest = await client.getJob(jobId);
438
+ if (TERMINAL_JOB_STATES.has(latest.state)) return latest;
439
+ const remaining = callerDeadline - Date.now();
440
+ if (remaining <= 0) {
441
+ throw new import_errors.JobTimeoutError(
442
+ `Timed out waiting for terminal job ${jobId}`,
443
+ {
444
+ jobId,
445
+ timeoutMs,
446
+ state: latest.state
447
+ }
448
+ );
449
+ }
450
+ await sleep(Math.min(pollMs, remaining));
451
+ }
452
+ },
453
+ async openResult(handle, openOptions) {
454
+ if (!privateKey) {
455
+ throw new import_errors.JobRejectedError(
456
+ "openResult requires createJobsClient({ builderPrivateKey })"
457
+ );
458
+ }
459
+ const response = await request(
460
+ handle.url,
461
+ { method: GET_METHOD },
462
+ "Job result fetch"
463
+ );
464
+ if (!response.ok) {
465
+ throw await rejectedResponse(response, "Job result fetch rejected");
466
+ }
467
+ let bytes;
468
+ try {
469
+ bytes = new Uint8Array(await response.arrayBuffer());
470
+ } catch (error) {
471
+ throw new import_errors.JobTransportError(
472
+ `Job result fetch failed: ${error instanceof Error ? error.message : String(error)}`,
473
+ error
474
+ );
475
+ }
476
+ const actualHash = (0, import_viem.bytesToHex)((0, import_sha2.sha256)(bytes));
477
+ if (actualHash.toLowerCase() !== handle.hash.toLowerCase()) {
478
+ throw new import_errors.JobResultIntegrityError(
479
+ "Fetched job result hash does not match its handle",
480
+ {
481
+ objectKey: handle.objectKey,
482
+ expectedHash: handle.hash,
483
+ actualHash
484
+ }
485
+ );
486
+ }
487
+ if (bytes.length !== handle.size) {
488
+ throw new import_errors.JobResultIntegrityError(
489
+ "Fetched job result size does not match its handle",
490
+ {
491
+ objectKey: handle.objectKey,
492
+ expectedSize: handle.size,
493
+ actualSize: bytes.length
494
+ }
495
+ );
496
+ }
497
+ return (0, import_job.openJobResult)(bytes, privateKey, ecies, openOptions.expect);
498
+ },
499
+ async readRaw(params) {
500
+ const submitted = await client.submitRawRead(params);
501
+ const job = submitted.job && TERMINAL_JOB_STATES.has(submitted.job.state) ? submitted.job : await client.waitForJob(submitted.jobId, {
502
+ timeoutMs: params.timeoutMs,
503
+ pollMs: params.pollMs
504
+ });
505
+ if (job.state !== "completed") {
506
+ throw new import_errors.JobRejectedError(
507
+ `Job ${job.jobId} ended in state ${job.state}${job.failureReason ? `: ${job.failureReason}` : ""}`,
508
+ void 0,
509
+ null,
510
+ {
511
+ jobId: job.jobId,
512
+ state: job.state,
513
+ ...job.failureReason ? { failureReason: job.failureReason } : {}
514
+ }
515
+ );
516
+ }
517
+ if (!job.result) {
518
+ throw new import_errors.JobRejectedError(
519
+ "Completed job status does not include a result handle",
520
+ void 0,
521
+ null,
522
+ { jobId: job.jobId, state: job.state }
523
+ );
524
+ }
525
+ return client.openResult(job.result, {
526
+ expect: {
527
+ jobId: submitted.jobId,
528
+ scope: params.scope,
529
+ ...params.pinnedVersion !== void 0 && params.pinnedVersion !== null ? { version: params.pinnedVersion } : {}
530
+ }
531
+ });
532
+ }
533
+ };
534
+ return client;
535
+ }
536
+ // Annotate the CommonJS export names for ESM import in node:
537
+ 0 && (module.exports = {
538
+ createJobsClient
539
+ });
540
+ //# sourceMappingURL=jobs-client.cjs.map