@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/src/owner.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { formatEther, type Address, type Hex, type SignedAuthorization } from "viem";
|
|
2
|
+
import {
|
|
3
|
+
type RootSigner,
|
|
4
|
+
callFromJson,
|
|
5
|
+
sign7702Authorization,
|
|
6
|
+
signedBatchedCallTypedData,
|
|
7
|
+
transactionFromJson,
|
|
8
|
+
type CallJson,
|
|
9
|
+
type TransactionJson,
|
|
10
|
+
} from "@mantle-agent/core";
|
|
11
|
+
import {
|
|
12
|
+
addPlatformDelegate,
|
|
13
|
+
createAgentWallet,
|
|
14
|
+
type OwnerApi,
|
|
15
|
+
type PolicyIntent,
|
|
16
|
+
type SignedTurnkeyRequest,
|
|
17
|
+
} from "@mantle-agent/custody/owner";
|
|
18
|
+
import type { OwnerRequest, RuntimeStatus } from "./agent.js";
|
|
19
|
+
|
|
20
|
+
export type { OwnerRequest, OwnerRequestKind, RuntimeStatus } from "./agent.js";
|
|
21
|
+
import { request, type Fetch } from "./http.js";
|
|
22
|
+
|
|
23
|
+
export { ClientError } from "./http.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Everything the owner does with Turnkey, on their own device and with their own credential (passkey in
|
|
27
|
+
* production). The platform is never involved in these calls. See `@mantle-agent/client/browser` for the passkey
|
|
28
|
+
* implementation.
|
|
29
|
+
*/
|
|
30
|
+
export interface OwnerTurnkey {
|
|
31
|
+
readonly kind: "passkey" | "dev-key";
|
|
32
|
+
/** Creates the owner's credential for signup (passkey prompt in production). */
|
|
33
|
+
register(ownerName: string): Promise<SignupCredential>;
|
|
34
|
+
/** A whoami stamped against the parent org, so login works from any device. */
|
|
35
|
+
stampWhoami(): Promise<SignedTurnkeyRequest>;
|
|
36
|
+
/** The owner acting inside their sub-org (wallets, users, policies). */
|
|
37
|
+
owner(subOrgId: string): OwnerApi;
|
|
38
|
+
/** The agent EOA's root key, for the owner's signatures. */
|
|
39
|
+
rootSigner(subOrgId: string, address: Address): RootSigner;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What signup sends to the API: a passkey attestation, or (dev only) an API public key. */
|
|
43
|
+
export type SignupCredential =
|
|
44
|
+
| { passkey: { authenticatorName: string; challenge: string; attestation: unknown } }
|
|
45
|
+
| { apiPublicKey: string };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The owner's side of the Mantle Agent API (the confirmation page backend). Holds the owner session once logged
|
|
49
|
+
* in; pass it to the flow functions below.
|
|
50
|
+
*/
|
|
51
|
+
export class OwnerClient {
|
|
52
|
+
token: string | null = null;
|
|
53
|
+
private readonly fetchImpl: Fetch;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
readonly baseUrl: string,
|
|
57
|
+
fetchImpl?: Fetch,
|
|
58
|
+
) {
|
|
59
|
+
this.fetchImpl = fetchImpl ?? ((...a) => fetch(...a));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async call<T>(method: "GET" | "POST", path: string, body?: unknown): Promise<T> {
|
|
63
|
+
return (await request<T>(this.fetchImpl, this.baseUrl, method, path, { token: this.token, body })).body;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export type Platform = {
|
|
69
|
+
chainId: number;
|
|
70
|
+
caliburEntry: Address;
|
|
71
|
+
hook: Address;
|
|
72
|
+
targets: Address[];
|
|
73
|
+
executor: Address;
|
|
74
|
+
platformApiPublicKey: string;
|
|
75
|
+
turnkeyOrganizationId: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type Owner = { ownerId: string; subOrgId: string; turnkeyUserId: string; daUserId: string | null; custodyVerified: boolean };
|
|
79
|
+
export type Session = { token: string; expiresAt: string; owner: Owner };
|
|
80
|
+
|
|
81
|
+
export const platform = (api: OwnerClient) => api.call<Platform>("GET", "/v1/platform");
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
84
|
+
// Sign up / log in / custody
|
|
85
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
export async function logIn(api: OwnerClient, tk: OwnerTurnkey): Promise<Session> {
|
|
88
|
+
const session = await api.call<Session>("POST", "/v1/owner/login", await tk.stampWhoami());
|
|
89
|
+
api.token = session.token;
|
|
90
|
+
return session;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** New owner: create the credential (passkey prompt), let the API create the owner's Turnkey space, log in. */
|
|
94
|
+
export async function signUp(api: OwnerClient, tk: OwnerTurnkey, ownerName: string): Promise<Session> {
|
|
95
|
+
const credential = await tk.register(ownerName);
|
|
96
|
+
await api.call("POST", "/v1/owner/signup", { ownerName, ...credential });
|
|
97
|
+
return logIn(api, tk);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One-time custody setup from the owner's own session: add the platform as a non-root user that can only sign
|
|
102
|
+
* what later policies allow, then have the API verify it with Turnkey.
|
|
103
|
+
*/
|
|
104
|
+
export async function ensureCustody(api: OwnerClient, tk: OwnerTurnkey, session: Session): Promise<Session> {
|
|
105
|
+
if (session.owner.custodyVerified) return session;
|
|
106
|
+
const { platformApiPublicKey } = await platform(api);
|
|
107
|
+
const { daUserId } = await addPlatformDelegate(tk.owner(session.owner.subOrgId), {
|
|
108
|
+
subOrgId: session.owner.subOrgId,
|
|
109
|
+
platformApiPublicKey,
|
|
110
|
+
});
|
|
111
|
+
const owner = await api.call<Owner>("POST", "/v1/owner/custody", { daUserId });
|
|
112
|
+
return { ...session, owner };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
116
|
+
// Claims and agents
|
|
117
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
export type PendingClaim = {
|
|
120
|
+
claimId: string;
|
|
121
|
+
userCode: string;
|
|
122
|
+
agentName: string;
|
|
123
|
+
host: string;
|
|
124
|
+
fingerprint: string;
|
|
125
|
+
expiresAt: string;
|
|
126
|
+
/** Limits the agent proposed; after approval they come back as an authorize request (`requestId`). */
|
|
127
|
+
proposal: string[] | null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const lookupClaim = (api: OwnerClient, code: string) =>
|
|
131
|
+
api.call<PendingClaim>("GET", `/v1/owner/claims/${encodeURIComponent(code.trim().toUpperCase())}`);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The owner checked the fingerprint against the CLI's: create the agent's own wallet in their space and approve.
|
|
135
|
+
* The API reads the wallet back from Turnkey before accepting it.
|
|
136
|
+
*/
|
|
137
|
+
export async function approveClaim(
|
|
138
|
+
api: OwnerClient,
|
|
139
|
+
tk: OwnerTurnkey,
|
|
140
|
+
session: Session,
|
|
141
|
+
claim: PendingClaim,
|
|
142
|
+
): Promise<{ agentId: string; requestId: string | null }> {
|
|
143
|
+
const wallet = await createAgentWallet(tk.owner(session.owner.subOrgId), {
|
|
144
|
+
subOrgId: session.owner.subOrgId,
|
|
145
|
+
agentId: claim.claimId,
|
|
146
|
+
});
|
|
147
|
+
return api.call("POST", `/v1/owner/claims/${claim.claimId}/approve`, { walletId: wallet.walletId, fingerprint: claim.fingerprint });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
151
|
+
// Owner requests: the agent asks in chat, the owner confirms here (PRD WEB-01/03)
|
|
152
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
export const getRequest = (api: OwnerClient, requestId: string) => api.call<OwnerRequest>("GET", `/v1/owner/requests/${requestId}`);
|
|
155
|
+
export const pendingRequests = (api: OwnerClient) => api.call<OwnerRequest[]>("GET", "/v1/owner/requests");
|
|
156
|
+
export const rejectRequest = (api: OwnerClient, requestId: string) => api.call<OwnerRequest>("POST", `/v1/owner/requests/${requestId}/reject`);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The owner confirmed the summary. Signing kinds return the operation to run with `runOperation` (the same one
|
|
160
|
+
* again if the page was reloaded before it finished); auto-run kinds take effect right away (`prepared: null`).
|
|
161
|
+
*/
|
|
162
|
+
export const approveRequest = (api: OwnerClient, requestId: string) =>
|
|
163
|
+
api.call<{ request: OwnerRequest; prepared: PreparedOperation | null }>("POST", `/v1/owner/requests/${requestId}/approve`);
|
|
164
|
+
|
|
165
|
+
export type AgentSummary = {
|
|
166
|
+
agent_id: string;
|
|
167
|
+
name: string;
|
|
168
|
+
status: "pending" | "active" | "stopped";
|
|
169
|
+
account: Address | null;
|
|
170
|
+
balanceWei: string | null;
|
|
171
|
+
};
|
|
172
|
+
export const listAgents = (api: OwnerClient) => api.call<AgentSummary[]>("GET", "/v1/owner/agents");
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
175
|
+
// Owner operations (authorize / renew / revoke)
|
|
176
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
/** PRD WAL-02 step 2 inputs for UIs that start an authorization themselves (MNT amounts in whole units). */
|
|
179
|
+
export type AuthorizeForm = {
|
|
180
|
+
perTradeMnt: string;
|
|
181
|
+
periodMnt: string;
|
|
182
|
+
totalMnt: string;
|
|
183
|
+
periodHours: number;
|
|
184
|
+
durationDays: number;
|
|
185
|
+
maxSlippagePercent: number;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const DEFAULT_LIMITS: AuthorizeForm = {
|
|
189
|
+
perTradeMnt: "2",
|
|
190
|
+
periodMnt: "10",
|
|
191
|
+
totalMnt: "20",
|
|
192
|
+
periodHours: 24,
|
|
193
|
+
durationDays: 7,
|
|
194
|
+
maxSlippagePercent: 2,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const wei = (mnt: string) => {
|
|
198
|
+
if (!/^\d+(\.\d{1,18})?$/.test(mnt)) throw new Error(`invalid MNT amount: ${mnt}`);
|
|
199
|
+
const [whole, frac = ""] = mnt.split(".");
|
|
200
|
+
return (BigInt(whole!) * 10n ** 18n + BigInt(frac.padEnd(18, "0"))).toString();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export type PreparedOperation = {
|
|
204
|
+
operationId: string;
|
|
205
|
+
kind: "authorize" | "renew" | "revoke" | "withdraw";
|
|
206
|
+
status: "prepared" | "submitted" | "confirmed" | "failed";
|
|
207
|
+
account: Address;
|
|
208
|
+
chainId: number;
|
|
209
|
+
call?: CallJson;
|
|
210
|
+
/** withdraw: a plain transaction from the agent account, paid by it. */
|
|
211
|
+
transaction?: TransactionJson;
|
|
212
|
+
authorization?: { contractAddress: Address; chainId: number; nonce: number };
|
|
213
|
+
turnkeyPolicy?: PolicyIntent;
|
|
214
|
+
retirePolicyId?: string | null;
|
|
215
|
+
error: string | null;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const prepareAuthorize = (api: OwnerClient, agentId: string, f: AuthorizeForm) =>
|
|
219
|
+
api.call<PreparedOperation>("POST", `/v1/owner/agents/${agentId}/authorize`, {
|
|
220
|
+
perTradeCap: wei(f.perTradeMnt),
|
|
221
|
+
periodCap: wei(f.periodMnt),
|
|
222
|
+
totalCap: wei(f.totalMnt),
|
|
223
|
+
periodSeconds: Math.round(f.periodHours * 3600),
|
|
224
|
+
durationSeconds: Math.round(f.durationDays * 86_400),
|
|
225
|
+
maxSlippageBps: Math.round(f.maxSlippagePercent * 100),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
export const prepareRenew = (api: OwnerClient, agentId: string, durationDays: number) =>
|
|
229
|
+
api.call<PreparedOperation>("POST", `/v1/owner/agents/${agentId}/renew`, { durationSeconds: Math.round(durationDays * 86_400) });
|
|
230
|
+
|
|
231
|
+
export const prepareRevoke = (api: OwnerClient, agentId: string) =>
|
|
232
|
+
api.call<PreparedOperation>("POST", `/v1/owner/agents/${agentId}/revoke`);
|
|
233
|
+
|
|
234
|
+
/** PRD WAL-06: MNT or a token, an exact amount (whole units, 18 decimals) or everything. */
|
|
235
|
+
export const prepareWithdraw = (api: OwnerClient, agentId: string, p: { to: Address; asset: "MNT" | Address; amount: string | "max" }) =>
|
|
236
|
+
api.call<PreparedOperation>("POST", `/v1/owner/agents/${agentId}/withdraw`, {
|
|
237
|
+
to: p.to,
|
|
238
|
+
asset: p.asset,
|
|
239
|
+
amount: p.amount === "max" ? "max" : wei(p.amount),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
/** Emergency stop: everything server-side now, plus the on-chain revoke to sign (null if no session). */
|
|
243
|
+
export const emergencyStop = (api: OwnerClient, agentId: string) =>
|
|
244
|
+
api.call<{ stopped: true; revokedCredentials: number; revoke: PreparedOperation | null }>("POST", `/v1/owner/agents/${agentId}/stop`);
|
|
245
|
+
|
|
246
|
+
export type StepName = "policy" | "authorization" | "signature" | "transaction" | "submit" | "confirm" | "cleanup";
|
|
247
|
+
export type StepState = { name: StepName; status: "todo" | "running" | "done" | "failed" | "skipped"; error?: string };
|
|
248
|
+
|
|
249
|
+
/** Progress of one operation; pass it back to `runOperation` to retry from the step that failed (PRD WEB-01). */
|
|
250
|
+
export type OperationRun = {
|
|
251
|
+
prepared: PreparedOperation;
|
|
252
|
+
steps: StepState[];
|
|
253
|
+
policyId?: string;
|
|
254
|
+
authorization?: SignedAuthorization;
|
|
255
|
+
signature?: Hex;
|
|
256
|
+
signedTransaction?: Hex;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export function startRun(prepared: PreparedOperation): OperationRun {
|
|
260
|
+
if (prepared.transaction) {
|
|
261
|
+
return {
|
|
262
|
+
prepared,
|
|
263
|
+
steps: [
|
|
264
|
+
{ name: "transaction", status: "todo" },
|
|
265
|
+
{ name: "submit", status: "todo" },
|
|
266
|
+
{ name: "confirm", status: "todo" },
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const steps: StepState[] = [
|
|
271
|
+
{ name: "policy", status: prepared.turnkeyPolicy ? "todo" : "skipped" },
|
|
272
|
+
{ name: "authorization", status: prepared.authorization ? "todo" : "skipped" },
|
|
273
|
+
{ name: "signature", status: "todo" },
|
|
274
|
+
{ name: "submit", status: "todo" },
|
|
275
|
+
{ name: "confirm", status: "todo" },
|
|
276
|
+
{ name: "cleanup", status: prepared.retirePolicyId ? "todo" : "skipped" },
|
|
277
|
+
];
|
|
278
|
+
return { prepared, steps };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Executes the owner's part of an operation step by step. Completed steps are never repeated, so a retry after a
|
|
283
|
+
* failure does not create a second Turnkey policy or a second signature.
|
|
284
|
+
*/
|
|
285
|
+
export async function runOperation(
|
|
286
|
+
api: OwnerClient,
|
|
287
|
+
tk: OwnerTurnkey,
|
|
288
|
+
session: Session,
|
|
289
|
+
run: OperationRun,
|
|
290
|
+
opts: { onChange?: (run: OperationRun) => void; pollMs?: number; timeoutMs?: number } = {},
|
|
291
|
+
): Promise<OperationRun> {
|
|
292
|
+
const { prepared } = run;
|
|
293
|
+
const subOrgId = session.owner.subOrgId;
|
|
294
|
+
const root = () => tk.rootSigner(subOrgId, prepared.account);
|
|
295
|
+
const owner = tk.owner(subOrgId);
|
|
296
|
+
let state = { ...run, steps: run.steps.map((s) => ({ ...s })) };
|
|
297
|
+
const set = (name: StepName, patch: Partial<StepState>) => {
|
|
298
|
+
state = { ...state, steps: state.steps.map((s) => (s.name === name ? { ...s, ...patch } : s)) };
|
|
299
|
+
opts.onChange?.(state);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const actions: Record<StepName, () => Promise<void>> = {
|
|
303
|
+
policy: async () => {
|
|
304
|
+
const { policyIds } = await owner.createPolicies({ organizationId: subOrgId, policies: [prepared.turnkeyPolicy!] });
|
|
305
|
+
state = { ...state, policyId: policyIds[0]! };
|
|
306
|
+
},
|
|
307
|
+
authorization: async () => {
|
|
308
|
+
const a = prepared.authorization!;
|
|
309
|
+
state = { ...state, authorization: await sign7702Authorization(root(), { chainId: a.chainId, nonce: a.nonce }) };
|
|
310
|
+
},
|
|
311
|
+
signature: async () => {
|
|
312
|
+
const typed = signedBatchedCallTypedData({ account: prepared.account, chainId: prepared.chainId }, callFromJson(prepared.call!));
|
|
313
|
+
state = { ...state, signature: await root().signTypedData(typed) };
|
|
314
|
+
},
|
|
315
|
+
transaction: async () => {
|
|
316
|
+
state = { ...state, signedTransaction: await root().signTransaction(transactionFromJson(prepared.transaction!)) };
|
|
317
|
+
},
|
|
318
|
+
submit: async () => {
|
|
319
|
+
if (state.signedTransaction) {
|
|
320
|
+
await api.call("POST", `/v1/owner/operations/${prepared.operationId}/submit`, { signedTransaction: state.signedTransaction });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const a = state.authorization;
|
|
324
|
+
await api.call("POST", `/v1/owner/operations/${prepared.operationId}/submit`, {
|
|
325
|
+
signature: state.signature,
|
|
326
|
+
...(state.policyId ? { policyId: state.policyId } : {}),
|
|
327
|
+
...(a ? { authorization: { address: a.address, chainId: a.chainId, nonce: a.nonce, r: a.r, s: a.s, yParity: a.yParity } } : {}),
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
confirm: async () => {
|
|
331
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 120_000);
|
|
332
|
+
for (;;) {
|
|
333
|
+
const op = await api.call<PreparedOperation>("GET", `/v1/owner/operations/${prepared.operationId}`);
|
|
334
|
+
if (op.status === "confirmed") return;
|
|
335
|
+
if (op.status === "failed") throw new Error(op.error ?? "operation failed on chain");
|
|
336
|
+
if (Date.now() > deadline) throw new Error("still waiting for the chain; check again shortly");
|
|
337
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 1_500));
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
// Renew: drop the policy the new one replaced. Revoke: drop the session policy so Turnkey stops signing too.
|
|
341
|
+
cleanup: async () => {
|
|
342
|
+
await owner.deletePolicies({ organizationId: subOrgId, policyIds: [prepared.retirePolicyId!] });
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
for (const step of state.steps) {
|
|
347
|
+
if (step.status === "done" || step.status === "skipped") continue;
|
|
348
|
+
set(step.name, { status: "running", error: undefined });
|
|
349
|
+
try {
|
|
350
|
+
await actions[step.name]();
|
|
351
|
+
set(step.name, { status: "done" });
|
|
352
|
+
} catch (err) {
|
|
353
|
+
set(step.name, { status: "failed", error: err instanceof Error ? err.message : String(err) });
|
|
354
|
+
return state;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return state;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export const runSucceeded = (run: OperationRun) => run.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
361
|
+
|
|
362
|
+
/** Human summary of what the owner is about to sign (PRD WAL-02 step 4: show before confirming). */
|
|
363
|
+
export function describeLimits(f: AuthorizeForm): string[] {
|
|
364
|
+
return [
|
|
365
|
+
`每笔最多 ${f.perTradeMnt} MNT`,
|
|
366
|
+
`每 ${f.periodHours} 小时最多 ${f.periodMnt} MNT`,
|
|
367
|
+
`总共最多 ${f.totalMnt} MNT(卖出不会恢复额度)`,
|
|
368
|
+
`有效期 ${f.durationDays} 天`,
|
|
369
|
+
`最大滑点 ${f.maxSlippagePercent}%`,
|
|
370
|
+
"只能买卖白名单协议上的代币,只能卖出本次授权期间买入的代币",
|
|
371
|
+
"不能提现、不能修改权限",
|
|
372
|
+
];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export const formatMnt = (weiValue: string | bigint) => `${formatEther(BigInt(weiValue))} MNT`;
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
378
|
+
// Auto-run (PRD RUN-02/03)
|
|
379
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
/** Wizard step 3 inputs for scheduled runs. */
|
|
382
|
+
export type RuntimeForm = {
|
|
383
|
+
enabled: boolean;
|
|
384
|
+
intervalHours: number;
|
|
385
|
+
maxTradesPerRun: number;
|
|
386
|
+
maxTradesPerDay: number;
|
|
387
|
+
maxSpendPerDayMnt: string;
|
|
388
|
+
maxLlmCostPerDayUsd: number;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const DEFAULT_RUNTIME_FORM: RuntimeForm = {
|
|
392
|
+
enabled: false,
|
|
393
|
+
intervalHours: 1,
|
|
394
|
+
maxTradesPerRun: 2,
|
|
395
|
+
maxTradesPerDay: 10,
|
|
396
|
+
maxSpendPerDayMnt: "5",
|
|
397
|
+
maxLlmCostPerDayUsd: 5,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
export const getRuntime = (api: OwnerClient, agentId: string) => api.call<RuntimeStatus>("GET", `/v1/owner/agents/${agentId}/runtime`);
|
|
401
|
+
|
|
402
|
+
export const configureRuntime = (api: OwnerClient, agentId: string, f: RuntimeForm) =>
|
|
403
|
+
api.call<RuntimeStatus>("POST", `/v1/owner/agents/${agentId}/runtime`, {
|
|
404
|
+
enabled: f.enabled,
|
|
405
|
+
intervalSeconds: Math.round(f.intervalHours * 3600),
|
|
406
|
+
maxRunSeconds: Math.min(600, Math.round(f.intervalHours * 3600)),
|
|
407
|
+
maxTradesPerRun: f.maxTradesPerRun,
|
|
408
|
+
maxTradesPerDay: f.maxTradesPerDay,
|
|
409
|
+
maxSpendPerDay: wei(f.maxSpendPerDayMnt),
|
|
410
|
+
maxLlmCostPerDayUsd: f.maxLlmCostPerDayUsd,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
export const runtimeCommand = (api: OwnerClient, agentId: string, kind: "pause" | "resume" | "run_now") =>
|
|
414
|
+
api.call<{ commandId: string }>("POST", `/v1/owner/agents/${agentId}/runtime/${kind}`);
|