@agentlayer.tech/wallet 0.1.100 → 0.1.102
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/.openclaw/extensions/agent-wallet/dist/index.js +46 -1
- package/.openclaw/extensions/agent-wallet/index.ts +46 -1
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/README.md +18 -0
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/connector_cli.py +198 -0
- package/agent-wallet/agent_wallet/connectors/__init__.py +28 -0
- package/agent-wallet/agent_wallet/connectors/catalog.py +68 -0
- package/agent-wallet/agent_wallet/connectors/client.py +308 -0
- package/agent-wallet/agent_wallet/connectors/intent_policy.py +243 -0
- package/agent-wallet/agent_wallet/connectors/manifest.py +192 -0
- package/agent-wallet/agent_wallet/connectors/registry.py +341 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +56 -5
- package/agent-wallet/agent_wallet/providers/solana_rpc.py +1 -1
- package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +7 -4
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +7 -2
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +1 -1
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +4 -2
- package/agent-wallet/scripts/build_release_bundle.py +1 -0
- package/agent-wallet/scripts/install_agent_wallet.py +1 -1
- package/bin/openclaw-agent-wallet.mjs +38 -0
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/server.py +17 -5
- package/connectors/AGENTS.md +37 -0
- package/connectors/DEVELOPER_GUIDE.md +131 -0
- package/connectors/README.md +139 -0
- package/connectors/READ_ONLY_BETA.md +54 -0
- package/connectors/RELEASING.md +48 -0
- package/connectors/conformance/README.md +31 -0
- package/connectors/conformance/package.json +54 -0
- package/connectors/conformance/scripts/sync-license.mjs +3 -0
- package/connectors/conformance/scripts/sync-spec.mjs +16 -0
- package/connectors/conformance/src/cli.ts +43 -0
- package/connectors/conformance/src/index.ts +7 -0
- package/connectors/conformance/src/runner.ts +314 -0
- package/connectors/conformance/src/types.ts +27 -0
- package/connectors/conformance/test/fixtures/invalid-response-identity.json +14 -0
- package/connectors/conformance/test/fixtures/missing-tool-fixture.json +4 -0
- package/connectors/conformance/test/runner.test.mjs +151 -0
- package/connectors/conformance/tsconfig.json +17 -0
- package/connectors/examples/reference-crypto-data/Dockerfile +21 -0
- package/connectors/examples/reference-crypto-data/README.md +51 -0
- package/connectors/examples/reference-crypto-data/conformance.json +11 -0
- package/connectors/examples/reference-crypto-data/connector.json +95 -0
- package/connectors/examples/reference-crypto-data/package.json +21 -0
- package/connectors/examples/reference-crypto-data/railway.toml +9 -0
- package/connectors/examples/reference-crypto-data/src/connector.ts +137 -0
- package/connectors/examples/reference-crypto-data/src/server.ts +13 -0
- package/connectors/examples/reference-crypto-data/test/connector.test.mjs +97 -0
- package/connectors/examples/reference-crypto-data/tsconfig.json +17 -0
- package/connectors/package-lock.json +561 -0
- package/connectors/package.json +19 -0
- package/connectors/scripts/smoke-packed-packages.mjs +121 -0
- package/connectors/sdk-typescript/README.md +47 -0
- package/connectors/sdk-typescript/package.json +49 -0
- package/connectors/sdk-typescript/scripts/sync-license.mjs +3 -0
- package/connectors/sdk-typescript/src/connector.ts +204 -0
- package/connectors/sdk-typescript/src/errors.ts +11 -0
- package/connectors/sdk-typescript/src/http.ts +116 -0
- package/connectors/sdk-typescript/src/index.ts +26 -0
- package/connectors/sdk-typescript/src/types.ts +101 -0
- package/connectors/sdk-typescript/test/sdk.test.mjs +181 -0
- package/connectors/sdk-typescript/tsconfig.json +21 -0
- package/connectors/spec/connector-manifest.schema.json +340 -0
- package/connectors/spec/connector-protocol.md +159 -0
- package/connectors/spec/transaction-intent.schema.json +281 -0
- package/connectors/templates/read-only/.env.example +1 -0
- package/connectors/templates/read-only/Dockerfile +11 -0
- package/connectors/templates/read-only/README.md +33 -0
- package/connectors/templates/read-only/conformance.json +11 -0
- package/connectors/templates/read-only/connector.json +64 -0
- package/connectors/templates/read-only/package.json +21 -0
- package/connectors/templates/read-only/railway.toml +9 -0
- package/connectors/templates/read-only/src/connector.ts +61 -0
- package/connectors/templates/read-only/src/server.ts +14 -0
- package/connectors/templates/read-only/test/connector.test.mjs +62 -0
- package/connectors/templates/read-only/tsconfig.json +17 -0
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +2 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
FROM node:24-alpine AS build
|
|
2
|
+
|
|
3
|
+
WORKDIR /app/connectors
|
|
4
|
+
COPY connectors/package.json connectors/package-lock.json ./
|
|
5
|
+
COPY connectors/sdk-typescript ./sdk-typescript
|
|
6
|
+
COPY connectors/conformance ./conformance
|
|
7
|
+
COPY connectors/templates/read-only ./templates/read-only
|
|
8
|
+
COPY connectors/examples/reference-crypto-data ./examples/reference-crypto-data
|
|
9
|
+
RUN npm ci
|
|
10
|
+
RUN npm run build --workspace @agentlayer.tech/connector-sdk
|
|
11
|
+
RUN npm run build --workspace @agentlayer.tech/reference-crypto-data-connector
|
|
12
|
+
|
|
13
|
+
FROM node:24-alpine
|
|
14
|
+
|
|
15
|
+
ENV NODE_ENV=production
|
|
16
|
+
WORKDIR /app/connectors
|
|
17
|
+
COPY --from=build --chown=node:node /app/connectors /app/connectors
|
|
18
|
+
USER node
|
|
19
|
+
EXPOSE 3000
|
|
20
|
+
|
|
21
|
+
CMD ["npm", "run", "start", "--workspace", "@agentlayer.tech/reference-crypto-data-connector"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# AgentLayer crypto-data reference connector
|
|
2
|
+
|
|
3
|
+
This is a neutral, read-only reference implementation for Connector Protocol
|
|
4
|
+
v1. It exposes public spot-price and asset-metadata tools. It does not receive a
|
|
5
|
+
wallet address, construct transactions, make payments, sign, or broadcast.
|
|
6
|
+
|
|
7
|
+
The upstream is Coinbase Exchange's public market-data API. The reference uses
|
|
8
|
+
the documented public ticker and currency endpoints and requires no API key:
|
|
9
|
+
|
|
10
|
+
- https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/products/get-product-ticker
|
|
11
|
+
- https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/currencies/get-a-currency
|
|
12
|
+
|
|
13
|
+
Run locally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm ci --prefix connectors
|
|
17
|
+
npm run build --workspace @agentlayer.tech/reference-crypto-data-connector --prefix connectors
|
|
18
|
+
npm run start --workspace @agentlayer.tech/reference-crypto-data-connector --prefix connectors
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The reviewed beta deployment is available at
|
|
22
|
+
`https://reference-crypto-data-production.up.railway.app`. Its manifest is
|
|
23
|
+
already pinned to that exact HTTPS endpoint. Re-run its live conformance check
|
|
24
|
+
after any immutable version release:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd connectors
|
|
28
|
+
node conformance/dist/cli.js \
|
|
29
|
+
--manifest examples/reference-crypto-data/connector.json \
|
|
30
|
+
--fixture examples/reference-crypto-data/conformance.json \
|
|
31
|
+
--endpoint https://reference-crypto-data-production.up.railway.app
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Railway deployment
|
|
35
|
+
|
|
36
|
+
Deploy this directory as one stateless service in a new, isolated Railway
|
|
37
|
+
project. Keep the repository root as the Docker build context and point the
|
|
38
|
+
service Dockerfile at
|
|
39
|
+
`connectors/examples/reference-crypto-data/Dockerfile` (for a root deployment,
|
|
40
|
+
set Railway's `RAILWAY_DOCKERFILE_PATH` service variable to that value).
|
|
41
|
+
Configure the Railway deployment health-check path as `/healthz` with a
|
|
42
|
+
10-second timeout and an `ON_FAILURE` restart policy. The service needs no
|
|
43
|
+
application variables, volume, database, wallet material, or connection to the
|
|
44
|
+
AgentLayer provider gateway.
|
|
45
|
+
|
|
46
|
+
After Railway generates the public domain:
|
|
47
|
+
|
|
48
|
+
1. update `connector.json` to that exact HTTPS URL;
|
|
49
|
+
2. redeploy the immutable manifest version;
|
|
50
|
+
3. run both live conformance and wallet invocation tests;
|
|
51
|
+
4. install the reviewed manifest with `wallet connectors install ... --enable --yes`.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "tech.agentlayer.crypto-data",
|
|
4
|
+
"name": "AgentLayer Crypto Data Reference",
|
|
5
|
+
"version": "0.1.0-beta.1",
|
|
6
|
+
"publisher": {
|
|
7
|
+
"id": "agentlayer",
|
|
8
|
+
"name": "AgentLayer",
|
|
9
|
+
"url": "https://agentlayer.tech"
|
|
10
|
+
},
|
|
11
|
+
"agentlayer": {
|
|
12
|
+
"protocol_version": 1,
|
|
13
|
+
"runtime_range": ">=0.1.101"
|
|
14
|
+
},
|
|
15
|
+
"trust": "community_read_only",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "https",
|
|
18
|
+
"url": "https://reference-crypto-data-production.up.railway.app",
|
|
19
|
+
"timeout_ms": 10000
|
|
20
|
+
},
|
|
21
|
+
"permissions": {
|
|
22
|
+
"wallet_address": false,
|
|
23
|
+
"transaction_intents": false,
|
|
24
|
+
"network_hosts": ["api.exchange.coinbase.com"]
|
|
25
|
+
},
|
|
26
|
+
"tools": [
|
|
27
|
+
{
|
|
28
|
+
"name": "get_spot_price",
|
|
29
|
+
"description": "Get a public spot ticker snapshot for a crypto/fiat or crypto/crypto product.",
|
|
30
|
+
"read_only": true,
|
|
31
|
+
"risk_level": "low",
|
|
32
|
+
"input_schema": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"base": { "type": "string", "pattern": "^[A-Za-z0-9]{2,12}$" },
|
|
36
|
+
"quote": { "type": "string", "pattern": "^[A-Za-z0-9]{2,12}$" }
|
|
37
|
+
},
|
|
38
|
+
"required": ["base", "quote"],
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"output_schema": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"product_id": { "type": "string", "minLength": 5, "maxLength": 25 },
|
|
45
|
+
"base_currency": { "type": "string", "minLength": 2, "maxLength": 12 },
|
|
46
|
+
"quote_currency": { "type": "string", "minLength": 2, "maxLength": 12 },
|
|
47
|
+
"price": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
48
|
+
"bid": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
49
|
+
"ask": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
50
|
+
"last_size": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
51
|
+
"volume_24h": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
52
|
+
"observed_at": { "type": "string", "minLength": 10, "maxLength": 64 },
|
|
53
|
+
"source": { "const": "coinbase_exchange" }
|
|
54
|
+
},
|
|
55
|
+
"required": [
|
|
56
|
+
"product_id", "base_currency", "quote_currency", "price", "bid", "ask",
|
|
57
|
+
"last_size", "volume_24h", "observed_at", "source"
|
|
58
|
+
],
|
|
59
|
+
"additionalProperties": false
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "get_asset_metadata",
|
|
64
|
+
"description": "Get public metadata and network status for a currency code.",
|
|
65
|
+
"read_only": true,
|
|
66
|
+
"risk_level": "low",
|
|
67
|
+
"input_schema": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"asset": { "type": "string", "pattern": "^[A-Za-z0-9]{2,12}$" }
|
|
71
|
+
},
|
|
72
|
+
"required": ["asset"],
|
|
73
|
+
"additionalProperties": false
|
|
74
|
+
},
|
|
75
|
+
"output_schema": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"asset": { "type": "string", "minLength": 2, "maxLength": 12 },
|
|
79
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 120 },
|
|
80
|
+
"status": { "type": "string", "minLength": 1, "maxLength": 40 },
|
|
81
|
+
"type": { "type": ["string", "null"], "maxLength": 40 },
|
|
82
|
+
"min_size": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
83
|
+
"max_precision": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
84
|
+
"default_network": { "type": ["string", "null"], "maxLength": 80 },
|
|
85
|
+
"source": { "const": "coinbase_exchange" }
|
|
86
|
+
},
|
|
87
|
+
"required": [
|
|
88
|
+
"asset", "name", "status", "type", "min_size", "max_precision",
|
|
89
|
+
"default_network", "source"
|
|
90
|
+
],
|
|
91
|
+
"additionalProperties": false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentlayer.tech/reference-crypto-data-connector",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"start": "node dist/server.js",
|
|
12
|
+
"check": "npm run build && node --test test/*.test.mjs"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@agentlayer.tech/connector-sdk": "0.1.0-beta.1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24.0.0",
|
|
19
|
+
"typescript": "^7.0.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ConnectorSdkError,
|
|
5
|
+
defineReadOnlyConnector,
|
|
6
|
+
type ConnectorManifest,
|
|
7
|
+
type JsonObject,
|
|
8
|
+
} from "@agentlayer.tech/connector-sdk";
|
|
9
|
+
|
|
10
|
+
const manifest = JSON.parse(
|
|
11
|
+
readFileSync(new URL("../connector.json", import.meta.url), "utf8")
|
|
12
|
+
) as ConnectorManifest;
|
|
13
|
+
const UPSTREAM_ORIGIN = "https://api.exchange.coinbase.com";
|
|
14
|
+
const MAX_UPSTREAM_BYTES = 256 * 1024;
|
|
15
|
+
const ASSET_PATTERN = /^[A-Z0-9]{2,12}$/;
|
|
16
|
+
|
|
17
|
+
function assetCode(value: unknown, field: string): string {
|
|
18
|
+
const normalized = String(value ?? "").trim().toUpperCase();
|
|
19
|
+
if (!ASSET_PATTERN.test(normalized)) {
|
|
20
|
+
throw new ConnectorSdkError("invalid_arguments", `${field} must be a 2-12 character asset code.`);
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function requiredString(value: unknown, field: string, maxLength = 120): string {
|
|
26
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
27
|
+
throw new ConnectorSdkError("invalid_upstream", `Upstream ${field} is invalid.`, 502);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function optionalString(value: unknown, maxLength = 80): string | null {
|
|
33
|
+
return typeof value === "string" && value.length > 0 && value.length <= maxLength ? value : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readObject(response: Response): Promise<Record<string, unknown>> {
|
|
37
|
+
const contentLength = Number(response.headers.get("content-length") ?? 0);
|
|
38
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_UPSTREAM_BYTES) {
|
|
39
|
+
throw new ConnectorSdkError("upstream_too_large", "Upstream response exceeds the size limit.", 502);
|
|
40
|
+
}
|
|
41
|
+
if (!response.body) {
|
|
42
|
+
throw new ConnectorSdkError("invalid_upstream", "Upstream response body is missing.", 502);
|
|
43
|
+
}
|
|
44
|
+
const reader = response.body.getReader();
|
|
45
|
+
const chunks: Uint8Array[] = [];
|
|
46
|
+
let size = 0;
|
|
47
|
+
while (true) {
|
|
48
|
+
const { done, value } = await reader.read();
|
|
49
|
+
if (done) break;
|
|
50
|
+
size += value.byteLength;
|
|
51
|
+
if (size > MAX_UPSTREAM_BYTES) {
|
|
52
|
+
await reader.cancel();
|
|
53
|
+
throw new ConnectorSdkError("upstream_too_large", "Upstream response exceeds the size limit.", 502);
|
|
54
|
+
}
|
|
55
|
+
chunks.push(value);
|
|
56
|
+
}
|
|
57
|
+
let payload: unknown;
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
60
|
+
} catch {
|
|
61
|
+
throw new ConnectorSdkError("invalid_upstream", "Upstream response is not valid JSON.", 502);
|
|
62
|
+
}
|
|
63
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
64
|
+
throw new ConnectorSdkError("invalid_upstream", "Upstream response root is invalid.", 502);
|
|
65
|
+
}
|
|
66
|
+
return payload as Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function fetchObject(
|
|
70
|
+
path: string,
|
|
71
|
+
fetchImpl: typeof fetch
|
|
72
|
+
): Promise<Record<string, unknown>> {
|
|
73
|
+
const url = new URL(path, UPSTREAM_ORIGIN);
|
|
74
|
+
if (url.origin !== UPSTREAM_ORIGIN || !manifest.permissions.network_hosts.includes(url.hostname)) {
|
|
75
|
+
throw new ConnectorSdkError("upstream_not_allowed", "Upstream host is not allowlisted.", 500);
|
|
76
|
+
}
|
|
77
|
+
const response = await fetchImpl(url, {
|
|
78
|
+
headers: {
|
|
79
|
+
accept: "application/json",
|
|
80
|
+
"cache-control": "no-cache",
|
|
81
|
+
"user-agent": "AgentLayer-Reference-Connector/0.1",
|
|
82
|
+
},
|
|
83
|
+
redirect: "error",
|
|
84
|
+
signal: AbortSignal.timeout(5000),
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new ConnectorSdkError("upstream_http_error", `Upstream returned HTTP ${response.status}.`, 502);
|
|
88
|
+
}
|
|
89
|
+
return readObject(response);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createCryptoDataConnector(fetchImpl: typeof fetch = fetch) {
|
|
93
|
+
return defineReadOnlyConnector({
|
|
94
|
+
manifest,
|
|
95
|
+
responseTtlSeconds: 30,
|
|
96
|
+
handlers: {
|
|
97
|
+
async get_spot_price(arguments_: JsonObject): Promise<JsonObject> {
|
|
98
|
+
const base = assetCode(arguments_.base, "base");
|
|
99
|
+
const quote = assetCode(arguments_.quote, "quote");
|
|
100
|
+
const productId = `${base}-${quote}`;
|
|
101
|
+
const ticker = await fetchObject(`/products/${encodeURIComponent(productId)}/ticker`, fetchImpl);
|
|
102
|
+
return {
|
|
103
|
+
product_id: productId,
|
|
104
|
+
base_currency: base,
|
|
105
|
+
quote_currency: quote,
|
|
106
|
+
price: requiredString(ticker.price, "price", 80),
|
|
107
|
+
bid: requiredString(ticker.bid, "bid", 80),
|
|
108
|
+
ask: requiredString(ticker.ask, "ask", 80),
|
|
109
|
+
last_size: requiredString(ticker.size, "size", 80),
|
|
110
|
+
volume_24h: requiredString(ticker.volume, "volume", 80),
|
|
111
|
+
observed_at: requiredString(ticker.time, "time", 64),
|
|
112
|
+
source: "coinbase_exchange",
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
async get_asset_metadata(arguments_: JsonObject): Promise<JsonObject> {
|
|
116
|
+
const asset = assetCode(arguments_.asset, "asset");
|
|
117
|
+
const currency = await fetchObject(`/currencies/${encodeURIComponent(asset)}`, fetchImpl);
|
|
118
|
+
const details =
|
|
119
|
+
currency.details && typeof currency.details === "object" && !Array.isArray(currency.details)
|
|
120
|
+
? (currency.details as Record<string, unknown>)
|
|
121
|
+
: {};
|
|
122
|
+
return {
|
|
123
|
+
asset: requiredString(currency.id, "id", 12),
|
|
124
|
+
name: requiredString(currency.name, "name"),
|
|
125
|
+
status: requiredString(currency.status, "status", 40),
|
|
126
|
+
type: optionalString(details.type, 40),
|
|
127
|
+
min_size: requiredString(currency.min_size, "min_size", 80),
|
|
128
|
+
max_precision: requiredString(currency.max_precision, "max_precision", 80),
|
|
129
|
+
default_network: optionalString(currency.default_network, 80),
|
|
130
|
+
source: "coinbase_exchange",
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const connector = createCryptoDataConnector();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { startConnectorServer } from "@agentlayer.tech/connector-sdk";
|
|
2
|
+
|
|
3
|
+
import { connector } from "./connector.js";
|
|
4
|
+
|
|
5
|
+
const server = await startConnectorServer(connector);
|
|
6
|
+
console.log(
|
|
7
|
+
JSON.stringify({
|
|
8
|
+
event: "connector_started",
|
|
9
|
+
connector_id: connector.manifest.id,
|
|
10
|
+
connector_version: connector.manifest.version,
|
|
11
|
+
address: server.address(),
|
|
12
|
+
})
|
|
13
|
+
);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { createCryptoDataConnector } from "../dist/connector.js";
|
|
5
|
+
|
|
6
|
+
function request(connector, tool, arguments_) {
|
|
7
|
+
return {
|
|
8
|
+
protocol_version: 1,
|
|
9
|
+
request_id: `reference-${tool}`,
|
|
10
|
+
connector: { id: connector.manifest.id, version: connector.manifest.version },
|
|
11
|
+
tool,
|
|
12
|
+
arguments: arguments_,
|
|
13
|
+
context: {},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("normalizes public ticker data without wallet context", async () => {
|
|
18
|
+
const connector = createCryptoDataConnector(async (url, options) => {
|
|
19
|
+
assert.equal(url.href, "https://api.exchange.coinbase.com/products/BTC-USD/ticker");
|
|
20
|
+
assert.equal(options.redirect, "error");
|
|
21
|
+
assert.equal(options.headers["cache-control"], "no-cache");
|
|
22
|
+
return new Response(
|
|
23
|
+
JSON.stringify({
|
|
24
|
+
price: "78000.12",
|
|
25
|
+
bid: "78000.11",
|
|
26
|
+
ask: "78000.13",
|
|
27
|
+
size: "0.01",
|
|
28
|
+
volume: "1234.5",
|
|
29
|
+
time: "2026-08-27T00:00:00Z",
|
|
30
|
+
}),
|
|
31
|
+
{ status: 200, headers: { "content-type": "application/json" } }
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
const response = await connector.invoke(request(connector, "get_spot_price", {
|
|
35
|
+
base: "btc",
|
|
36
|
+
quote: "usd",
|
|
37
|
+
}));
|
|
38
|
+
assert.deepEqual(response.result, {
|
|
39
|
+
product_id: "BTC-USD",
|
|
40
|
+
base_currency: "BTC",
|
|
41
|
+
quote_currency: "USD",
|
|
42
|
+
price: "78000.12",
|
|
43
|
+
bid: "78000.11",
|
|
44
|
+
ask: "78000.13",
|
|
45
|
+
last_size: "0.01",
|
|
46
|
+
volume_24h: "1234.5",
|
|
47
|
+
observed_at: "2026-08-27T00:00:00Z",
|
|
48
|
+
source: "coinbase_exchange",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("normalizes public asset metadata", async () => {
|
|
53
|
+
const connector = createCryptoDataConnector(async (url) => {
|
|
54
|
+
assert.equal(url.href, "https://api.exchange.coinbase.com/currencies/BTC");
|
|
55
|
+
return new Response(
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
id: "BTC",
|
|
58
|
+
name: "Bitcoin",
|
|
59
|
+
min_size: "0.00000001",
|
|
60
|
+
max_precision: "0.00000001",
|
|
61
|
+
status: "online",
|
|
62
|
+
default_network: "bitcoin",
|
|
63
|
+
details: { type: "crypto" },
|
|
64
|
+
}),
|
|
65
|
+
{ status: 200, headers: { "content-type": "application/json" } }
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
const response = await connector.invoke(request(connector, "get_asset_metadata", { asset: "btc" }));
|
|
69
|
+
assert.deepEqual(response.result, {
|
|
70
|
+
asset: "BTC",
|
|
71
|
+
name: "Bitcoin",
|
|
72
|
+
status: "online",
|
|
73
|
+
type: "crypto",
|
|
74
|
+
min_size: "0.00000001",
|
|
75
|
+
max_precision: "0.00000001",
|
|
76
|
+
default_network: "bitcoin",
|
|
77
|
+
source: "coinbase_exchange",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("rejects oversized and malformed upstream responses", async () => {
|
|
82
|
+
const oversized = createCryptoDataConnector(async () =>
|
|
83
|
+
new Response("{}", { status: 200, headers: { "content-length": "999999" } })
|
|
84
|
+
);
|
|
85
|
+
await assert.rejects(
|
|
86
|
+
oversized.invoke(request(oversized, "get_asset_metadata", { asset: "BTC" })),
|
|
87
|
+
/size limit/
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const malformed = createCryptoDataConnector(async () =>
|
|
91
|
+
new Response("not-json", { status: 200, headers: { "content-type": "application/json" } })
|
|
92
|
+
);
|
|
93
|
+
await assert.rejects(
|
|
94
|
+
malformed.invoke(request(malformed, "get_asset_metadata", { asset: "BTC" })),
|
|
95
|
+
/not valid JSON/
|
|
96
|
+
);
|
|
97
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"exactOptionalPropertyTypes": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"types": ["node"],
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts"]
|
|
17
|
+
}
|