@mantle-agent/client 0.0.0-stage → 0.1.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 +81 -2
- package/dist/agent.d.ts +307 -0
- package/dist/agent.js +149 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.d.ts +16 -0
- package/dist/browser.js +53 -0
- package/dist/browser.js.map +1 -0
- package/dist/http.d.ts +21 -0
- package/dist/http.js +40 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/owner.d.ts +199 -0
- package/dist/owner.js +237 -0
- package/dist/owner.js.map +1 -0
- package/package.json +82 -3
- package/src/agent.ts +337 -0
- package/src/browser.ts +62 -0
- package/src/http.ts +46 -0
- package/src/index.ts +3 -0
- package/src/owner.ts +414 -0
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mantle-agent/client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Client SDK for **Mantle Agent Accounts**: give an AI agent a Mantle account it can trade with inside limits its
|
|
4
|
+
owner sets on chain, while the owner keeps every key.
|
|
5
|
+
|
|
6
|
+
- **Agent side** (`AgentClient`, `connectAgent`): claim an account, quote, trade, read status, scheduled runs,
|
|
7
|
+
emergency stop. The agent never holds a private key.
|
|
8
|
+
- **Owner side** (`@mantle-agent/client/owner`: `OwnerClient` + flow functions): passkey signup/login, custody setup, approving an agent,
|
|
9
|
+
authorize / renew / revoke / withdraw, auto-run settings. Each owner operation runs step by step and can resume
|
|
10
|
+
from a failed step.
|
|
11
|
+
- **Browser** (`@mantle-agent/client/browser`): Turnkey passkey implementation of the owner's signer.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @mantle-agent/client # agent side: no other dependencies
|
|
15
|
+
npm install @mantle-agent/core @mantle-agent/custody viem # + owner side (@mantle-agent/client/owner)
|
|
16
|
+
npm install @turnkey/sdk-browser @turnkey/api-key-stamper @turnkey/crypto # + passkeys in the browser
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Agent side
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { AgentClient, connectAgent } from "@mantle-agent/client";
|
|
23
|
+
import { parseEther } from "viem";
|
|
24
|
+
|
|
25
|
+
// Once: register the agent's Ed25519 key and wait for the owner to approve on the confirmation page.
|
|
26
|
+
const { agentId, credentials } = await connectAgent({
|
|
27
|
+
baseUrl: "https://agent-api.example",
|
|
28
|
+
agentName: "my-agent",
|
|
29
|
+
host: "my-app",
|
|
30
|
+
key: { publicKey, sign: (msg) => ed25519SignHex(msg) }, // your key storage
|
|
31
|
+
onClaim: (claim) => console.log(`Open ${claim.claimUrl} and confirm ${claim.userCode} (${claim.fingerprint})`),
|
|
32
|
+
});
|
|
33
|
+
// Store `credentials` securely. They are returned exactly once.
|
|
34
|
+
|
|
35
|
+
const agent = new AgentClient("https://agent-api.example", credentials);
|
|
36
|
+
const status = await agent.status(); // balance, remaining limits, recent trades
|
|
37
|
+
const trade = await agent.trade({ side: "buy", token: "0x…", amountIn: parseEther("1"), requestId: "my-unique-id" });
|
|
38
|
+
if (trade.status !== "confirmed") console.log(trade.errorCode, trade.error);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Retry with the **same `requestId`**: the same trade is never executed twice. Errors are `ClientError` with a stable
|
|
42
|
+
`code` (`PER_TRADE_CAP_EXCEEDED`, `INSUFFICIENT_INVENTORY`, `SESSION_INACTIVE`, …).
|
|
43
|
+
|
|
44
|
+
Things only the owner can do are **asked for**, never done: `agent.requests.authorize(limits)`, `.renew(seconds)`,
|
|
45
|
+
`.revoke()`, `.withdraw({ to, asset, amount })`, `.autoRun(settings)`, `.resume()`. Each returns an `OwnerRequest`
|
|
46
|
+
with `confirmUrl` and the exact `summary` the owner will confirm with a passkey; `agent.requests.get(id)` tells you
|
|
47
|
+
when it is `completed`. `connectAgent({ …, authorize: limits })` proposes limits with the claim, so the owner claims
|
|
48
|
+
and authorizes in one visit.
|
|
49
|
+
|
|
50
|
+
Scheduled runs: `agent.runtime.lease(host)` → trade (pass `leaseId` to the constructor) → `agent.runtime.complete(leaseId, { llmCostUsd })`.
|
|
51
|
+
|
|
52
|
+
## Owner side
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { OwnerClient, signUp, logIn, ensureCustody, lookupClaim, approveClaim,
|
|
56
|
+
getRequest, approveRequest, runOperation, startRun, runSucceeded } from "@mantle-agent/client/owner";
|
|
57
|
+
import { passkeyTurnkey } from "@mantle-agent/client/browser";
|
|
58
|
+
|
|
59
|
+
const api = new OwnerClient("https://agent-api.example");
|
|
60
|
+
const { turnkeyOrganizationId } = await api.call("GET", "/v1/platform");
|
|
61
|
+
const tk = passkeyTurnkey({ parentOrgId: turnkeyOrganizationId });
|
|
62
|
+
|
|
63
|
+
let session = await logIn(api, tk); // or signUp(api, tk, name) the first time
|
|
64
|
+
session = await ensureCustody(api, tk, session); // once: add the platform as a restricted delegate
|
|
65
|
+
|
|
66
|
+
const claim = await lookupClaim(api, userCodeFromTheAgent);
|
|
67
|
+
// show claim.fingerprint and ask the owner to compare it with the agent's
|
|
68
|
+
const { agentId, requestId } = await approveClaim(api, tk, session, claim); // requestId: the limits proposed with the claim
|
|
69
|
+
|
|
70
|
+
// The agent asked in the chat; the owner opened its link (`/r/<requestId>`) and read `request.summary`.
|
|
71
|
+
const request = await getRequest(api, requestId);
|
|
72
|
+
const { prepared } = await approveRequest(api, requestId); // null for auto-run settings: already applied
|
|
73
|
+
const run = await runOperation(api, tk, session, startRun(prepared!), {
|
|
74
|
+
onChange: (r) => render(r.steps), // progress for the UI
|
|
75
|
+
});
|
|
76
|
+
if (!runSucceeded(run)) { /* show the failed step; call runOperation(api, tk, session, run) to retry from it */ }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Everything the owner signs happens on their device with their passkey; the API verifies signatures and reads
|
|
80
|
+
Turnkey state back instead of trusting the page.
|
|
81
|
+
|
|
82
|
+
See the repository's `docs/integration.md` for the full picture.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
import { type Fetch } from "./http.js";
|
|
3
|
+
export type CredentialKind = "trading" | "social" | "runtime";
|
|
4
|
+
export type Credentials = Record<CredentialKind, {
|
|
5
|
+
token: string;
|
|
6
|
+
expiresAt: string;
|
|
7
|
+
}>;
|
|
8
|
+
/** What the agent signs with when proving it holds the key that started a claim. */
|
|
9
|
+
export type AgentKey = {
|
|
10
|
+
/** Ed25519 public key, 32 raw bytes as hex. */
|
|
11
|
+
publicKey: string;
|
|
12
|
+
/** Ed25519 signature over `message` (UTF-8), 64 bytes as hex. */
|
|
13
|
+
sign(message: string): string | Promise<string>;
|
|
14
|
+
};
|
|
15
|
+
export type Claim = {
|
|
16
|
+
claimId: string;
|
|
17
|
+
claimUrl: string;
|
|
18
|
+
userCode: string;
|
|
19
|
+
fingerprint: string;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
/** Proposed limits as the owner will see them, or null. */
|
|
22
|
+
proposal: string[] | null;
|
|
23
|
+
};
|
|
24
|
+
export type Side = "buy" | "sell";
|
|
25
|
+
/** Amounts are decimal strings in wei (18 decimals), as the API returns them. */
|
|
26
|
+
export type Quote = {
|
|
27
|
+
quoteId: string;
|
|
28
|
+
side: Side;
|
|
29
|
+
adapter: Address;
|
|
30
|
+
token: Address;
|
|
31
|
+
amountIn: string;
|
|
32
|
+
expectedOut: string;
|
|
33
|
+
minAmountOut: string;
|
|
34
|
+
expiresAt: string;
|
|
35
|
+
};
|
|
36
|
+
export type IntentStatus = "accepted" | "submitted" | "pending" | "confirmed" | "failed" | "unknown";
|
|
37
|
+
export type Intent = {
|
|
38
|
+
intentId: string;
|
|
39
|
+
requestId: string;
|
|
40
|
+
quoteId: string;
|
|
41
|
+
side: Side;
|
|
42
|
+
token: Address;
|
|
43
|
+
amountIn: string;
|
|
44
|
+
minAmountOut: string;
|
|
45
|
+
status: IntentStatus;
|
|
46
|
+
txHash: Hex | null;
|
|
47
|
+
amountOut: string | null;
|
|
48
|
+
errorCode: string | null;
|
|
49
|
+
error: string | null;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
};
|
|
53
|
+
export type DimensionState = "active" | "expiring" | "expired" | "revoked" | "none";
|
|
54
|
+
export type RuntimeStatus = {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
paused: boolean;
|
|
57
|
+
nextRunAt: string | null;
|
|
58
|
+
config: {
|
|
59
|
+
intervalSeconds: number;
|
|
60
|
+
maxRunSeconds: number;
|
|
61
|
+
maxTradesPerRun: number;
|
|
62
|
+
maxTradesPerDay: number;
|
|
63
|
+
maxSpendPerDayWei: string;
|
|
64
|
+
maxLlmCostPerDayUsd: number;
|
|
65
|
+
};
|
|
66
|
+
today: {
|
|
67
|
+
trades: number;
|
|
68
|
+
spendWei: string;
|
|
69
|
+
llmCostUsd: number;
|
|
70
|
+
};
|
|
71
|
+
running: {
|
|
72
|
+
leaseId: string;
|
|
73
|
+
expiresAt: string;
|
|
74
|
+
trades: number;
|
|
75
|
+
} | null;
|
|
76
|
+
pendingCommands: {
|
|
77
|
+
commandId: string;
|
|
78
|
+
kind: string;
|
|
79
|
+
}[];
|
|
80
|
+
} | null;
|
|
81
|
+
export type AgentStatus = {
|
|
82
|
+
agent: {
|
|
83
|
+
agentId: string;
|
|
84
|
+
name: string;
|
|
85
|
+
status: "pending" | "active" | "stopped";
|
|
86
|
+
account: Address | null;
|
|
87
|
+
};
|
|
88
|
+
balanceWei: string | null;
|
|
89
|
+
session: {
|
|
90
|
+
keyHash: Hex;
|
|
91
|
+
state: DimensionState;
|
|
92
|
+
expiresAt: string;
|
|
93
|
+
maxSlippageBps: number;
|
|
94
|
+
limits: {
|
|
95
|
+
perTradeCap: string;
|
|
96
|
+
periodCap: string;
|
|
97
|
+
totalCap: string;
|
|
98
|
+
};
|
|
99
|
+
remaining: {
|
|
100
|
+
perTrade: string;
|
|
101
|
+
period: string;
|
|
102
|
+
total: string;
|
|
103
|
+
} | null;
|
|
104
|
+
} | null;
|
|
105
|
+
credentials: {
|
|
106
|
+
kind: CredentialKind;
|
|
107
|
+
state: DimensionState;
|
|
108
|
+
expiresAt: string;
|
|
109
|
+
lastUsedAt: string | null;
|
|
110
|
+
}[];
|
|
111
|
+
runtime: RuntimeStatus;
|
|
112
|
+
recentTrades: Intent[];
|
|
113
|
+
};
|
|
114
|
+
export type Lease = {
|
|
115
|
+
granted: false;
|
|
116
|
+
reason: "disabled" | "paused" | "not_due" | "budget_exhausted";
|
|
117
|
+
nextRunAt: string | null;
|
|
118
|
+
} | {
|
|
119
|
+
granted: true;
|
|
120
|
+
leaseId: string;
|
|
121
|
+
expiresAt: string;
|
|
122
|
+
budget: {
|
|
123
|
+
trades: number;
|
|
124
|
+
spendWei: string;
|
|
125
|
+
llmCostUsd: number;
|
|
126
|
+
};
|
|
127
|
+
commands: {
|
|
128
|
+
commandId: string;
|
|
129
|
+
kind: "pause" | "resume" | "run_now";
|
|
130
|
+
createdAt: string;
|
|
131
|
+
}[];
|
|
132
|
+
};
|
|
133
|
+
/** Session limits an agent can ask for (PRD WAL-02). Amounts in MNT wei; the owner confirms them with a passkey. */
|
|
134
|
+
export type SessionLimits = {
|
|
135
|
+
perTradeCap: bigint;
|
|
136
|
+
periodCap: bigint;
|
|
137
|
+
totalCap: bigint;
|
|
138
|
+
periodSeconds: number;
|
|
139
|
+
durationSeconds: number;
|
|
140
|
+
maxSlippageBps: number;
|
|
141
|
+
};
|
|
142
|
+
/** PRD V1 defaults: 2 MNT per trade, 10 MNT per 24 h, 20 MNT in total, 7 days, 2 % slippage. */
|
|
143
|
+
export declare const DEFAULT_SESSION_LIMITS: SessionLimits;
|
|
144
|
+
/** Scheduled-run budgets (PRD RUN-02/03), on top of the on-chain limits. */
|
|
145
|
+
export type AutoRunSettings = {
|
|
146
|
+
enabled: boolean;
|
|
147
|
+
intervalSeconds: number;
|
|
148
|
+
maxRunSeconds: number;
|
|
149
|
+
maxTradesPerRun: number;
|
|
150
|
+
maxTradesPerDay: number;
|
|
151
|
+
maxSpendPerDay: bigint;
|
|
152
|
+
maxLlmCostPerDayUsd: number;
|
|
153
|
+
};
|
|
154
|
+
export type OwnerRequestKind = "authorize" | "renew" | "revoke" | "withdraw" | "runtime" | "resume";
|
|
155
|
+
/**
|
|
156
|
+
* Something only the owner can do, asked for by the agent. Relay `confirmUrl` and `summary` to the owner; they
|
|
157
|
+
* confirm on the page with a passkey. `completed` once it took effect (on chain for signing kinds).
|
|
158
|
+
*/
|
|
159
|
+
export type OwnerRequest = {
|
|
160
|
+
requestId: string;
|
|
161
|
+
agentId: string;
|
|
162
|
+
agentName: string;
|
|
163
|
+
kind: OwnerRequestKind;
|
|
164
|
+
status: "pending" | "approved" | "completed" | "rejected" | "expired" | "superseded";
|
|
165
|
+
createdBy: "agent" | "claim";
|
|
166
|
+
summary: string[];
|
|
167
|
+
warnings: string[];
|
|
168
|
+
confirmUrl: string;
|
|
169
|
+
expiresAt: string;
|
|
170
|
+
createdAt: string;
|
|
171
|
+
operation: {
|
|
172
|
+
operationId: string;
|
|
173
|
+
status: "prepared" | "submitted" | "confirmed" | "failed";
|
|
174
|
+
error: string | null;
|
|
175
|
+
} | null;
|
|
176
|
+
note: string | null;
|
|
177
|
+
};
|
|
178
|
+
/** Must match the API's claimProofMessage. */
|
|
179
|
+
export declare const claimProofMessage: (claimId: string) => string;
|
|
180
|
+
/**
|
|
181
|
+
* PRD IDN-02: register the agent key, hand the owner a link + code + fingerprint via `onClaim`, then poll with a
|
|
182
|
+
* proof of the key until the owner approves. Returns the credentials exactly once; store them securely.
|
|
183
|
+
*/
|
|
184
|
+
export declare function connectAgent(params: {
|
|
185
|
+
baseUrl: string;
|
|
186
|
+
agentName: string;
|
|
187
|
+
host: string;
|
|
188
|
+
key: AgentKey;
|
|
189
|
+
/** Show this to the owner. Compare `fingerprint` with one computed locally from the key. */
|
|
190
|
+
onClaim: (claim: Claim) => void | Promise<void>;
|
|
191
|
+
/** Limits to propose: the owner confirms them right after claiming, in the same visit. */
|
|
192
|
+
authorize?: SessionLimits;
|
|
193
|
+
pollMs?: number;
|
|
194
|
+
fetch?: Fetch;
|
|
195
|
+
}): Promise<{
|
|
196
|
+
agentId: string;
|
|
197
|
+
credentials: Credentials;
|
|
198
|
+
}>;
|
|
199
|
+
/** First half of `connectAgent`: register the key and get the link + code + fingerprint for the owner. */
|
|
200
|
+
export declare function startClaim(params: {
|
|
201
|
+
baseUrl: string;
|
|
202
|
+
agentName: string;
|
|
203
|
+
host: string;
|
|
204
|
+
key: AgentKey;
|
|
205
|
+
authorize?: SessionLimits;
|
|
206
|
+
fetch?: Fetch;
|
|
207
|
+
}): Promise<Claim>;
|
|
208
|
+
export type ClaimResult = {
|
|
209
|
+
status: "pending";
|
|
210
|
+
expiresAt: string;
|
|
211
|
+
} | {
|
|
212
|
+
status: "exchanged";
|
|
213
|
+
agentId: string;
|
|
214
|
+
credentials: Credentials;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Second half of `connectAgent`, one attempt: `pending` until the owner approves, then the credentials exactly once.
|
|
218
|
+
* Stateless, so an agent can start a claim, end its turn, and check again later (CLAIM_EXPIRED once it lapsed).
|
|
219
|
+
*/
|
|
220
|
+
export declare function exchangeClaim(params: {
|
|
221
|
+
baseUrl: string;
|
|
222
|
+
claimId: string;
|
|
223
|
+
key: AgentKey;
|
|
224
|
+
fetch?: Fetch;
|
|
225
|
+
}): Promise<ClaimResult>;
|
|
226
|
+
/**
|
|
227
|
+
* The agent's side of a Mantle Agent Account. It can only do what the owner authorized: trade within on-chain
|
|
228
|
+
* limits, read its status, manage its own scheduled runs, and stop itself. It never holds a private key.
|
|
229
|
+
*/
|
|
230
|
+
export declare class AgentClient {
|
|
231
|
+
readonly baseUrl: string;
|
|
232
|
+
private readonly credentials;
|
|
233
|
+
private readonly fetchImpl;
|
|
234
|
+
/** Set when running inside a scheduled run; trades are charged to that run's budget. */
|
|
235
|
+
leaseId: string | undefined;
|
|
236
|
+
constructor(baseUrl: string, credentials: Partial<Credentials> | null, opts?: {
|
|
237
|
+
fetch?: Fetch;
|
|
238
|
+
leaseId?: string;
|
|
239
|
+
});
|
|
240
|
+
private call;
|
|
241
|
+
status(): Promise<AgentStatus>;
|
|
242
|
+
/** Price a trade. Valid ~30 s and for one trade. `amountIn` is wei (MNT for buys, tokens for sells). */
|
|
243
|
+
quote(params: {
|
|
244
|
+
side: Side;
|
|
245
|
+
token: Address;
|
|
246
|
+
amountIn: bigint;
|
|
247
|
+
}): Promise<Quote>;
|
|
248
|
+
/**
|
|
249
|
+
* Quote and execute. Reuse `requestId` when retrying: the same trade is never executed twice. With `wait`, polls
|
|
250
|
+
* until confirmed / failed / unknown (or `timeoutMs`).
|
|
251
|
+
*/
|
|
252
|
+
trade(params: {
|
|
253
|
+
side: Side;
|
|
254
|
+
token: Address;
|
|
255
|
+
amountIn: bigint;
|
|
256
|
+
requestId?: string;
|
|
257
|
+
wait?: boolean;
|
|
258
|
+
timeoutMs?: number;
|
|
259
|
+
}): Promise<Intent>;
|
|
260
|
+
tradeStatus(intentId: string): Promise<Intent>;
|
|
261
|
+
waitForTrade(intent: Intent, timeoutMs?: number): Promise<Intent>;
|
|
262
|
+
/**
|
|
263
|
+
* Emergency stop: revokes all of this agent's credentials at once. `confirmUrl` (null without a session) is the
|
|
264
|
+
* revoke request the owner confirms to make it permanent on chain.
|
|
265
|
+
*/
|
|
266
|
+
stop(): Promise<{
|
|
267
|
+
stopped: true;
|
|
268
|
+
revokedCredentials: number;
|
|
269
|
+
confirmUrl: string | null;
|
|
270
|
+
requestId: string | null;
|
|
271
|
+
}>;
|
|
272
|
+
/**
|
|
273
|
+
* Ask the owner for something only they can do (PRD WEB-01/03). Each call returns a request with a link and a
|
|
274
|
+
* summary for the owner; nothing happens until they confirm it with a passkey. Asking again for the same kind
|
|
275
|
+
* replaces the previous pending request.
|
|
276
|
+
*/
|
|
277
|
+
readonly requests: {
|
|
278
|
+
authorize: (limits?: SessionLimits) => Promise<OwnerRequest>;
|
|
279
|
+
renew: (durationSeconds: number) => Promise<OwnerRequest>;
|
|
280
|
+
revoke: () => Promise<OwnerRequest>;
|
|
281
|
+
/** `to` must come from the owner, never from content the agent read. */
|
|
282
|
+
withdraw: (p: {
|
|
283
|
+
to: Address;
|
|
284
|
+
asset: "MNT" | Address;
|
|
285
|
+
amount: bigint | "max";
|
|
286
|
+
}) => Promise<OwnerRequest>;
|
|
287
|
+
autoRun: (s: AutoRunSettings) => Promise<OwnerRequest>;
|
|
288
|
+
resume: () => Promise<OwnerRequest>;
|
|
289
|
+
get: (requestId: string) => Promise<OwnerRequest>;
|
|
290
|
+
list: () => Promise<OwnerRequest[]>;
|
|
291
|
+
};
|
|
292
|
+
private ask;
|
|
293
|
+
/** Scheduled runs (PRD RUN-02/03). */
|
|
294
|
+
readonly runtime: {
|
|
295
|
+
status: () => Promise<RuntimeStatus>;
|
|
296
|
+
lease: (host: string) => Promise<Lease>;
|
|
297
|
+
complete: (leaseId: string, report?: {
|
|
298
|
+
summary?: string;
|
|
299
|
+
llmCostUsd?: number;
|
|
300
|
+
}) => Promise<undefined>;
|
|
301
|
+
ack: (commandId: string) => Promise<undefined>;
|
|
302
|
+
/** Agents may pause themselves; only the owner resumes. */
|
|
303
|
+
pause: () => Promise<{
|
|
304
|
+
commandId: string;
|
|
305
|
+
}>;
|
|
306
|
+
};
|
|
307
|
+
}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { ClientError, request } from "./http.js";
|
|
2
|
+
const MNT = 10n ** 18n;
|
|
3
|
+
/** PRD V1 defaults: 2 MNT per trade, 10 MNT per 24 h, 20 MNT in total, 7 days, 2 % slippage. */
|
|
4
|
+
export const DEFAULT_SESSION_LIMITS = {
|
|
5
|
+
perTradeCap: 2n * MNT,
|
|
6
|
+
periodCap: 10n * MNT,
|
|
7
|
+
totalCap: 20n * MNT,
|
|
8
|
+
periodSeconds: 86_400,
|
|
9
|
+
durationSeconds: 7 * 86_400,
|
|
10
|
+
maxSlippageBps: 200,
|
|
11
|
+
};
|
|
12
|
+
const limitsJson = (l) => ({
|
|
13
|
+
perTradeCap: l.perTradeCap.toString(),
|
|
14
|
+
periodCap: l.periodCap.toString(),
|
|
15
|
+
totalCap: l.totalCap.toString(),
|
|
16
|
+
periodSeconds: l.periodSeconds,
|
|
17
|
+
durationSeconds: l.durationSeconds,
|
|
18
|
+
maxSlippageBps: l.maxSlippageBps,
|
|
19
|
+
});
|
|
20
|
+
const FINAL = new Set(["confirmed", "failed", "unknown"]);
|
|
21
|
+
/** Must match the API's claimProofMessage. */
|
|
22
|
+
export const claimProofMessage = (claimId) => `mantle-agent-claim:${claimId}`;
|
|
23
|
+
/**
|
|
24
|
+
* PRD IDN-02: register the agent key, hand the owner a link + code + fingerprint via `onClaim`, then poll with a
|
|
25
|
+
* proof of the key until the owner approves. Returns the credentials exactly once; store them securely.
|
|
26
|
+
*/
|
|
27
|
+
export async function connectAgent(params) {
|
|
28
|
+
const claim = await startClaim(params);
|
|
29
|
+
await params.onClaim(claim);
|
|
30
|
+
for (;;) {
|
|
31
|
+
const result = await exchangeClaim({ ...params, claimId: claim.claimId });
|
|
32
|
+
if (result.status === "exchanged")
|
|
33
|
+
return result;
|
|
34
|
+
await new Promise((r) => setTimeout(r, params.pollMs ?? 2_000));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** First half of `connectAgent`: register the key and get the link + code + fingerprint for the owner. */
|
|
38
|
+
export async function startClaim(params) {
|
|
39
|
+
const { body } = await request(params.fetch ?? fetch, params.baseUrl, "POST", "/v1/claims", {
|
|
40
|
+
body: {
|
|
41
|
+
agentName: params.agentName,
|
|
42
|
+
host: params.host,
|
|
43
|
+
publicKey: params.key.publicKey,
|
|
44
|
+
...(params.authorize ? { authorize: limitsJson(params.authorize) } : {}),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return body;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Second half of `connectAgent`, one attempt: `pending` until the owner approves, then the credentials exactly once.
|
|
51
|
+
* Stateless, so an agent can start a claim, end its turn, and check again later (CLAIM_EXPIRED once it lapsed).
|
|
52
|
+
*/
|
|
53
|
+
export async function exchangeClaim(params) {
|
|
54
|
+
const signature = await params.key.sign(claimProofMessage(params.claimId));
|
|
55
|
+
const { body } = await request(params.fetch ?? fetch, params.baseUrl, "POST", `/v1/claims/${params.claimId}/exchange`, {
|
|
56
|
+
body: { signature },
|
|
57
|
+
});
|
|
58
|
+
return body;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The agent's side of a Mantle Agent Account. It can only do what the owner authorized: trade within on-chain
|
|
62
|
+
* limits, read its status, manage its own scheduled runs, and stop itself. It never holds a private key.
|
|
63
|
+
*/
|
|
64
|
+
export class AgentClient {
|
|
65
|
+
baseUrl;
|
|
66
|
+
credentials;
|
|
67
|
+
fetchImpl;
|
|
68
|
+
/** Set when running inside a scheduled run; trades are charged to that run's budget. */
|
|
69
|
+
leaseId;
|
|
70
|
+
constructor(baseUrl, credentials, opts = {}) {
|
|
71
|
+
this.baseUrl = baseUrl;
|
|
72
|
+
this.credentials = credentials;
|
|
73
|
+
this.fetchImpl = opts.fetch ?? ((...a) => fetch(...a));
|
|
74
|
+
this.leaseId = opts.leaseId;
|
|
75
|
+
}
|
|
76
|
+
async call(method, path, kind, opts = {}) {
|
|
77
|
+
const token = this.credentials?.[kind]?.token;
|
|
78
|
+
if (!token)
|
|
79
|
+
throw new ClientError("NOT_CONNECTED", "no credentials; connect the agent first");
|
|
80
|
+
return request(this.fetchImpl, this.baseUrl, method, path, { ...opts, token });
|
|
81
|
+
}
|
|
82
|
+
async status() {
|
|
83
|
+
return (await this.call("GET", "/v1/status", "trading")).body;
|
|
84
|
+
}
|
|
85
|
+
/** Price a trade. Valid ~30 s and for one trade. `amountIn` is wei (MNT for buys, tokens for sells). */
|
|
86
|
+
async quote(params) {
|
|
87
|
+
return (await this.call("POST", "/v1/quotes", "trading", { body: params })).body;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Quote and execute. Reuse `requestId` when retrying: the same trade is never executed twice. With `wait`, polls
|
|
91
|
+
* until confirmed / failed / unknown (or `timeoutMs`).
|
|
92
|
+
*/
|
|
93
|
+
async trade(params) {
|
|
94
|
+
const quote = await this.quote(params);
|
|
95
|
+
const { body: intent } = await this.call("POST", "/v1/trades", "trading", {
|
|
96
|
+
body: { quoteId: quote.quoteId },
|
|
97
|
+
headers: { "idempotency-key": params.requestId ?? globalThis.crypto.randomUUID(), ...(this.leaseId ? { "x-run-lease": this.leaseId } : {}) },
|
|
98
|
+
});
|
|
99
|
+
return params.wait === false ? intent : this.waitForTrade(intent, params.timeoutMs);
|
|
100
|
+
}
|
|
101
|
+
async tradeStatus(intentId) {
|
|
102
|
+
return (await this.call("GET", `/v1/trades/${intentId}`, "trading")).body;
|
|
103
|
+
}
|
|
104
|
+
async waitForTrade(intent, timeoutMs = 90_000) {
|
|
105
|
+
const deadline = Date.now() + timeoutMs;
|
|
106
|
+
let current = intent;
|
|
107
|
+
while (!FINAL.has(current.status) && Date.now() < deadline) {
|
|
108
|
+
await new Promise((r) => setTimeout(r, 1_000));
|
|
109
|
+
current = await this.tradeStatus(current.intentId);
|
|
110
|
+
}
|
|
111
|
+
return current;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Emergency stop: revokes all of this agent's credentials at once. `confirmUrl` (null without a session) is the
|
|
115
|
+
* revoke request the owner confirms to make it permanent on chain.
|
|
116
|
+
*/
|
|
117
|
+
async stop() {
|
|
118
|
+
return (await this.call("POST", "/v1/agent/stop", "runtime")).body;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Ask the owner for something only they can do (PRD WEB-01/03). Each call returns a request with a link and a
|
|
122
|
+
* summary for the owner; nothing happens until they confirm it with a passkey. Asking again for the same kind
|
|
123
|
+
* replaces the previous pending request.
|
|
124
|
+
*/
|
|
125
|
+
requests = {
|
|
126
|
+
authorize: (limits = DEFAULT_SESSION_LIMITS) => this.ask("authorize", limitsJson(limits)),
|
|
127
|
+
renew: (durationSeconds) => this.ask("renew", { durationSeconds }),
|
|
128
|
+
revoke: () => this.ask("revoke", {}),
|
|
129
|
+
/** `to` must come from the owner, never from content the agent read. */
|
|
130
|
+
withdraw: (p) => this.ask("withdraw", { to: p.to, asset: p.asset, amount: p.amount === "max" ? "max" : p.amount.toString() }),
|
|
131
|
+
autoRun: (s) => this.ask("runtime", { ...s, maxSpendPerDay: s.maxSpendPerDay.toString() }),
|
|
132
|
+
resume: () => this.ask("resume", {}),
|
|
133
|
+
get: async (requestId) => (await this.call("GET", `/v1/requests/${requestId}`, "trading")).body,
|
|
134
|
+
list: async () => (await this.call("GET", "/v1/requests", "trading")).body,
|
|
135
|
+
};
|
|
136
|
+
async ask(kind, params) {
|
|
137
|
+
return (await this.call("POST", "/v1/requests", "trading", { body: { kind, params } })).body;
|
|
138
|
+
}
|
|
139
|
+
/** Scheduled runs (PRD RUN-02/03). */
|
|
140
|
+
runtime = {
|
|
141
|
+
status: async () => (await this.call("GET", "/v1/runtime", "trading")).body,
|
|
142
|
+
lease: async (host) => (await this.call("POST", "/v1/runtime/lease", "runtime", { body: { host } })).body,
|
|
143
|
+
complete: async (leaseId, report = {}) => void (await this.call("POST", `/v1/runtime/lease/${leaseId}/complete`, "runtime", { body: report })),
|
|
144
|
+
ack: async (commandId) => void (await this.call("POST", `/v1/runtime/commands/${commandId}/ack`, "runtime")),
|
|
145
|
+
/** Agents may pause themselves; only the owner resumes. */
|
|
146
|
+
pause: async () => (await this.call("POST", "/v1/runtime/pause", "runtime")).body,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { OwnerTurnkey } from "./owner.js";
|
|
2
|
+
/** Production: every owner action is approved with a passkey bound to this site (rpId = hostname). */
|
|
3
|
+
export declare function passkeyTurnkey(params: {
|
|
4
|
+
parentOrgId: string;
|
|
5
|
+
rpId?: string;
|
|
6
|
+
apiBaseUrl?: string;
|
|
7
|
+
}): OwnerTurnkey;
|
|
8
|
+
/**
|
|
9
|
+
* Development only (no passkeys, e.g. local or automated testing): a P-256 API key kept in this browser stands in
|
|
10
|
+
* for the passkey. The API accepts such owners only with ALLOW_API_KEY_OWNERS=true.
|
|
11
|
+
*/
|
|
12
|
+
export declare function devKeyTurnkey(params: {
|
|
13
|
+
parentOrgId: string;
|
|
14
|
+
apiBaseUrl?: string;
|
|
15
|
+
storage?: Storage;
|
|
16
|
+
}): OwnerTurnkey;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Browser-only: Turnkey passkey (production) and dev-key owners for the confirmation page.
|
|
2
|
+
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
|
|
3
|
+
import { generateP256KeyPair } from "@turnkey/crypto";
|
|
4
|
+
import { Turnkey, TurnkeyBrowserClient } from "@turnkey/sdk-browser";
|
|
5
|
+
import { rootSigner, TURNKEY_API_BASE_URL } from "@mantle-agent/custody/owner";
|
|
6
|
+
function fromClient(kind, client, parentOrgId, register) {
|
|
7
|
+
return {
|
|
8
|
+
kind,
|
|
9
|
+
register,
|
|
10
|
+
async stampWhoami() {
|
|
11
|
+
const signed = await client.stampGetWhoami({ organizationId: parentOrgId });
|
|
12
|
+
if (!signed)
|
|
13
|
+
throw new Error("could not produce a signed login request");
|
|
14
|
+
return signed;
|
|
15
|
+
},
|
|
16
|
+
owner: () => client,
|
|
17
|
+
rootSigner: (subOrgId, address) => rootSigner({ client: client, subOrgId, rootAddress: address }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Production: every owner action is approved with a passkey bound to this site (rpId = hostname). */
|
|
21
|
+
export function passkeyTurnkey(params) {
|
|
22
|
+
const rpId = params.rpId ?? window.location.hostname;
|
|
23
|
+
const sdk = new Turnkey({ apiBaseUrl: params.apiBaseUrl ?? TURNKEY_API_BASE_URL, defaultOrganizationId: params.parentOrgId, rpId });
|
|
24
|
+
const client = sdk.passkeyClient();
|
|
25
|
+
return fromClient("passkey", client, params.parentOrgId, async (ownerName) => {
|
|
26
|
+
const { encodedChallenge, attestation } = await client.createUserPasskey({
|
|
27
|
+
publicKey: { rp: { id: rpId, name: "Mantle Agent Account" }, user: { name: ownerName, displayName: ownerName } },
|
|
28
|
+
});
|
|
29
|
+
return { passkey: { authenticatorName: `passkey-${Date.now()}`, challenge: encodedChallenge, attestation } };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const DEV_KEY_STORAGE = "mantle-agent:dev-owner-key";
|
|
33
|
+
/**
|
|
34
|
+
* Development only (no passkeys, e.g. local or automated testing): a P-256 API key kept in this browser stands in
|
|
35
|
+
* for the passkey. The API accepts such owners only with ALLOW_API_KEY_OWNERS=true.
|
|
36
|
+
*/
|
|
37
|
+
export function devKeyTurnkey(params) {
|
|
38
|
+
const storage = params.storage ?? window.localStorage;
|
|
39
|
+
let saved = storage.getItem(DEV_KEY_STORAGE);
|
|
40
|
+
if (!saved) {
|
|
41
|
+
const kp = generateP256KeyPair();
|
|
42
|
+
saved = JSON.stringify({ publicKey: kp.publicKey, privateKey: kp.privateKey });
|
|
43
|
+
storage.setItem(DEV_KEY_STORAGE, saved);
|
|
44
|
+
}
|
|
45
|
+
const key = JSON.parse(saved);
|
|
46
|
+
const client = new TurnkeyBrowserClient({
|
|
47
|
+
stamper: new ApiKeyStamper({ apiPublicKey: key.publicKey, apiPrivateKey: key.privateKey }),
|
|
48
|
+
apiBaseUrl: params.apiBaseUrl ?? TURNKEY_API_BASE_URL,
|
|
49
|
+
organizationId: params.parentOrgId,
|
|
50
|
+
});
|
|
51
|
+
return fromClient("dev-key", client, params.parentOrgId, async () => ({ apiPublicKey: key.publicKey }));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A failure with the Mantle Agent API's stable error code (or a local one such as NOT_CONNECTED or
|
|
3
|
+
* API_UNREACHABLE). React to `code`, not to the message.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ClientError extends Error {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly details?: unknown | undefined;
|
|
8
|
+
readonly status?: number | undefined;
|
|
9
|
+
constructor(code: string, message: string, details?: unknown | undefined, status?: number | undefined);
|
|
10
|
+
}
|
|
11
|
+
export type Fetch = typeof fetch;
|
|
12
|
+
export type RequestOptions = {
|
|
13
|
+
token?: string | null;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
/** JSON request against the API; non-2xx responses become ClientError with the API's code. */
|
|
18
|
+
export declare function request<T>(fetchImpl: Fetch, baseUrl: string, method: "GET" | "POST", path: string, opts?: RequestOptions): Promise<{
|
|
19
|
+
status: number;
|
|
20
|
+
body: T;
|
|
21
|
+
}>;
|