@singularity-layer/grid 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/index.d.mts +261 -103
- package/dist/index.d.ts +261 -103
- package/dist/index.js +218 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +216 -147
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
34
34
|
GridClient: () => GridClient,
|
|
35
|
+
PROCESSORS_BASE_URL: () => PROCESSORS_BASE_URL,
|
|
36
|
+
ProcessorsClient: () => ProcessorsClient,
|
|
35
37
|
SGLAPIError: () => SGLAPIError,
|
|
36
38
|
SGLAuthError: () => SGLAuthError,
|
|
37
39
|
SGLConnectionError: () => SGLConnectionError,
|
|
@@ -496,153 +498,6 @@ var GridClient = class {
|
|
|
496
498
|
}
|
|
497
499
|
if (!sawFinal) throw new SGLAPIError(502, "stream ended before final chunk (truncated)");
|
|
498
500
|
}
|
|
499
|
-
// -- Processor helpers ----------------------------------------------------
|
|
500
|
-
async requestWithWalletAuth(method, path, wallet, body) {
|
|
501
|
-
const url = `${this.baseUrl}${path}`;
|
|
502
|
-
const controller = new AbortController();
|
|
503
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
504
|
-
const reqHeaders = {
|
|
505
|
-
...this.headers,
|
|
506
|
-
"X-Auth-Address": wallet.address,
|
|
507
|
-
"X-Auth-Chain": wallet.chain ?? "solana",
|
|
508
|
-
"X-Auth-Signature": wallet.signature,
|
|
509
|
-
"X-Auth-Timestamp": wallet.timestamp,
|
|
510
|
-
"X-Auth-Nonce": wallet.nonce
|
|
511
|
-
};
|
|
512
|
-
let response;
|
|
513
|
-
try {
|
|
514
|
-
response = await fetch(url, {
|
|
515
|
-
method,
|
|
516
|
-
headers: reqHeaders,
|
|
517
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
518
|
-
signal: controller.signal
|
|
519
|
-
});
|
|
520
|
-
} catch (err) {
|
|
521
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
522
|
-
throw new SGLConnectionError(`Request to ${url} timed out`);
|
|
523
|
-
}
|
|
524
|
-
throw new SGLConnectionError(
|
|
525
|
-
`Could not connect to ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
526
|
-
);
|
|
527
|
-
} finally {
|
|
528
|
-
clearTimeout(timer);
|
|
529
|
-
}
|
|
530
|
-
if (!response.ok) {
|
|
531
|
-
let errorBody;
|
|
532
|
-
let message = response.statusText;
|
|
533
|
-
try {
|
|
534
|
-
errorBody = await response.json();
|
|
535
|
-
const err = errorBody?.error;
|
|
536
|
-
if (typeof err === "string") message = err;
|
|
537
|
-
else if (err && typeof err === "object" && "message" in err)
|
|
538
|
-
message = String(err.message);
|
|
539
|
-
} catch {
|
|
540
|
-
}
|
|
541
|
-
if (response.status === 401 || response.status === 403) {
|
|
542
|
-
throw new SGLAuthError(response.status, message, errorBody);
|
|
543
|
-
}
|
|
544
|
-
if (response.status === 404) {
|
|
545
|
-
throw new SGLNotFoundError(message, errorBody);
|
|
546
|
-
}
|
|
547
|
-
throw new SGLAPIError(response.status, message, errorBody);
|
|
548
|
-
}
|
|
549
|
-
if (response.status === 204) return {};
|
|
550
|
-
return await response.json();
|
|
551
|
-
}
|
|
552
|
-
async requestWithPayment(method, path, body, paymentHeader) {
|
|
553
|
-
const url = `${this.baseUrl}${path}`;
|
|
554
|
-
const controller = new AbortController();
|
|
555
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
556
|
-
const reqHeaders = { ...this.headers };
|
|
557
|
-
if (paymentHeader) {
|
|
558
|
-
reqHeaders["X-Payment"] = paymentHeader;
|
|
559
|
-
}
|
|
560
|
-
let response;
|
|
561
|
-
try {
|
|
562
|
-
response = await fetch(url, {
|
|
563
|
-
method,
|
|
564
|
-
headers: reqHeaders,
|
|
565
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
566
|
-
signal: controller.signal
|
|
567
|
-
});
|
|
568
|
-
} catch (err) {
|
|
569
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
570
|
-
throw new SGLConnectionError(`Request to ${url} timed out`);
|
|
571
|
-
}
|
|
572
|
-
throw new SGLConnectionError(
|
|
573
|
-
`Could not connect to ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
574
|
-
);
|
|
575
|
-
} finally {
|
|
576
|
-
clearTimeout(timer);
|
|
577
|
-
}
|
|
578
|
-
if (response.status === 402) {
|
|
579
|
-
const requirements = await response.json();
|
|
580
|
-
throw new SGLAPIError(402, "Payment required", requirements);
|
|
581
|
-
}
|
|
582
|
-
if (!response.ok) {
|
|
583
|
-
let errorBody;
|
|
584
|
-
let message = response.statusText;
|
|
585
|
-
try {
|
|
586
|
-
errorBody = await response.json();
|
|
587
|
-
const err = errorBody?.error;
|
|
588
|
-
if (typeof err === "string") message = err;
|
|
589
|
-
} catch {
|
|
590
|
-
}
|
|
591
|
-
throw new SGLAPIError(response.status, message, errorBody);
|
|
592
|
-
}
|
|
593
|
-
return await response.json();
|
|
594
|
-
}
|
|
595
|
-
// -- Processors -----------------------------------------------------------
|
|
596
|
-
async deployProcessor(wallet, options) {
|
|
597
|
-
return this.requestWithWalletAuth(
|
|
598
|
-
"POST",
|
|
599
|
-
"/grid/processors",
|
|
600
|
-
wallet,
|
|
601
|
-
options
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
async invokeProcessor(processorName, input, options) {
|
|
605
|
-
const body = { input };
|
|
606
|
-
if (options?.paymentToken) body.payment_token = options.paymentToken;
|
|
607
|
-
return this.requestWithPayment(
|
|
608
|
-
"POST",
|
|
609
|
-
`/grid/processors/${encodeURIComponent(processorName)}/invoke`,
|
|
610
|
-
body,
|
|
611
|
-
options?.paymentHeader
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
async listProcessors(options) {
|
|
615
|
-
const params = new URLSearchParams();
|
|
616
|
-
if (options?.owner) params.set("owner", options.owner);
|
|
617
|
-
if (options?.page != null) params.set("page", String(options.page));
|
|
618
|
-
if (options?.limit != null) params.set("limit", String(options.limit));
|
|
619
|
-
const qs = params.toString();
|
|
620
|
-
return this.request(
|
|
621
|
-
"GET",
|
|
622
|
-
`/grid/processors${qs ? `?${qs}` : ""}`
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
async getProcessor(processorId) {
|
|
626
|
-
return this.request("GET", `/grid/processors/${processorId}`);
|
|
627
|
-
}
|
|
628
|
-
async deleteProcessor(processorId, wallet) {
|
|
629
|
-
return this.requestWithWalletAuth(
|
|
630
|
-
"DELETE",
|
|
631
|
-
`/grid/processors/${processorId}`,
|
|
632
|
-
wallet
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
async getProcessorLogs(processorId, wallet, options) {
|
|
636
|
-
const params = new URLSearchParams();
|
|
637
|
-
if (options?.page != null) params.set("page", String(options.page));
|
|
638
|
-
if (options?.limit != null) params.set("limit", String(options.limit));
|
|
639
|
-
const qs = params.toString();
|
|
640
|
-
return this.requestWithWalletAuth(
|
|
641
|
-
"GET",
|
|
642
|
-
`/grid/processors/${processorId}/logs${qs ? `?${qs}` : ""}`,
|
|
643
|
-
wallet
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
501
|
};
|
|
647
502
|
|
|
648
503
|
// src/vault.ts
|
|
@@ -815,10 +670,226 @@ var VaultClient = class _VaultClient {
|
|
|
815
670
|
return decryptEnvelope(blob, passphrase, parseAadFromKey(info.r2Key));
|
|
816
671
|
}
|
|
817
672
|
};
|
|
673
|
+
|
|
674
|
+
// src/processors.ts
|
|
675
|
+
var PROCESSORS_BASE_URL = "https://processors.x402compute.cc";
|
|
676
|
+
var DEFAULT_TIMEOUT2 = 6e4;
|
|
677
|
+
function isLoopback(hostname) {
|
|
678
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
679
|
+
}
|
|
680
|
+
function parseBaseUrl(raw) {
|
|
681
|
+
let u;
|
|
682
|
+
try {
|
|
683
|
+
u = new URL(raw);
|
|
684
|
+
} catch {
|
|
685
|
+
throw new Error(`ProcessorsClient baseUrl is not a valid URL: ${raw}`);
|
|
686
|
+
}
|
|
687
|
+
if (u.protocol !== "https:" && !(u.protocol === "http:" && isLoopback(u.hostname))) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`ProcessorsClient baseUrl must be https (or http on localhost); got ${u.protocol}//${u.hostname}. The API key is a long-lived full-control credential and must not be sent in the clear.`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
return u;
|
|
693
|
+
}
|
|
694
|
+
function keyAllowedOnHost(u) {
|
|
695
|
+
return u.origin === new URL(PROCESSORS_BASE_URL).origin || isLoopback(u.hostname);
|
|
696
|
+
}
|
|
697
|
+
var ProcessorsClient = class {
|
|
698
|
+
constructor(options = {}) {
|
|
699
|
+
const parsed = parseBaseUrl(options.baseUrl ?? PROCESSORS_BASE_URL);
|
|
700
|
+
this.baseUrl = (options.baseUrl ?? PROCESSORS_BASE_URL).replace(/\/+$/, "");
|
|
701
|
+
this.timeout = options.timeoutMs ?? DEFAULT_TIMEOUT2;
|
|
702
|
+
this.headers = { Accept: "application/json", "Content-Type": "application/json" };
|
|
703
|
+
if (options.apiKey) {
|
|
704
|
+
if (!keyAllowedOnHost(parsed) && !options.allowKeyOnCustomHost) {
|
|
705
|
+
throw new Error(
|
|
706
|
+
`ProcessorsClient refuses to send an API key to ${parsed.origin}. It is neither ${new URL(PROCESSORS_BASE_URL).origin} nor loopback. If you really do run your own processors host, pass allowKeyOnCustomHost: true.`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
this.headers["X-API-Key"] = options.apiKey;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async request(method, path, body, extraHeaders, sendApiKey = true) {
|
|
713
|
+
const url = `${this.baseUrl}${path}`;
|
|
714
|
+
const controller = new AbortController();
|
|
715
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
716
|
+
let response;
|
|
717
|
+
try {
|
|
718
|
+
const base = { ...this.headers };
|
|
719
|
+
if (!sendApiKey) delete base["X-API-Key"];
|
|
720
|
+
response = await fetch(url, {
|
|
721
|
+
method,
|
|
722
|
+
headers: { ...base, ...extraHeaders },
|
|
723
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
724
|
+
signal: controller.signal
|
|
725
|
+
});
|
|
726
|
+
} catch (err) {
|
|
727
|
+
throw new SGLConnectionError(
|
|
728
|
+
`Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
729
|
+
);
|
|
730
|
+
} finally {
|
|
731
|
+
clearTimeout(timer);
|
|
732
|
+
}
|
|
733
|
+
if (response.status === 204) return {};
|
|
734
|
+
const text = await response.text();
|
|
735
|
+
let parsed;
|
|
736
|
+
try {
|
|
737
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
738
|
+
} catch {
|
|
739
|
+
parsed = text;
|
|
740
|
+
}
|
|
741
|
+
if (!response.ok) {
|
|
742
|
+
const body2 = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
743
|
+
const detail = typeof body2?.detail === "string" && body2.detail || typeof body2?.error === "string" && body2.error || "";
|
|
744
|
+
if (response.status === 401 || response.status === 403) {
|
|
745
|
+
throw new SGLAuthError(response.status, detail || "unauthorized", body2);
|
|
746
|
+
}
|
|
747
|
+
if (response.status === 404) {
|
|
748
|
+
throw new SGLNotFoundError(detail || "not found", body2);
|
|
749
|
+
}
|
|
750
|
+
throw new SGLAPIError(response.status, detail || `HTTP ${response.status}`, body2);
|
|
751
|
+
}
|
|
752
|
+
return parsed;
|
|
753
|
+
}
|
|
754
|
+
// ── Discovery ────────────────────────────────────────────────────────────
|
|
755
|
+
/**
|
|
756
|
+
* The public catalogue.
|
|
757
|
+
*
|
|
758
|
+
* Sends NO credential, even when the client holds one. `GET /processors` is owner-scoped when a
|
|
759
|
+
* key is presented and public otherwise, so passing the key here would silently return your own
|
|
760
|
+
* processors instead of the catalogue — the opposite of what the name promises. Use `list()`
|
|
761
|
+
* when you want yours.
|
|
762
|
+
*/
|
|
763
|
+
async catalogue() {
|
|
764
|
+
return this.request("GET", "/processors", void 0, void 0, false);
|
|
765
|
+
}
|
|
766
|
+
/** Processors owned by this key's wallet. Needs `processors:read`. */
|
|
767
|
+
async list() {
|
|
768
|
+
return this.request("GET", "/processors");
|
|
769
|
+
}
|
|
770
|
+
/** Owner projection when the key owns it, public projection otherwise. */
|
|
771
|
+
async get(slug) {
|
|
772
|
+
return this.request("GET", `/processors/${encodeURIComponent(slug)}`);
|
|
773
|
+
}
|
|
774
|
+
// ── Lifecycle (needs processors:write) ───────────────────────────────────
|
|
775
|
+
/**
|
|
776
|
+
* Deploy. The wallet behind the key becomes `owner_wallet`, which is also the x402 `payTo` and
|
|
777
|
+
* the runtime-billing account — so the key must be minted on a SOLANA wallet or this returns
|
|
778
|
+
* `400 solana_wallet_required`.
|
|
779
|
+
*
|
|
780
|
+
* The `invoke_token` in the response is shown once and never again.
|
|
781
|
+
*/
|
|
782
|
+
async deploy(input) {
|
|
783
|
+
return this.request("POST", "/processors", input);
|
|
784
|
+
}
|
|
785
|
+
/** Push new code, a new manifest, or both. Omitting `manifest` keeps the stored one. */
|
|
786
|
+
async update(slug, input) {
|
|
787
|
+
return this.request("PATCH", `/processors/${encodeURIComponent(slug)}`, input);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Delete. **Irreversible, and the slug is burned forever** — it can never be reused, by you or
|
|
791
|
+
* anyone. In-flight runs finish first; the code is wiped when they drain.
|
|
792
|
+
*/
|
|
793
|
+
async delete(slug) {
|
|
794
|
+
return this.request("DELETE", `/processors/${encodeURIComponent(slug)}`);
|
|
795
|
+
}
|
|
796
|
+
/** Stop or restart traffic WITHOUT losing the slug. This is the switch, not `delete`. */
|
|
797
|
+
async setPaused(slug, paused) {
|
|
798
|
+
return this.request("PUT", `/processors/${encodeURIComponent(slug)}/pause`, { paused });
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* List or unlist publicly. Instant, no review step.
|
|
802
|
+
*
|
|
803
|
+
* Unlisting is NOT stopping: an unlisted processor keeps answering anyone holding the URL or an
|
|
804
|
+
* invoke token, earning nothing while still drawing compute from your balance. Use `setPaused`.
|
|
805
|
+
*/
|
|
806
|
+
async setListing(slug, listed) {
|
|
807
|
+
return this.request("PUT", `/processors/${encodeURIComponent(slug)}/listing`, { listed });
|
|
808
|
+
}
|
|
809
|
+
/** Set secret VALUES. Each name must already be declared in `manifest.secrets`. */
|
|
810
|
+
async setSecrets(slug, values) {
|
|
811
|
+
return this.request("PUT", `/processors/${encodeURIComponent(slug)}/secrets`, { values });
|
|
812
|
+
}
|
|
813
|
+
/** Mint a new invoke token. The old one stops working immediately. */
|
|
814
|
+
async rotateToken(slug) {
|
|
815
|
+
return this.request("POST", `/processors/${encodeURIComponent(slug)}/rotate-token`);
|
|
816
|
+
}
|
|
817
|
+
// ── Observability ────────────────────────────────────────────────────────
|
|
818
|
+
async runs(slug) {
|
|
819
|
+
return this.request("GET", `/processors/${encodeURIComponent(slug)}/runs`);
|
|
820
|
+
}
|
|
821
|
+
async run_(slug, runId) {
|
|
822
|
+
return this.request(
|
|
823
|
+
"GET",
|
|
824
|
+
`/processors/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
/** Sales (paid straight to your wallet, with the on-chain tx per row) and runtime spend. */
|
|
828
|
+
async earnings(slug) {
|
|
829
|
+
return this.request("GET", `/processors/${encodeURIComponent(slug)}/earnings`);
|
|
830
|
+
}
|
|
831
|
+
/** The processor's own key/value state. Read-only from out here, by design. */
|
|
832
|
+
async kv(slug) {
|
|
833
|
+
return this.request("GET", `/processors/${encodeURIComponent(slug)}/kv`);
|
|
834
|
+
}
|
|
835
|
+
// ── Webhooks ─────────────────────────────────────────────────────────────
|
|
836
|
+
async getWebhook(slug) {
|
|
837
|
+
return this.request("GET", `/processors/${encodeURIComponent(slug)}/webhook`);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Register or replace. We immediately POST a signed verification to the URL: it must answer 2xx
|
|
841
|
+
* or the webhook stays registered-but-inactive and delivers nothing. The signing secret comes
|
|
842
|
+
* back EXACTLY ONCE.
|
|
843
|
+
*/
|
|
844
|
+
async setWebhook(slug, url) {
|
|
845
|
+
return this.request("PUT", `/processors/${encodeURIComponent(slug)}/webhook`, { url });
|
|
846
|
+
}
|
|
847
|
+
async deleteWebhook(slug) {
|
|
848
|
+
return this.request("DELETE", `/processors/${encodeURIComponent(slug)}/webhook`);
|
|
849
|
+
}
|
|
850
|
+
async testWebhook(slug) {
|
|
851
|
+
return this.request("POST", `/processors/${encodeURIComponent(slug)}/webhook/test`);
|
|
852
|
+
}
|
|
853
|
+
// ── Invoking ─────────────────────────────────────────────────────────────
|
|
854
|
+
/**
|
|
855
|
+
* Run YOUR OWN processor with the invoke token from `deploy()`.
|
|
856
|
+
*
|
|
857
|
+
* Not the API key: the run route deliberately does not read a key as an ownership claim, because
|
|
858
|
+
* it is the only route with both a money path and an anonymous buyer lane. You pay for the
|
|
859
|
+
* compute; nobody pays at call time.
|
|
860
|
+
*/
|
|
861
|
+
async run(slug, input, invokeToken) {
|
|
862
|
+
return this.request(
|
|
863
|
+
"POST",
|
|
864
|
+
`/processors/${encodeURIComponent(slug)}/run`,
|
|
865
|
+
{ input },
|
|
866
|
+
{ Authorization: `Bearer ${invokeToken}` },
|
|
867
|
+
false
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Run someone else's processor as a buyer, with an x402 payment header.
|
|
872
|
+
*
|
|
873
|
+
* Call once WITHOUT `paymentHeader` to get the 402 and its `accepts` array — one entry per chain
|
|
874
|
+
* that publisher takes. Match on `network`, pay that entry, and retry with the header.
|
|
875
|
+
*
|
|
876
|
+
* Re-sending the SAME header returns the run that payment already bought and does NOT charge
|
|
877
|
+
* again. That is the recovery path for every failure mode, because **there are no refunds**: the
|
|
878
|
+
* money went straight to the publisher and the platform never held it.
|
|
879
|
+
*/
|
|
880
|
+
async runWithPayment(slug, input, paymentHeader, acceptNetworks) {
|
|
881
|
+
const extra = {};
|
|
882
|
+
if (paymentHeader) extra["X-Payment"] = paymentHeader;
|
|
883
|
+
if (acceptNetworks?.length) extra["X-Accept-Networks"] = acceptNetworks.join(",");
|
|
884
|
+
return this.request("POST", `/processors/${encodeURIComponent(slug)}/run`, { input }, extra, false);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
818
887
|
// Annotate the CommonJS export names for ESM import in node:
|
|
819
888
|
0 && (module.exports = {
|
|
820
889
|
DEFAULT_BASE_URL,
|
|
821
890
|
GridClient,
|
|
891
|
+
PROCESSORS_BASE_URL,
|
|
892
|
+
ProcessorsClient,
|
|
822
893
|
SGLAPIError,
|
|
823
894
|
SGLAuthError,
|
|
824
895
|
SGLConnectionError,
|