@run402/sdk 1.40.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 +69 -0
- package/core-dist/allowance-auth.d.ts +35 -0
- package/core-dist/allowance-auth.js +129 -0
- package/core-dist/allowance.d.ts +11 -0
- package/core-dist/allowance.js +25 -0
- package/core-dist/client.d.ts +15 -0
- package/core-dist/client.js +42 -0
- package/core-dist/config.d.ts +5 -0
- package/core-dist/config.js +26 -0
- package/core-dist/keystore.d.ts +26 -0
- package/core-dist/keystore.js +97 -0
- package/core-dist/wallet-auth.d.ts +15 -0
- package/core-dist/wallet-auth.js +62 -0
- package/core-dist/wallet.d.ts +10 -0
- package/core-dist/wallet.js +25 -0
- package/dist/credentials.d.ts +70 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +19 -0
- package/dist/credentials.js.map +1 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +47 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +76 -0
- package/dist/kernel.js.map +1 -0
- package/dist/namespaces/admin.d.ts +30 -0
- package/dist/namespaces/admin.d.ts.map +1 -0
- package/dist/namespaces/admin.js +36 -0
- package/dist/namespaces/admin.js.map +1 -0
- package/dist/namespaces/ai.d.ts +57 -0
- package/dist/namespaces/ai.d.ts.map +1 -0
- package/dist/namespaces/ai.js +62 -0
- package/dist/namespaces/ai.js.map +1 -0
- package/dist/namespaces/allowance.d.ts +51 -0
- package/dist/namespaces/allowance.d.ts.map +1 -0
- package/dist/namespaces/allowance.js +116 -0
- package/dist/namespaces/allowance.js.map +1 -0
- package/dist/namespaces/apps.d.ts +158 -0
- package/dist/namespaces/apps.d.ts.map +1 -0
- package/dist/namespaces/apps.js +150 -0
- package/dist/namespaces/apps.js.map +1 -0
- package/dist/namespaces/auth.d.ts +53 -0
- package/dist/namespaces/auth.d.ts.map +1 -0
- package/dist/namespaces/auth.js +106 -0
- package/dist/namespaces/auth.js.map +1 -0
- package/dist/namespaces/billing.d.ts +64 -0
- package/dist/namespaces/billing.d.ts.map +1 -0
- package/dist/namespaces/billing.js +105 -0
- package/dist/namespaces/billing.js.map +1 -0
- package/dist/namespaces/blobs.d.ts +41 -0
- package/dist/namespaces/blobs.d.ts.map +1 -0
- package/dist/namespaces/blobs.js +202 -0
- package/dist/namespaces/blobs.js.map +1 -0
- package/dist/namespaces/blobs.types.d.ts +58 -0
- package/dist/namespaces/blobs.types.d.ts.map +1 -0
- package/dist/namespaces/blobs.types.js +9 -0
- package/dist/namespaces/blobs.types.js.map +1 -0
- package/dist/namespaces/contracts.d.ts +65 -0
- package/dist/namespaces/contracts.d.ts.map +1 -0
- package/dist/namespaces/contracts.js +163 -0
- package/dist/namespaces/contracts.js.map +1 -0
- package/dist/namespaces/domains.d.ts +57 -0
- package/dist/namespaces/domains.d.ts.map +1 -0
- package/dist/namespaces/domains.js +60 -0
- package/dist/namespaces/domains.js.map +1 -0
- package/dist/namespaces/email.d.ts +131 -0
- package/dist/namespaces/email.d.ts.map +1 -0
- package/dist/namespaces/email.js +318 -0
- package/dist/namespaces/email.js.map +1 -0
- package/dist/namespaces/functions.d.ts +43 -0
- package/dist/namespaces/functions.d.ts.map +1 -0
- package/dist/namespaces/functions.js +146 -0
- package/dist/namespaces/functions.js.map +1 -0
- package/dist/namespaces/functions.types.d.ts +96 -0
- package/dist/namespaces/functions.types.d.ts.map +1 -0
- package/dist/namespaces/functions.types.js +6 -0
- package/dist/namespaces/functions.types.js.map +1 -0
- package/dist/namespaces/projects.d.ts +97 -0
- package/dist/namespaces/projects.d.ts.map +1 -0
- package/dist/namespaces/projects.js +214 -0
- package/dist/namespaces/projects.js.map +1 -0
- package/dist/namespaces/projects.types.d.ts +112 -0
- package/dist/namespaces/projects.types.d.ts.map +1 -0
- package/dist/namespaces/projects.types.js +9 -0
- package/dist/namespaces/projects.types.js.map +1 -0
- package/dist/namespaces/secrets.d.ts +23 -0
- package/dist/namespaces/secrets.d.ts.map +1 -0
- package/dist/namespaces/secrets.js +45 -0
- package/dist/namespaces/secrets.js.map +1 -0
- package/dist/namespaces/sender-domain.d.ts +40 -0
- package/dist/namespaces/sender-domain.d.ts.map +1 -0
- package/dist/namespaces/sender-domain.js +69 -0
- package/dist/namespaces/sender-domain.js.map +1 -0
- package/dist/namespaces/service.d.ts +51 -0
- package/dist/namespaces/service.d.ts.map +1 -0
- package/dist/namespaces/service.js +25 -0
- package/dist/namespaces/service.js.map +1 -0
- package/dist/namespaces/sites.d.ts +50 -0
- package/dist/namespaces/sites.d.ts.map +1 -0
- package/dist/namespaces/sites.js +38 -0
- package/dist/namespaces/sites.js.map +1 -0
- package/dist/namespaces/subdomains.d.ts +36 -0
- package/dist/namespaces/subdomains.d.ts.map +1 -0
- package/dist/namespaces/subdomains.js +54 -0
- package/dist/namespaces/subdomains.js.map +1 -0
- package/dist/namespaces/tier.d.ts +36 -0
- package/dist/namespaces/tier.d.ts.map +1 -0
- package/dist/namespaces/tier.js +31 -0
- package/dist/namespaces/tier.js.map +1 -0
- package/dist/node/credentials.d.ts +26 -0
- package/dist/node/credentials.d.ts.map +1 -0
- package/dist/node/credentials.js +69 -0
- package/dist/node/credentials.js.map +1 -0
- package/dist/node/index.d.ts +44 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +43 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/paid-fetch.d.ts +22 -0
- package/dist/node/paid-fetch.d.ts.map +1 -0
- package/dist/node/paid-fetch.js +112 -0
- package/dist/node/paid-fetch.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @run402/sdk
|
|
2
|
+
|
|
3
|
+
Typed TypeScript client for the [Run402](https://run402.com) API.
|
|
4
|
+
|
|
5
|
+
This package is the kernel shared by the `run402-mcp` MCP server, the `run402` CLI, and user-deployed run402 functions. It exposes every run402 API operation as a method on a resource namespace (`sdk.projects.provision()`, `sdk.blobs.put()`, `sdk.functions.deploy()`, etc.).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @run402/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start (Node)
|
|
14
|
+
|
|
15
|
+
The `/node` subpath provides zero-config defaults: reads credentials from the existing `~/.config/run402/` keystore and auto-retries x402 payments from your local allowance.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { run402 } from "@run402/sdk/node";
|
|
19
|
+
|
|
20
|
+
const r = run402();
|
|
21
|
+
const project = await r.projects.provision({ tier: "prototype" });
|
|
22
|
+
await r.blobs.put(project.id, "hello.txt", "hello world");
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start (isomorphic / sandbox)
|
|
26
|
+
|
|
27
|
+
The root entry is isomorphic — no filesystem access, no Node-only imports. Supply your own `CredentialsProvider`:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Run402 } from "@run402/sdk";
|
|
31
|
+
|
|
32
|
+
const r = new Run402({
|
|
33
|
+
apiBase: "https://api.run402.com",
|
|
34
|
+
credentials: {
|
|
35
|
+
async getAuth(path) {
|
|
36
|
+
return { Authorization: `Bearer ${mySessionToken}` };
|
|
37
|
+
},
|
|
38
|
+
async getProject(id) {
|
|
39
|
+
return mySessionProjects[id] ?? null;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Stability
|
|
46
|
+
|
|
47
|
+
This package is `0.x`. Breaking changes may occur between minor versions until `1.0`. Pin an exact version in production dependencies until stabilization.
|
|
48
|
+
|
|
49
|
+
## Errors
|
|
50
|
+
|
|
51
|
+
All failures throw subclasses of `Run402Error`:
|
|
52
|
+
|
|
53
|
+
- `PaymentRequired` — HTTP 402, carries the x402 payment requirements
|
|
54
|
+
- `ProjectNotFound` — project ID not in the credential provider
|
|
55
|
+
- `Unauthorized` — HTTP 401 / 403
|
|
56
|
+
- `ApiError` — other non-2xx responses
|
|
57
|
+
- `NetworkError` — fetch rejected with no HTTP response
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { PaymentRequired } from "@run402/sdk";
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await r.projects.provision({ tier: "prototype" });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e instanceof PaymentRequired) {
|
|
66
|
+
console.log("needs funding:", e.body);
|
|
67
|
+
} else throw e;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowance auth helper — generates SIWX (Sign-In With X / EIP-4361) headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
export interface SIWxAuthHeaders {
|
|
6
|
+
"SIGN-IN-WITH-X": string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* EIP-55 mixed-case checksum encoding.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toChecksumAddress(address: string): string;
|
|
12
|
+
interface SIWEMessageOpts {
|
|
13
|
+
domain: string;
|
|
14
|
+
uri: string;
|
|
15
|
+
statement: string;
|
|
16
|
+
version: string;
|
|
17
|
+
chainId: number;
|
|
18
|
+
nonce: string;
|
|
19
|
+
issuedAt: string;
|
|
20
|
+
expirationTime?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Format an EIP-4361 (SIWE) message. Must be byte-for-byte compatible
|
|
24
|
+
* with the `siwe` library's message format used server-side for verification.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatSIWEMessage(opts: SIWEMessageOpts, address: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Get SIWX auth headers for the Run402 API.
|
|
29
|
+
* Returns null if no allowance is configured.
|
|
30
|
+
*
|
|
31
|
+
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAllowanceAuthHeaders(path: string, allowancePath?: string): SIWxAuthHeaders | null;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=allowance-auth.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowance auth helper — generates SIWX (Sign-In With X / EIP-4361) headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
7
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
8
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
9
|
+
import { readAllowance } from "./allowance.js";
|
|
10
|
+
import { getApiBase } from "./config.js";
|
|
11
|
+
/**
|
|
12
|
+
* EIP-55 mixed-case checksum encoding.
|
|
13
|
+
*/
|
|
14
|
+
export function toChecksumAddress(address) {
|
|
15
|
+
const lower = address.toLowerCase().replace("0x", "");
|
|
16
|
+
const hash = bytesToHex(keccak_256(new TextEncoder().encode(lower)));
|
|
17
|
+
let checksummed = "0x";
|
|
18
|
+
for (let i = 0; i < lower.length; i++) {
|
|
19
|
+
checksummed += parseInt(hash[i], 16) >= 8 ? lower[i].toUpperCase() : lower[i];
|
|
20
|
+
}
|
|
21
|
+
return checksummed;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* EIP-191 personal_sign: sign a message with the allowance's private key.
|
|
25
|
+
*/
|
|
26
|
+
function personalSign(privateKeyHex, address, message) {
|
|
27
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
28
|
+
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
29
|
+
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
30
|
+
prefixed.set(prefix);
|
|
31
|
+
prefixed.set(msgBytes, prefix.length);
|
|
32
|
+
const hash = keccak_256(prefixed);
|
|
33
|
+
const pkHex = privateKeyHex.startsWith("0x")
|
|
34
|
+
? privateKeyHex.slice(2)
|
|
35
|
+
: privateKeyHex;
|
|
36
|
+
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
37
|
+
const rawSig = secp256k1.sign(hash, pkBytes, { prehash: false });
|
|
38
|
+
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
39
|
+
// Determine recovery bit by trying both and matching the address
|
|
40
|
+
let recovery = 0;
|
|
41
|
+
for (const v of [0, 1]) {
|
|
42
|
+
try {
|
|
43
|
+
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
44
|
+
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
45
|
+
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
46
|
+
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
47
|
+
recovery = v;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const r = sig.r.toString(16).padStart(64, "0");
|
|
56
|
+
const s = sig.s.toString(16).padStart(64, "0");
|
|
57
|
+
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
58
|
+
return "0x" + r + s + vHex;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format an EIP-4361 (SIWE) message. Must be byte-for-byte compatible
|
|
62
|
+
* with the `siwe` library's message format used server-side for verification.
|
|
63
|
+
*/
|
|
64
|
+
export function formatSIWEMessage(opts, address) {
|
|
65
|
+
const checksummed = toChecksumAddress(address);
|
|
66
|
+
const lines = [
|
|
67
|
+
`${opts.domain} wants you to sign in with your Ethereum account:`,
|
|
68
|
+
checksummed,
|
|
69
|
+
"",
|
|
70
|
+
opts.statement,
|
|
71
|
+
"",
|
|
72
|
+
`URI: ${opts.uri}`,
|
|
73
|
+
`Version: ${opts.version}`,
|
|
74
|
+
`Chain ID: ${opts.chainId}`,
|
|
75
|
+
`Nonce: ${opts.nonce}`,
|
|
76
|
+
`Issued At: ${opts.issuedAt}`,
|
|
77
|
+
];
|
|
78
|
+
if (opts.expirationTime) {
|
|
79
|
+
lines.push(`Expiration Time: ${opts.expirationTime}`);
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get SIWX auth headers for the Run402 API.
|
|
85
|
+
* Returns null if no allowance is configured.
|
|
86
|
+
*
|
|
87
|
+
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
88
|
+
*/
|
|
89
|
+
export function getAllowanceAuthHeaders(path, allowancePath) {
|
|
90
|
+
const allowance = readAllowance(allowancePath);
|
|
91
|
+
if (!allowance || !allowance.address || !allowance.privateKey)
|
|
92
|
+
return null;
|
|
93
|
+
const apiBase = getApiBase();
|
|
94
|
+
const url = new URL(apiBase);
|
|
95
|
+
const domain = url.hostname;
|
|
96
|
+
const uri = `${apiBase}${path}`;
|
|
97
|
+
const nonce = randomBytes(16).toString("hex");
|
|
98
|
+
const now = new Date();
|
|
99
|
+
const issuedAt = now.toISOString();
|
|
100
|
+
const expirationTime = new Date(now.getTime() + 5 * 60 * 1000).toISOString();
|
|
101
|
+
const message = formatSIWEMessage({
|
|
102
|
+
domain,
|
|
103
|
+
uri,
|
|
104
|
+
statement: "Sign in to Run402",
|
|
105
|
+
version: "1",
|
|
106
|
+
chainId: 84532, // Base Sepolia
|
|
107
|
+
nonce,
|
|
108
|
+
issuedAt,
|
|
109
|
+
expirationTime,
|
|
110
|
+
}, allowance.address);
|
|
111
|
+
const signature = personalSign(allowance.privateKey, allowance.address, message);
|
|
112
|
+
const payload = {
|
|
113
|
+
domain,
|
|
114
|
+
address: toChecksumAddress(allowance.address),
|
|
115
|
+
statement: "Sign in to Run402",
|
|
116
|
+
uri,
|
|
117
|
+
version: "1",
|
|
118
|
+
chainId: "eip155:84532",
|
|
119
|
+
type: "eip191",
|
|
120
|
+
nonce,
|
|
121
|
+
issuedAt,
|
|
122
|
+
expirationTime,
|
|
123
|
+
signature,
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
"SIGN-IN-WITH-X": Buffer.from(JSON.stringify(payload)).toString("base64"),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=allowance-auth.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AllowanceData {
|
|
2
|
+
address: string;
|
|
3
|
+
privateKey: string;
|
|
4
|
+
created?: string;
|
|
5
|
+
funded?: boolean;
|
|
6
|
+
lastFaucet?: string;
|
|
7
|
+
rail?: "x402" | "mpp";
|
|
8
|
+
}
|
|
9
|
+
export declare function readAllowance(path?: string): AllowanceData | null;
|
|
10
|
+
export declare function saveAllowance(data: AllowanceData, path?: string): void;
|
|
11
|
+
//# sourceMappingURL=allowance.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getAllowancePath } from "./config.js";
|
|
5
|
+
export function readAllowance(path) {
|
|
6
|
+
const p = path ?? getAllowancePath();
|
|
7
|
+
if (!existsSync(p))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveAllowance(data, path) {
|
|
17
|
+
const p = path ?? getAllowancePath();
|
|
18
|
+
const dir = dirname(p);
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
const tmp = join(dir, `.allowance.${randomBytes(4).toString("hex")}.tmp`);
|
|
21
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
+
renameSync(tmp, p);
|
|
23
|
+
chmodSync(p, 0o600);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=allowance.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ApiResponse {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
is402?: boolean;
|
|
4
|
+
status: number;
|
|
5
|
+
body: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface ApiRequestOptions {
|
|
8
|
+
method?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
/** Send body as raw string (e.g. for text/plain SQL) */
|
|
12
|
+
rawBody?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function apiRequest(path: string, opts?: ApiRequestOptions): Promise<ApiResponse>;
|
|
15
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getApiBase } from "./config.js";
|
|
2
|
+
export async function apiRequest(path, opts = {}) {
|
|
3
|
+
const { method = "GET", headers = {}, body, rawBody } = opts;
|
|
4
|
+
const url = `${getApiBase()}${path}`;
|
|
5
|
+
const fetchHeaders = { ...headers };
|
|
6
|
+
let fetchBody;
|
|
7
|
+
if (rawBody !== undefined) {
|
|
8
|
+
fetchBody = rawBody;
|
|
9
|
+
}
|
|
10
|
+
else if (body !== undefined) {
|
|
11
|
+
fetchHeaders["Content-Type"] = fetchHeaders["Content-Type"] || "application/json";
|
|
12
|
+
fetchBody = JSON.stringify(body);
|
|
13
|
+
}
|
|
14
|
+
let res;
|
|
15
|
+
try {
|
|
16
|
+
res = await fetch(url, {
|
|
17
|
+
method,
|
|
18
|
+
headers: fetchHeaders,
|
|
19
|
+
body: fetchBody,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
status: 0,
|
|
26
|
+
body: { error: `Network error: ${err.message}` },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let resBody;
|
|
30
|
+
const contentType = res.headers.get("content-type") || "";
|
|
31
|
+
if (contentType.includes("application/json")) {
|
|
32
|
+
resBody = await res.json();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
resBody = await res.text();
|
|
36
|
+
}
|
|
37
|
+
if (res.status === 402) {
|
|
38
|
+
return { ok: false, is402: true, status: 402, body: resBody };
|
|
39
|
+
}
|
|
40
|
+
return { ok: res.ok, status: res.status, body: resBody };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync } from "node:fs";
|
|
4
|
+
export function getApiBase() {
|
|
5
|
+
return process.env.RUN402_API_BASE || "https://api.run402.com";
|
|
6
|
+
}
|
|
7
|
+
export function getConfigDir() {
|
|
8
|
+
return process.env.RUN402_CONFIG_DIR || join(homedir(), ".config", "run402");
|
|
9
|
+
}
|
|
10
|
+
export function getKeystorePath() {
|
|
11
|
+
return join(getConfigDir(), "projects.json");
|
|
12
|
+
}
|
|
13
|
+
export function getAllowancePath() {
|
|
14
|
+
if (process.env.RUN402_ALLOWANCE_PATH)
|
|
15
|
+
return process.env.RUN402_ALLOWANCE_PATH;
|
|
16
|
+
const dir = getConfigDir();
|
|
17
|
+
const newPath = join(dir, "allowance.json");
|
|
18
|
+
const oldPath = join(dir, "wallet.json");
|
|
19
|
+
// Auto-migrate from wallet.json → allowance.json
|
|
20
|
+
if (!existsSync(newPath) && existsSync(oldPath)) {
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
renameSync(oldPath, newPath);
|
|
23
|
+
}
|
|
24
|
+
return newPath;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface StoredProject {
|
|
2
|
+
anon_key: string;
|
|
3
|
+
service_key: string;
|
|
4
|
+
site_url?: string;
|
|
5
|
+
deployed_at?: string;
|
|
6
|
+
last_deployment_id?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface KeyStore {
|
|
9
|
+
active_project_id?: string;
|
|
10
|
+
projects: Record<string, StoredProject>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Load the keystore from disk.
|
|
14
|
+
* Auto-migrates legacy formats:
|
|
15
|
+
* - Array format (CLI legacy): [{project_id, ...}] → {projects: {id: {...}}}
|
|
16
|
+
* - Old field name: expires_at → lease_expires_at
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadKeyStore(path?: string): KeyStore;
|
|
19
|
+
export declare function saveKeyStore(store: KeyStore, path?: string): void;
|
|
20
|
+
export declare function getProject(projectId: string, path?: string): StoredProject | undefined;
|
|
21
|
+
export declare function saveProject(projectId: string, project: StoredProject, path?: string): void;
|
|
22
|
+
export declare function updateProject(projectId: string, update: Partial<StoredProject>, path?: string): void;
|
|
23
|
+
export declare function removeProject(projectId: string, path?: string): void;
|
|
24
|
+
export declare function getActiveProjectId(path?: string): string | undefined;
|
|
25
|
+
export declare function setActiveProjectId(projectId: string, path?: string): void;
|
|
26
|
+
//# sourceMappingURL=keystore.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getKeystorePath } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Load the keystore from disk.
|
|
7
|
+
* Auto-migrates legacy formats:
|
|
8
|
+
* - Array format (CLI legacy): [{project_id, ...}] → {projects: {id: {...}}}
|
|
9
|
+
* - Old field name: expires_at → lease_expires_at
|
|
10
|
+
*/
|
|
11
|
+
export function loadKeyStore(path) {
|
|
12
|
+
const p = path ?? getKeystorePath();
|
|
13
|
+
try {
|
|
14
|
+
const data = readFileSync(p, "utf-8");
|
|
15
|
+
const parsed = JSON.parse(data);
|
|
16
|
+
// Auto-migrate array format (CLI legacy) to object format
|
|
17
|
+
if (Array.isArray(parsed)) {
|
|
18
|
+
const projects = {};
|
|
19
|
+
for (const item of parsed) {
|
|
20
|
+
if (item.project_id) {
|
|
21
|
+
projects[item.project_id] = {
|
|
22
|
+
anon_key: item.anon_key,
|
|
23
|
+
service_key: item.service_key,
|
|
24
|
+
...(item.site_url && { site_url: item.site_url }),
|
|
25
|
+
...(item.deployed_at && { deployed_at: item.deployed_at }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { projects };
|
|
30
|
+
}
|
|
31
|
+
if (parsed && typeof parsed === "object" && parsed.projects) {
|
|
32
|
+
// Strip legacy fields (tier, lease_expires_at, expires_at) from projects
|
|
33
|
+
for (const proj of Object.values(parsed.projects)) {
|
|
34
|
+
const rec = proj;
|
|
35
|
+
delete rec.tier;
|
|
36
|
+
delete rec.lease_expires_at;
|
|
37
|
+
delete rec.expires_at;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...(parsed.active_project_id && { active_project_id: parsed.active_project_id }),
|
|
41
|
+
projects: parsed.projects,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { projects: {} };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { projects: {} };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function saveKeyStore(store, path) {
|
|
51
|
+
const p = path ?? getKeystorePath();
|
|
52
|
+
const dir = dirname(p);
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
const tmp = join(dir, `.projects.${randomBytes(4).toString("hex")}.tmp`);
|
|
55
|
+
writeFileSync(tmp, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
56
|
+
renameSync(tmp, p);
|
|
57
|
+
chmodSync(p, 0o600);
|
|
58
|
+
}
|
|
59
|
+
export function getProject(projectId, path) {
|
|
60
|
+
const store = loadKeyStore(path);
|
|
61
|
+
return store.projects[projectId];
|
|
62
|
+
}
|
|
63
|
+
export function saveProject(projectId, project, path) {
|
|
64
|
+
const p = path ?? getKeystorePath();
|
|
65
|
+
const store = loadKeyStore(p);
|
|
66
|
+
store.projects[projectId] = project;
|
|
67
|
+
saveKeyStore(store, p);
|
|
68
|
+
}
|
|
69
|
+
export function updateProject(projectId, update, path) {
|
|
70
|
+
const p = path ?? getKeystorePath();
|
|
71
|
+
const store = loadKeyStore(p);
|
|
72
|
+
const existing = store.projects[projectId];
|
|
73
|
+
if (existing) {
|
|
74
|
+
store.projects[projectId] = { ...existing, ...update };
|
|
75
|
+
saveKeyStore(store, p);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function removeProject(projectId, path) {
|
|
79
|
+
const p = path ?? getKeystorePath();
|
|
80
|
+
const store = loadKeyStore(p);
|
|
81
|
+
delete store.projects[projectId];
|
|
82
|
+
if (store.active_project_id === projectId) {
|
|
83
|
+
delete store.active_project_id;
|
|
84
|
+
}
|
|
85
|
+
saveKeyStore(store, p);
|
|
86
|
+
}
|
|
87
|
+
export function getActiveProjectId(path) {
|
|
88
|
+
const store = loadKeyStore(path);
|
|
89
|
+
return store.active_project_id;
|
|
90
|
+
}
|
|
91
|
+
export function setActiveProjectId(projectId, path) {
|
|
92
|
+
const p = path ?? getKeystorePath();
|
|
93
|
+
const store = loadKeyStore(p);
|
|
94
|
+
store.active_project_id = projectId;
|
|
95
|
+
saveKeyStore(store, p);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
export interface WalletAuthHeaders {
|
|
6
|
+
"X-Run402-Wallet": string;
|
|
7
|
+
"X-Run402-Signature": string;
|
|
8
|
+
"X-Run402-Timestamp": string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get wallet auth headers for the Run402 API.
|
|
12
|
+
* Returns null if no wallet is configured.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getWalletAuthHeaders(walletPath?: string): WalletAuthHeaders | null;
|
|
15
|
+
//# sourceMappingURL=wallet-auth.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
7
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
8
|
+
import { readWallet } from "./wallet.js";
|
|
9
|
+
/**
|
|
10
|
+
* EIP-191 personal_sign: sign a message with the wallet's private key.
|
|
11
|
+
*/
|
|
12
|
+
function personalSign(privateKeyHex, address, message) {
|
|
13
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
14
|
+
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
15
|
+
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
16
|
+
prefixed.set(prefix);
|
|
17
|
+
prefixed.set(msgBytes, prefix.length);
|
|
18
|
+
const hash = keccak_256(prefixed);
|
|
19
|
+
const pkHex = privateKeyHex.startsWith("0x")
|
|
20
|
+
? privateKeyHex.slice(2)
|
|
21
|
+
: privateKeyHex;
|
|
22
|
+
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
23
|
+
const rawSig = secp256k1.sign(hash, pkBytes);
|
|
24
|
+
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
25
|
+
// Determine recovery bit by trying both and matching the address
|
|
26
|
+
let recovery = 0;
|
|
27
|
+
for (const v of [0, 1]) {
|
|
28
|
+
try {
|
|
29
|
+
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
30
|
+
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
31
|
+
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
32
|
+
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
33
|
+
recovery = v;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const r = sig.r.toString(16).padStart(64, "0");
|
|
42
|
+
const s = sig.s.toString(16).padStart(64, "0");
|
|
43
|
+
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
44
|
+
return "0x" + r + s + vHex;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get wallet auth headers for the Run402 API.
|
|
48
|
+
* Returns null if no wallet is configured.
|
|
49
|
+
*/
|
|
50
|
+
export function getWalletAuthHeaders(walletPath) {
|
|
51
|
+
const wallet = readWallet(walletPath);
|
|
52
|
+
if (!wallet || !wallet.address || !wallet.privateKey)
|
|
53
|
+
return null;
|
|
54
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
55
|
+
const signature = personalSign(wallet.privateKey, wallet.address, `run402:${timestamp}`);
|
|
56
|
+
return {
|
|
57
|
+
"X-Run402-Wallet": wallet.address,
|
|
58
|
+
"X-Run402-Signature": signature,
|
|
59
|
+
"X-Run402-Timestamp": timestamp,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=wallet-auth.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WalletData {
|
|
2
|
+
address: string;
|
|
3
|
+
privateKey: string;
|
|
4
|
+
created?: string;
|
|
5
|
+
funded?: boolean;
|
|
6
|
+
lastFaucet?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function readWallet(path?: string): WalletData | null;
|
|
9
|
+
export declare function saveWallet(data: WalletData, path?: string): void;
|
|
10
|
+
//# sourceMappingURL=wallet.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getWalletPath } from "./config.js";
|
|
5
|
+
export function readWallet(path) {
|
|
6
|
+
const p = path ?? getWalletPath();
|
|
7
|
+
if (!existsSync(p))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveWallet(data, path) {
|
|
17
|
+
const p = path ?? getWalletPath();
|
|
18
|
+
const dir = dirname(p);
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
const tmp = join(dir, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
|
|
21
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
+
renameSync(tmp, p);
|
|
23
|
+
chmodSync(p, 0o600);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=wallet.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential provider interface for the Run402 SDK.
|
|
3
|
+
*
|
|
4
|
+
* The SDK's request kernel calls `getAuth` before each request to obtain
|
|
5
|
+
* signed auth headers, and `getProject` to resolve per-project anon/service
|
|
6
|
+
* keys. All filesystem, environment, and session-state access lives inside
|
|
7
|
+
* provider implementations — never in the kernel.
|
|
8
|
+
*
|
|
9
|
+
* Node consumers use {@link NodeCredentialsProvider} from `@run402/sdk/node`
|
|
10
|
+
* which wraps the local keystore + allowance. Sandbox consumers supply their
|
|
11
|
+
* own implementation bound to a session token issued by the supervisor.
|
|
12
|
+
*
|
|
13
|
+
* The two required methods (`getAuth`, `getProject`) support every API call.
|
|
14
|
+
* The optional methods let providers opt in to local persistence (keystore
|
|
15
|
+
* writes, active-project tracking). Namespace methods that need a missing
|
|
16
|
+
* optional method throw a descriptive error at runtime.
|
|
17
|
+
*/
|
|
18
|
+
export interface ProjectKeys {
|
|
19
|
+
anon_key: string;
|
|
20
|
+
service_key: string;
|
|
21
|
+
site_url?: string;
|
|
22
|
+
deployed_at?: string;
|
|
23
|
+
last_deployment_id?: string;
|
|
24
|
+
mailbox_id?: string;
|
|
25
|
+
mailbox_address?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AllowanceData {
|
|
28
|
+
address: string;
|
|
29
|
+
privateKey: string;
|
|
30
|
+
created?: string;
|
|
31
|
+
funded?: boolean;
|
|
32
|
+
lastFaucet?: string;
|
|
33
|
+
rail?: "x402" | "mpp";
|
|
34
|
+
}
|
|
35
|
+
export interface CredentialsProvider {
|
|
36
|
+
/**
|
|
37
|
+
* Return per-request auth headers for the given API path, or null if none
|
|
38
|
+
* are available. Typical headers: `SIGN-IN-WITH-X` (SIWE), `Authorization:
|
|
39
|
+
* Bearer ...`. The kernel merges these into the request headers without
|
|
40
|
+
* overwriting headers explicitly set on the request.
|
|
41
|
+
*/
|
|
42
|
+
getAuth(path: string): Promise<Record<string, string> | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the anon/service keys for a project. Returns null if the project
|
|
45
|
+
* is not known to this provider — the kernel then throws ProjectNotFound.
|
|
46
|
+
*/
|
|
47
|
+
getProject(id: string): Promise<ProjectKeys | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Persist project keys after a successful provision or deploy. Optional:
|
|
50
|
+
* providers without local storage (pure session providers) may omit this.
|
|
51
|
+
*/
|
|
52
|
+
saveProject?(id: string, project: ProjectKeys): Promise<void>;
|
|
53
|
+
/** Partially update a project's stored fields (e.g. mailbox_id, last_deployment_id). Optional. */
|
|
54
|
+
updateProject?(id: string, patch: Partial<ProjectKeys>): Promise<void>;
|
|
55
|
+
/** Remove a project from local storage after deletion. Optional. */
|
|
56
|
+
removeProject?(id: string): Promise<void>;
|
|
57
|
+
/** Set the active/default project id in local state. Optional. */
|
|
58
|
+
setActiveProject?(id: string): Promise<void>;
|
|
59
|
+
/** Get the active/default project id from local state. Optional. */
|
|
60
|
+
getActiveProject?(): Promise<string | null>;
|
|
61
|
+
/** Read the local allowance (wallet). Optional — sandbox providers may omit. */
|
|
62
|
+
readAllowance?(): Promise<AllowanceData | null>;
|
|
63
|
+
/** Persist the local allowance. Optional. */
|
|
64
|
+
saveAllowance?(data: AllowanceData): Promise<void>;
|
|
65
|
+
/** Generate a fresh allowance keypair. Optional — Node default uses secp256k1 + keccak for the Ethereum address. */
|
|
66
|
+
createAllowance?(): Promise<AllowanceData>;
|
|
67
|
+
/** Return the absolute path to the local allowance file, for diagnostic output. Optional. */
|
|
68
|
+
getAllowancePath?(): string;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=credentials.d.ts.map
|