@absol-labs/agent 0.7.3 → 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 +19 -4
- package/dist/discovery/registry.d.ts +110 -305
- package/dist/discovery/registry.d.ts.map +1 -1
- package/dist/discovery/registry.js +141 -318
- package/dist/discovery/registry.js.map +1 -1
- package/dist/frameworks/agentkit.d.ts.map +1 -1
- package/dist/frameworks/agentkit.js +23 -6
- package/dist/frameworks/agentkit.js.map +1 -1
- package/dist/gateway/caller-auth-gateway.d.ts +103 -2
- package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
- package/dist/gateway/caller-auth-gateway.js +176 -19
- package/dist/gateway/caller-auth-gateway.js.map +1 -1
- package/dist/gateway/http-server.d.ts +12 -0
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +45 -1
- package/dist/gateway/http-server.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/wallet/autonomous-wallet-store.d.ts +123 -0
- package/dist/wallet/autonomous-wallet-store.d.ts.map +1 -0
- package/dist/wallet/autonomous-wallet-store.js +318 -0
- package/dist/wallet/autonomous-wallet-store.js.map +1 -0
- package/dist/wallet/autonomous-wallet.d.ts +14 -39
- package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
- package/dist/wallet/autonomous-wallet.js +12 -145
- package/dist/wallet/autonomous-wallet.js.map +1 -1
- package/dist/wallet/cdp-sdk.d.ts +23 -0
- package/dist/wallet/cdp-sdk.d.ts.map +1 -0
- package/dist/wallet/cdp-sdk.js +27 -0
- package/dist/wallet/cdp-sdk.js.map +1 -0
- package/dist/wallet/encrypted-file-credential-store.d.ts +41 -0
- package/dist/wallet/encrypted-file-credential-store.d.ts.map +1 -0
- package/dist/wallet/encrypted-file-credential-store.js +221 -0
- package/dist/wallet/encrypted-file-credential-store.js.map +1 -0
- package/dist/wallet/provider.d.ts +1 -1
- package/dist/wallet/provider.d.ts.map +1 -1
- package/dist/wallet/provider.js +8 -3
- package/dist/wallet/provider.js.map +1 -1
- package/dist/wallet/secret-service-probe.d.ts +56 -0
- package/dist/wallet/secret-service-probe.d.ts.map +1 -0
- package/dist/wallet/secret-service-probe.js +407 -0
- package/dist/wallet/secret-service-probe.js.map +1 -0
- package/dist/zktls/reclaim-js-sdk.d.ts +24 -0
- package/dist/zktls/reclaim-js-sdk.d.ts.map +1 -0
- package/dist/zktls/reclaim-js-sdk.js +29 -0
- package/dist/zktls/reclaim-js-sdk.js.map +1 -0
- package/dist/zktls/reclaim.d.ts +14 -2
- package/dist/zktls/reclaim.d.ts.map +1 -1
- package/dist/zktls/reclaim.js +29 -6
- package/dist/zktls/reclaim.js.map +1 -1
- package/dist/zktls/t2-delivery-proof.d.ts +8 -1
- package/dist/zktls/t2-delivery-proof.d.ts.map +1 -1
- package/dist/zktls/t2-delivery-proof.js +22 -6
- package/dist/zktls/t2-delivery-proof.js.map +1 -1
- package/docs/agent-layer.md +150 -0
- package/docs/autonomous-privy-wallet.md +133 -0
- package/docs/crewai.md +70 -0
- package/docs/eliza.md +109 -0
- package/docs/langchain.md +63 -0
- package/docs/mcp-hosted.md +137 -0
- package/docs/privy-embedded-wallet.md +102 -0
- package/docs/quickstart.md +370 -0
- package/docs/threat-model.md +160 -0
- package/package.json +19 -6
- package/src/discovery/registry.ts +242 -414
- package/src/frameworks/agentkit.ts +24 -5
- package/src/gateway/caller-auth-gateway.ts +281 -15
- package/src/gateway/http-server.ts +64 -0
- package/src/index.ts +24 -0
- package/src/wallet/autonomous-wallet-store.ts +487 -0
- package/src/wallet/autonomous-wallet.ts +57 -224
- package/src/wallet/cdp-sdk.ts +33 -0
- package/src/wallet/encrypted-file-credential-store.ts +341 -0
- package/src/wallet/provider.ts +16 -9
- package/src/wallet/secret-service-probe.ts +487 -0
- package/src/zktls/reclaim-js-sdk.ts +50 -0
- package/src/zktls/reclaim.ts +57 -23
- package/src/zktls/t2-delivery-proof.ts +28 -10
package/docs/eliza.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ElizaOS Plugin
|
|
2
|
+
|
|
3
|
+
Metrik ships a real ElizaOS plugin so an Eliza agent can **hire verified services**,
|
|
4
|
+
monitor them, and reclaim unspent funds out of the box — without ever bypassing the
|
|
5
|
+
signed spend mandate. It reuses the exact same SDK client and mandate budget-guard as
|
|
6
|
+
the Coinbase AgentKit action provider (`src/frameworks/agentkit.ts`); the business logic
|
|
7
|
+
is not forked.
|
|
8
|
+
|
|
9
|
+
> _x402 proves the payment, Metrik proves the delivery._
|
|
10
|
+
|
|
11
|
+
The plugin lives in [`src/frameworks/eliza.ts`](../src/frameworks/eliza.ts).
|
|
12
|
+
|
|
13
|
+
## Actions
|
|
14
|
+
|
|
15
|
+
| Action (Eliza name) | What it does |
|
|
16
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `HIRE_VERIFIED_SERVICE` | Escrow USDC on Base for a per-second, oracle-verified delivery stream to a seller. Fails closed **before any transaction** if the hire would breach the signed mandate. |
|
|
18
|
+
| `CHECK_STREAM_STATUS` | Read verified accrual, current state, and claimable/reclaimable USDC. In V2, failed or unproven intervals do not advance entitlement; buyer close or expiry ends the session. Sets `shouldStop` when the stream is no longer `active`. |
|
|
19
|
+
| `RECLAIM_UNSPENT` | Return unspent escrowed USDC to the buyer (optionally `closeFirst`), under the original mandate and the V2 checkpoint / escape-window rules. |
|
|
20
|
+
| `CLOSE_STREAM` | Finalize accrual and halt further payment, under the original mandate. |
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
`@elizaos/core` is a peer of `@absol-labs/agent`.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @absol-labs/agent @elizaos/core
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Env
|
|
31
|
+
|
|
32
|
+
The plugin needs a signing wallet + an SDK client pointed at the Metrik escrow. On
|
|
33
|
+
testnet you may pass a raw private key; in production inject a Coinbase CDP wallet
|
|
34
|
+
instead (`createWalletBackedAgentClient` accepts either).
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
METRIK_AGENT_RPC_URL Base Sepolia RPC URL
|
|
38
|
+
METRIK_AGENT_ESCROW Metrik escrow contract address (0x...)
|
|
39
|
+
METRIK_AGENT_USDC Settlement USDC token address (0x...)
|
|
40
|
+
METRIK_AGENT_PRIVATE_KEY Agent signing key (testnet only)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Add the plugin to a character
|
|
44
|
+
|
|
45
|
+
The Metrik plugin is a **factory** — it needs a wallet-backed client — so it is built
|
|
46
|
+
programmatically and attached to the agent, rather than referenced by a bare package
|
|
47
|
+
name (the runtime cannot inject a funded wallet into a string-named plugin). A full,
|
|
48
|
+
runnable-shaped example lives in
|
|
49
|
+
[`examples/metrik-character.ts`](../examples/metrik-character.ts).
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { http } from "viem";
|
|
53
|
+
import { baseSepolia } from "viem/chains";
|
|
54
|
+
import { createMetrikElizaPlugin } from "@absol-labs/agent/eliza";
|
|
55
|
+
import { createWalletBackedAgentClient } from "@absol-labs/agent";
|
|
56
|
+
|
|
57
|
+
const { agentClient } = await createWalletBackedAgentClient(
|
|
58
|
+
{
|
|
59
|
+
chain: baseSepolia,
|
|
60
|
+
transport: http(process.env.METRIK_AGENT_RPC_URL!),
|
|
61
|
+
escrow: process.env.METRIK_AGENT_ESCROW! as `0x${string}`,
|
|
62
|
+
usdc: process.env.METRIK_AGENT_USDC! as `0x${string}`,
|
|
63
|
+
settlementTarget: {
|
|
64
|
+
chainId: baseSepolia.id,
|
|
65
|
+
escrow: process.env.METRIK_AGENT_ESCROW! as `0x${string}`,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{ privateKey: process.env.METRIK_AGENT_PRIVATE_KEY as `0x${string}` },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const metrikPlugin = createMetrikElizaPlugin({ agentClient });
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
agents: [
|
|
75
|
+
{
|
|
76
|
+
character: { name: "Metrik Buyer", plugins: [] },
|
|
77
|
+
plugins: [metrikPlugin],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Action envelope
|
|
84
|
+
|
|
85
|
+
Eliza actions receive a namespaced payload under `content.metrik`. Build it with
|
|
86
|
+
`createMetrikElizaContent()` rather than hand-rolling the envelope:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { createMetrikElizaContent } from "@absol-labs/agent/eliza";
|
|
90
|
+
|
|
91
|
+
const hire = createMetrikElizaContent("HIRE_VERIFIED_SERVICE", {
|
|
92
|
+
operator: "0x2222222222222222222222222222222222222222", // seller payout wallet
|
|
93
|
+
serviceRef: "0x1111...1111", // bytes32 service id the oracle verifies
|
|
94
|
+
budgetUsdc: "90000000", // atomic units, 6 decimals
|
|
95
|
+
ratePerSecondUsdc: "1000000",
|
|
96
|
+
maxDurationSeconds: 3600,
|
|
97
|
+
signedMandate, // owner-signed spend mandate
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`RECLAIM_UNSPENT` and `CLOSE_STREAM` take `{ streamId, signedMandate }`
|
|
102
|
+
(`RECLAIM_UNSPENT` also accepts `closeFirst`). `CHECK_STREAM_STATUS` takes `{ streamId }`.
|
|
103
|
+
|
|
104
|
+
## Publishing
|
|
105
|
+
|
|
106
|
+
The plugin is publish-ready under `@absol-labs/agent`. Submitting it to the ElizaOS
|
|
107
|
+
plugin **registry** (or an npm publish) requires registry/npm credentials and a
|
|
108
|
+
registry PR — those steps are human-gated and are **not** performed by this package.
|
|
109
|
+
See the PR description for the follow-up.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# LangChain Adapter
|
|
2
|
+
|
|
3
|
+
The Phase 2 LangChain surface is a small, mandate-gated toolset built on the
|
|
4
|
+
existing agent client:
|
|
5
|
+
|
|
6
|
+
- `hire_verified_service`
|
|
7
|
+
- `check_stream_status`
|
|
8
|
+
- `reclaim_unspent`
|
|
9
|
+
|
|
10
|
+
Create them with [`createLangChainVerifiedStreamTools()`](../src/frameworks/langchain.ts).
|
|
11
|
+
|
|
12
|
+
## Safety model
|
|
13
|
+
|
|
14
|
+
- `hire_verified_service` checks the signed mandate before any stream open.
|
|
15
|
+
- `reclaim_unspent` passes the same signed-mandate context into the agent client,
|
|
16
|
+
which re-checks authorization before the SDK can write.
|
|
17
|
+
- `check_stream_status` is read-only and also returns `shouldStop` for a closed
|
|
18
|
+
stream. In V2, verification gaps do not auto-pause the stream; the buyer must
|
|
19
|
+
explicitly close it to end further exposure.
|
|
20
|
+
|
|
21
|
+
## Example loop
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createLangChainVerifiedStreamTools } from "@absol-labs/agent/langchain";
|
|
25
|
+
|
|
26
|
+
const tools = createLangChainVerifiedStreamTools({
|
|
27
|
+
agentClient,
|
|
28
|
+
now: () => Math.floor(Date.now() / 1000),
|
|
29
|
+
mandateStateResolver: {
|
|
30
|
+
getSpentSoFarUsdc: async () => spentSoFarUsdc,
|
|
31
|
+
getRevokedMandateIds: async () => revokedMandateIds,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const opened = await tools.hire.invoke({
|
|
36
|
+
operator: "0xYourOperator",
|
|
37
|
+
serviceRef: "0xYourServiceRef",
|
|
38
|
+
budgetUsdc: "90000000",
|
|
39
|
+
ratePerSecondUsdc: "1000000",
|
|
40
|
+
maxDurationSeconds: 3600,
|
|
41
|
+
signedMandate,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (;;) {
|
|
45
|
+
const status = await tools.status.invoke({ streamId: opened.streamId });
|
|
46
|
+
if (status.shouldStop) break;
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, 10_000));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await tools.reclaim.invoke({
|
|
51
|
+
streamId: opened.streamId,
|
|
52
|
+
closeFirst: true,
|
|
53
|
+
signedMandate,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Reviewer checklist
|
|
58
|
+
|
|
59
|
+
1. Provide a real `VerifiedStreamAgentClient` or compatible wrapper.
|
|
60
|
+
2. Provide a real signed mandate plus resolver-backed cumulative-spend /
|
|
61
|
+
revocation state.
|
|
62
|
+
3. Use `status.shouldStop` to halt work when the stream is no longer active.
|
|
63
|
+
4. Reclaim explicitly through `reclaim_unspent` when the task ends or quality drops.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Hosted MCP over Streamable HTTP
|
|
2
|
+
|
|
3
|
+
The agent lane ships two MCP transports over the **same** tool set
|
|
4
|
+
(`hire_verified_service`, `check_stream_status`, `reclaim_unspent`, `list_streams`,
|
|
5
|
+
and the optional `prove_https_response`):
|
|
6
|
+
|
|
7
|
+
- **stdio** (`src/mcp/stdio.ts`, `pnpm mcp:stdio`) — local dev, one process per
|
|
8
|
+
caller. Unchanged.
|
|
9
|
+
- **Streamable HTTP** (`src/mcp/http.ts` + `src/mcp/http-server.ts`,
|
|
10
|
+
`pnpm mcp:http`) — a hosted, **multi-tenant**, per-caller-authenticated
|
|
11
|
+
endpoint suitable for deployment. This document covers the HTTP transport.
|
|
12
|
+
|
|
13
|
+
The HTTP transport uses the MCP SDK's `StreamableHTTPServerTransport` (the
|
|
14
|
+
current remote transport; the deprecated HTTP+SSE transport is not used). It
|
|
15
|
+
speaks the standard Streamable HTTP session contract: `POST /mcp` for JSON-RPC
|
|
16
|
+
requests (an `initialize` opens a session and the server returns an
|
|
17
|
+
`Mcp-Session-Id` header), `GET /mcp` for the server→client SSE stream, and
|
|
18
|
+
`DELETE /mcp` to end a session — all carrying the `Mcp-Session-Id` header once a
|
|
19
|
+
session exists.
|
|
20
|
+
|
|
21
|
+
## Security model (fail-closed)
|
|
22
|
+
|
|
23
|
+
- **Per-caller auth before anything runs.** Every request must carry
|
|
24
|
+
`Authorization: Bearer <token>`. A missing or invalid token is rejected with
|
|
25
|
+
`401` **before** any MCP session is created or any tool executes — so an
|
|
26
|
+
unauthenticated request can never move funds or read another caller's state.
|
|
27
|
+
- **Constant-time token comparison.** Tokens are compared via SHA-256 +
|
|
28
|
+
`crypto.timingSafeEqual` across all configured tenants with no early exit;
|
|
29
|
+
tokens are never logged.
|
|
30
|
+
- **Tenant isolation.** Each valid token maps to its **own** tenant context —
|
|
31
|
+
its own wallet-backed agent client and its own stream registry. One caller
|
|
32
|
+
can never drive another caller's funds or observe another caller's streams.
|
|
33
|
+
MCP sessions are bound to the tenant that opened them: presenting another
|
|
34
|
+
tenant's `Mcp-Session-Id` is rejected with `403`; an unknown session id is
|
|
35
|
+
`404`.
|
|
36
|
+
- **Refuses to run open.** If no tenants/tokens are configured, the server
|
|
37
|
+
throws before binding — it will never serve an unauthenticated endpoint.
|
|
38
|
+
- **No stack traces leak.** Errors are returned as generic JSON-RPC error
|
|
39
|
+
envelopes; internal detail stays server-side.
|
|
40
|
+
- **CORS** is off by default (same-origin / non-browser clients). Set
|
|
41
|
+
`METRIK_MCP_ALLOWED_ORIGINS` to opt specific browser origins in.
|
|
42
|
+
|
|
43
|
+
## Environment contract
|
|
44
|
+
|
|
45
|
+
| Variable | Required | Default | Meaning |
|
|
46
|
+
| ---------------------------- | -------- | --------- | ---------------------------------------------------------------------- |
|
|
47
|
+
| `METRIK_MCP_TENANTS` | **yes** | — | JSON array of tenant configs (below). Empty/missing ⇒ refuse to start. |
|
|
48
|
+
| `PORT` / `METRIK_MCP_PORT` | no | `8080` | Listen port. |
|
|
49
|
+
| `METRIK_MCP_HOST` | no | `0.0.0.0` | Bind host. |
|
|
50
|
+
| `METRIK_MCP_PATH` | no | `/mcp` | MCP endpoint path. |
|
|
51
|
+
| `METRIK_MCP_ALLOWED_ORIGINS` | no | _(none)_ | Comma-separated CORS origins, or `*`. |
|
|
52
|
+
|
|
53
|
+
Each entry in `METRIK_MCP_TENANTS` is:
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
{
|
|
57
|
+
"id": "acme", // stable tenant id (routing/logging only)
|
|
58
|
+
"token": "<secret-bearer>", // the caller's bearer token — never logged
|
|
59
|
+
"env": {
|
|
60
|
+
// optional: layered over the process env to
|
|
61
|
+
"METRIK_AGENT_CDP_OWNER_NAME": "acme-agent", // build THIS tenant's wallet
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A tenant's `env` is merged over the process environment and used to build that
|
|
67
|
+
tenant's isolated, wallet-backed agent client via the same
|
|
68
|
+
`METRIK_AGENT_*` / wallet / Reclaim variables the stdio server uses
|
|
69
|
+
(`METRIK_AGENT_RPC_URL`, `METRIK_AGENT_ESCROW`, `METRIK_AGENT_USDC`,
|
|
70
|
+
`METRIK_AGENT_PRIVATE_KEY` **or** the `CDP_*` wallet vars, etc. — see
|
|
71
|
+
`src/mcp/server.ts` and `src/wallet/provider.ts`). Tenants that share the same
|
|
72
|
+
network but need distinct wallets override only the wallet vars in their `env`.
|
|
73
|
+
|
|
74
|
+
> Provide tokens and wallet secrets through your host's secret manager / env UI.
|
|
75
|
+
> Never commit them, and never bake them into the image.
|
|
76
|
+
|
|
77
|
+
## Run it
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
export METRIK_AGENT_RPC_URL="https://base-sepolia.example/rpc"
|
|
81
|
+
export METRIK_AGENT_ESCROW="0x21948a5E6AE8d9A3D1050791AB6138657Fb54286"
|
|
82
|
+
export METRIK_AGENT_USDC="0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
83
|
+
# ... wallet vars (private key or CDP) ...
|
|
84
|
+
export METRIK_MCP_TENANTS='[{"id":"acme","token":"REPLACE_WITH_SECRET"}]'
|
|
85
|
+
export PORT=8080
|
|
86
|
+
|
|
87
|
+
pnpm mcp:http # dev (tsx)
|
|
88
|
+
# or, from a build:
|
|
89
|
+
node dist/mcp/http-server.js
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The process binds `0.0.0.0:$PORT` and logs only its address (never secrets).
|
|
93
|
+
`SIGINT`/`SIGTERM` trigger a graceful shutdown of all live sessions.
|
|
94
|
+
|
|
95
|
+
## Connect an MCP client
|
|
96
|
+
|
|
97
|
+
Point any Streamable HTTP MCP client at `http(s)://<host>:<port>/mcp` with the
|
|
98
|
+
tenant's bearer token. With the TypeScript SDK:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
102
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
103
|
+
|
|
104
|
+
const transport = new StreamableHTTPClientTransport(
|
|
105
|
+
new URL("https://mcp.example.com/mcp"),
|
|
106
|
+
{
|
|
107
|
+
requestInit: {
|
|
108
|
+
headers: { Authorization: `Bearer ${process.env.METRIK_MCP_TOKEN}` },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
const client = new Client({ name: "my-agent", version: "1.0.0" });
|
|
113
|
+
await client.connect(transport);
|
|
114
|
+
await client.listTools();
|
|
115
|
+
await client.callTool({ name: "list_streams", arguments: {} });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Claude Desktop / other config-driven clients: add a remote MCP server pointing
|
|
119
|
+
at the `/mcp` URL and set the `Authorization: Bearer <token>` header. The
|
|
120
|
+
transport handles the session id, POST/GET/DELETE lifecycle automatically.
|
|
121
|
+
|
|
122
|
+
## Programmatic embedding
|
|
123
|
+
|
|
124
|
+
The transport is also exported from the package root for hosting inside another
|
|
125
|
+
service:
|
|
126
|
+
|
|
127
|
+
- `createHostedMcpHttpServer({ resolver, path?, allowedOrigins? })` — build a
|
|
128
|
+
Node `http.Server` around a `TenantResolver`; `listen(port, host)` /
|
|
129
|
+
`close()`.
|
|
130
|
+
- `StaticTenantResolver` — constant-time, in-memory resolver over a list of
|
|
131
|
+
`HostedMcpTenant`s.
|
|
132
|
+
- `parseHostedMcpServerConfig(env)` / `createHostedMcpTenants(config)` /
|
|
133
|
+
`startHostedMcpHttpServerFromEnv(env)` — the env-driven wiring used by
|
|
134
|
+
`pnpm mcp:http`.
|
|
135
|
+
|
|
136
|
+
Supply your own `TenantResolver` to source tokens/tenants from a database or a
|
|
137
|
+
secrets service instead of env.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Privy embedded-wallet integration
|
|
2
|
+
|
|
3
|
+
`@absol-labs/agent` provides a small, provider-neutral adapter for a
|
|
4
|
+
user-owned Privy embedded **EOA**. The site or host application must first
|
|
5
|
+
authenticate the user with Privy's client SDK and obtain that user's EIP-1193
|
|
6
|
+
wallet provider. The agent package does not authenticate users, create wallets
|
|
7
|
+
from an app id alone, receive an app secret, or persist key material.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { http } from "viem";
|
|
11
|
+
import { baseSepolia } from "viem/chains";
|
|
12
|
+
import {
|
|
13
|
+
createWalletBackedAgentClient,
|
|
14
|
+
type PrivyEip1193Provider,
|
|
15
|
+
} from "@absol-labs/agent";
|
|
16
|
+
|
|
17
|
+
// The authenticated Privy wallet/session is created by the host application.
|
|
18
|
+
const provider: PrivyEip1193Provider =
|
|
19
|
+
await host.getAuthenticatedPrivyProvider();
|
|
20
|
+
const address = await host.getAuthenticatedPrivyAddress();
|
|
21
|
+
|
|
22
|
+
const { wallet, agentClient } = await createWalletBackedAgentClient(
|
|
23
|
+
{
|
|
24
|
+
chain: baseSepolia,
|
|
25
|
+
// The Privy mode replaces this transport with the authenticated provider
|
|
26
|
+
// so writes use eth_sendTransaction. It remains the default for other modes.
|
|
27
|
+
transport: http(process.env.BASE_SEPOLIA_RPC_URL!),
|
|
28
|
+
escrow: "0x…",
|
|
29
|
+
usdc: "0x…",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
privy: {
|
|
33
|
+
appId: "your-public-privy-app-id",
|
|
34
|
+
address,
|
|
35
|
+
provider,
|
|
36
|
+
chainId: 84532,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// `wallet.account` is a viem-compatible JSON-RPC EOA. Every autonomous write
|
|
42
|
+
// still goes through the signed Metrik spend-mandate checks in agentClient.
|
|
43
|
+
await wallet.account.signMessage({ message: "user-approved test" });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The adapter verifies Base Sepolia (`chainId: 84532`) and the configured address
|
|
47
|
+
against `eth_chainId` and `eth_accounts` before construction and before every
|
|
48
|
+
`personal_sign` or `eth_signTypedData_v4` request. Contract writes are sent
|
|
49
|
+
through the same authenticated provider with `eth_sendTransaction`; the adapter
|
|
50
|
+
does not claim access to raw transaction signatures. A network or account switch
|
|
51
|
+
fails closed with `PrivyEmbeddedWalletError`.
|
|
52
|
+
|
|
53
|
+
## Headless external-agent session
|
|
54
|
+
|
|
55
|
+
The Metrik dApp can attach a Privy additional signer under a non-empty Privy policy,
|
|
56
|
+
collect an owner-signed Metrik spend mandate, and exchange those approvals for a
|
|
57
|
+
short-lived broker token. An external agent adapts the copied session JSON without any
|
|
58
|
+
Privy app secret or wallet key:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import {
|
|
62
|
+
createPrivySessionProvider,
|
|
63
|
+
createWalletBackedAgentClient,
|
|
64
|
+
} from "@absol-labs/agent";
|
|
65
|
+
|
|
66
|
+
const session = JSON.parse(process.env.METRIK_AGENT_SESSION_JSON!);
|
|
67
|
+
const provider = createPrivySessionProvider({
|
|
68
|
+
brokerUrl: session.brokerUrl,
|
|
69
|
+
sessionToken: session.sessionToken,
|
|
70
|
+
address: session.address,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const { agentClient } = await createWalletBackedAgentClient(sdkConfig, {
|
|
74
|
+
privy: {
|
|
75
|
+
appId: session.appId,
|
|
76
|
+
address: session.address,
|
|
77
|
+
provider,
|
|
78
|
+
chainId: session.chainId,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The broker permits only bounded USDC approval, V2 stream open/close/reclaim, and
|
|
84
|
+
short-lived invocation-capability signatures for an active buyer stream. It stores a
|
|
85
|
+
hash—not the plaintext—of the session token. Revoked or expired sessions fail closed.
|
|
86
|
+
It asks Privy to sponsor only the first successful write in the session.
|
|
87
|
+
|
|
88
|
+
## Boundary and limitations
|
|
89
|
+
|
|
90
|
+
- The adapter supports a user-owned Privy EOA only.
|
|
91
|
+
- It does not claim ERC-4337, EIP-1271, or smart-account support.
|
|
92
|
+
- The browser adapter alone does not provide headless access. Headless operation requires
|
|
93
|
+
the deployed broker, a real Privy additional-signer policy, and user approval in the dApp.
|
|
94
|
+
- The provider must implement the standard `eth_sendTransaction` EIP-1193 path.
|
|
95
|
+
In Privy mode, `createWalletBackedAgentClient` replaces the SDK transport with
|
|
96
|
+
that authenticated provider so a separate public RPC cannot bypass the wallet.
|
|
97
|
+
- The host must keep the Privy app secret and any authorization material
|
|
98
|
+
server-side. Only the public app id belongs in browser configuration.
|
|
99
|
+
|
|
100
|
+
Do not use an unauthenticated or arbitrary EIP-1193 provider as a substitute
|
|
101
|
+
for Privy's authenticated user wallet. Do not put a Privy app secret in
|
|
102
|
+
`PrivyEmbeddedWalletConfig`.
|