@temporalabs/treasury 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/LICENSE +201 -0
- package/NOTICE +10 -0
- package/README.md +153 -0
- package/THIRD_PARTY_NOTICES.md +366 -0
- package/dist/abi/erc4626.d.ts +243 -0
- package/dist/abi/erc4626.js +146 -0
- package/dist/abi/erc4626.js.map +1 -0
- package/dist/build.d.ts +88 -0
- package/dist/build.js +124 -0
- package/dist/build.js.map +1 -0
- package/dist/client.d.ts +429 -0
- package/dist/client.js +248 -0
- package/dist/client.js.map +1 -0
- package/dist/config/earn.d.ts +47 -0
- package/dist/config/earn.js +46 -0
- package/dist/config/earn.js.map +1 -0
- package/dist/disclosures.d.ts +14 -0
- package/dist/disclosures.js +28 -0
- package/dist/disclosures.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/links.d.ts +21 -0
- package/dist/links.js +32 -0
- package/dist/links.js.map +1 -0
- package/dist/mcp/server.d.ts +33 -0
- package/dist/mcp/server.js +346 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp-server.mjs +56281 -0
- package/dist/position.d.ts +142 -0
- package/dist/position.js +354 -0
- package/dist/position.js.map +1 -0
- package/dist/preflight.d.ts +62 -0
- package/dist/preflight.js +165 -0
- package/dist/preflight.js.map +1 -0
- package/dist/quote.d.ts +55 -0
- package/dist/quote.js +83 -0
- package/dist/quote.js.map +1 -0
- package/dist/redact.d.ts +44 -0
- package/dist/redact.js +92 -0
- package/dist/redact.js.map +1 -0
- package/dist/registry-schema.d.ts +127 -0
- package/dist/registry-schema.js +124 -0
- package/dist/registry-schema.js.map +1 -0
- package/dist/registry.d.ts +26 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/units.d.ts +8 -0
- package/dist/units.js +24 -0
- package/dist/units.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/package.json +58 -0
- package/registry/vaults.json +68 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAddress, isAddress } from "viem";
|
|
3
|
+
// `strict: true` refuses a lowercase or otherwise mis-cased address — the registry is a hand-edited
|
|
4
|
+
// artifact and a checksum is the one typo detector an address carries.
|
|
5
|
+
const address = z
|
|
6
|
+
.string()
|
|
7
|
+
.refine((s) => isAddress(s, { strict: true }), { message: "not a checksummed EVM address" })
|
|
8
|
+
.transform((s) => getAddress(s));
|
|
9
|
+
export const chassisSchema = z.enum(["morpho-v2", "fusion", "enzyme"]);
|
|
10
|
+
/**
|
|
11
|
+
* Which chassis expose a standard ERC-4626 `deposit`/`redeem` to a depositor. Enzyme does not
|
|
12
|
+
* (`buyShares` via the comptroller), which is what every refusal path in this client is measured
|
|
13
|
+
* against: build, pre-flight, the MCP boundary and the default-vault rule all discriminate on it.
|
|
14
|
+
*/
|
|
15
|
+
export const erc4626Chassis = new Set(["morpho-v2", "fusion"]);
|
|
16
|
+
/**
|
|
17
|
+
* Whose contract the depositor's USDC actually enters.
|
|
18
|
+
* - `morpho`: a public third-party Morpho vault, reached directly. No row uses it today (Tempora vaults only).
|
|
19
|
+
* - `tempora`: a Tempora fund vault (the product; each fund joins as it goes live).
|
|
20
|
+
*/
|
|
21
|
+
export const backendSchema = z.enum(["morpho", "tempora"]);
|
|
22
|
+
/**
|
|
23
|
+
* Whether a third party can deposit is a MEASUREMENT, not a config flag. `maxDeposit()` lies on
|
|
24
|
+
* two of the three live chassis (Fusion returns uint256.max behind a whitelist; Morpho V2 returns 0
|
|
25
|
+
* on an open vault), so the only honest value is the result of a simulated `deposit` from a
|
|
26
|
+
* stranger, stamped with when and how it was taken.
|
|
27
|
+
*/
|
|
28
|
+
export const depositOpenSchema = z.object({
|
|
29
|
+
open: z.boolean(),
|
|
30
|
+
method: z.literal("simulated-deposit-from-stranger"),
|
|
31
|
+
measuredAtBlock: z.number().int().positive(),
|
|
32
|
+
measuredAtIso: z.string().datetime(),
|
|
33
|
+
/** Present iff `open` is false. The observed revert, named only where the instrument discriminated. */
|
|
34
|
+
reason: z.enum(["WHITELIST_GATED", "NOT_ALLOWLISTED", "NOT_ERC4626", "PAUSED", "REVERTED_OTHER"]).optional(),
|
|
35
|
+
detail: z.string().optional(),
|
|
36
|
+
});
|
|
37
|
+
export const vaultEntrySchema = z
|
|
38
|
+
.object({
|
|
39
|
+
/**
|
|
40
|
+
* The vault's own ERC-20 ticker, from `symbol()` — the identifier every tool takes and returns.
|
|
41
|
+
* It is the chain's name for this vault rather than one this repository assigns, so anyone
|
|
42
|
+
* holding the address can check it, and `scripts/registry-check.ts` reconciles it.
|
|
43
|
+
*/
|
|
44
|
+
symbol: z.string().min(1).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
|
|
45
|
+
/** The vault's own `name()`, reconciled the same way. */
|
|
46
|
+
name: z.string().min(1),
|
|
47
|
+
/**
|
|
48
|
+
* What a depositor must be told about THIS vault before any deposit is prepared. REQUIRED,
|
|
49
|
+
* and deliberately not defaulted: a vault cannot enter the registry without someone stating
|
|
50
|
+
* its risk posture in words, and a production vault's text will not read like a test
|
|
51
|
+
* vault's. `earn_vaults` returns it per row; the skill shows it to the depositor rather
|
|
52
|
+
* than summarising it.
|
|
53
|
+
*/
|
|
54
|
+
warning: z.string().min(1),
|
|
55
|
+
chainId: z.literal(8453),
|
|
56
|
+
address,
|
|
57
|
+
chassis: chassisSchema,
|
|
58
|
+
backend: backendSchema,
|
|
59
|
+
/** Exactly one vault in the registry carries this; it is what the tools use when no `vault` is given. */
|
|
60
|
+
isDefault: z.boolean().default(false),
|
|
61
|
+
asset: z.object({ address, symbol: z.string().min(1), decimals: z.number().int().min(0).max(36) }),
|
|
62
|
+
/** Measured on-chain via `decimals()`. 18 on Morpho V2 and Enzyme, 8 on Fusion — never assume. */
|
|
63
|
+
shareDecimals: z.number().int().min(0).max(36),
|
|
64
|
+
depositOpen: depositOpenSchema,
|
|
65
|
+
/**
|
|
66
|
+
* The first block at which the vault contract has code (measured by bisecting `eth_getCode`). A
|
|
67
|
+
* position's Deposit/Withdraw history cannot predate it, so a scan from here is the WHOLE history —
|
|
68
|
+
* `earn_balance` starts here instead of guessing a lookback.
|
|
69
|
+
*/
|
|
70
|
+
deployedAtBlock: z.number().int().positive().optional(),
|
|
71
|
+
notes: z.array(z.string()).default([]),
|
|
72
|
+
})
|
|
73
|
+
.strict()
|
|
74
|
+
.superRefine((v, ctx) => {
|
|
75
|
+
if (!v.depositOpen.open && !v.depositOpen.reason) {
|
|
76
|
+
ctx.addIssue({ code: "custom", path: ["depositOpen", "reason"], message: "a closed vault must say why" });
|
|
77
|
+
}
|
|
78
|
+
if (v.depositOpen.open && v.depositOpen.reason) {
|
|
79
|
+
ctx.addIssue({ code: "custom", path: ["depositOpen", "reason"], message: "an open vault carries no reason" });
|
|
80
|
+
}
|
|
81
|
+
if (!erc4626Chassis.has(v.chassis) && v.depositOpen.open) {
|
|
82
|
+
ctx.addIssue({
|
|
83
|
+
code: "custom",
|
|
84
|
+
path: ["depositOpen"],
|
|
85
|
+
message: `${v.chassis} has no ERC-4626 deposit path; it cannot be marked open for this client`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
export const registrySchema = z
|
|
90
|
+
.object({
|
|
91
|
+
schemaVersion: z.literal(1),
|
|
92
|
+
/**
|
|
93
|
+
* When the rows were last reconciled against the chain (`scripts/registry-check.ts`). The chain is
|
|
94
|
+
* the only source a row is checked against; there is no upstream repository the client knows of.
|
|
95
|
+
*/
|
|
96
|
+
reconciledAtIso: z.string().datetime(),
|
|
97
|
+
vaults: z.array(vaultEntrySchema).min(1),
|
|
98
|
+
})
|
|
99
|
+
.strict()
|
|
100
|
+
.superRefine((r, ctx) => {
|
|
101
|
+
const symbols = new Set();
|
|
102
|
+
const addrs = new Set();
|
|
103
|
+
const defaults = r.vaults.filter((v) => v.isDefault);
|
|
104
|
+
if (defaults.length !== 1) {
|
|
105
|
+
ctx.addIssue({ code: "custom", path: ["vaults"], message: `exactly one vault must be isDefault; found ${defaults.length}` });
|
|
106
|
+
}
|
|
107
|
+
const d = defaults[0];
|
|
108
|
+
// A default may be WHITELIST_GATED. The
|
|
109
|
+
// pre-flight re-measures access live on every call and refuses a non-member, so
|
|
110
|
+
// a gated default costs an agent a clear refusal, never funds. It must still be a chassis this client
|
|
111
|
+
// can build for, and it may not be closed for any other reason.
|
|
112
|
+
if (d && !(erc4626Chassis.has(d.chassis) && (d.depositOpen.open || d.depositOpen.reason === "WHITELIST_GATED"))) {
|
|
113
|
+
ctx.addIssue({ code: "custom", path: ["vaults"], message: `the default vault (${d.symbol}) must be ERC-4626 and either measured open or WHITELIST_GATED` });
|
|
114
|
+
}
|
|
115
|
+
r.vaults.forEach((v, i) => {
|
|
116
|
+
if (symbols.has(v.symbol))
|
|
117
|
+
ctx.addIssue({ code: "custom", path: ["vaults", i, "symbol"], message: `duplicate symbol ${v.symbol}` });
|
|
118
|
+
if (addrs.has(v.address))
|
|
119
|
+
ctx.addIssue({ code: "custom", path: ["vaults", i, "address"], message: `duplicate address ${v.address}` });
|
|
120
|
+
symbols.add(v.symbol);
|
|
121
|
+
addrs.add(v.address);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=registry-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-schema.js","sourceRoot":"","sources":["../src/registry-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Registry, type VaultEntry } from "./registry-schema.js";
|
|
2
|
+
/** Parses and validates the shipped registry. Throws on any schema violation — a registry that does not parse is not a registry. */
|
|
3
|
+
export declare function loadRegistry(): Registry;
|
|
4
|
+
/**
|
|
5
|
+
* TEST SEAM — `tests/fixtures/registry.ts` only, and NOT re-exported from `index.ts`, so no
|
|
6
|
+
* consumer of this package can reach it. The unit tiers need chassis the registry does not ship
|
|
7
|
+
* (18-decimal shares, a non-ERC-4626 chassis, a vault open to anyone); without this they would
|
|
8
|
+
* pin themselves to whichever vaults the fund happens to offer, and a registry edit — a product
|
|
9
|
+
* decision — would redden unrelated unit tests. `undefined` restores the shipped file.
|
|
10
|
+
*/
|
|
11
|
+
export declare function __setRegistryForTests(r: Registry | undefined): void;
|
|
12
|
+
export declare function listVaults(): VaultEntry[];
|
|
13
|
+
export declare function getVault(symbol: string): VaultEntry;
|
|
14
|
+
/**
|
|
15
|
+
* The vault the tools use when the caller names none: `EARN.defaultVault` (config/earn.ts — the
|
|
16
|
+
* toggle), which the registry must also mark `isDefault` (the measurement; the schema guarantees exactly
|
|
17
|
+
* one and that it is open). A disagreement between the two is refused, never resolved silently.
|
|
18
|
+
*/
|
|
19
|
+
export declare function defaultVault(): VaultEntry;
|
|
20
|
+
/** Resolves an optional ticker to a vault: the named one, or the default. */
|
|
21
|
+
export declare function resolveVault(symbol?: string): VaultEntry;
|
|
22
|
+
/**
|
|
23
|
+
* The vaults this client can put money into: ACTIVE, on an ERC-4626 chassis, and MEASURED open.
|
|
24
|
+
* This is the rule, not a list — the count moves as vaults are provisioned, gated, or retired.
|
|
25
|
+
*/
|
|
26
|
+
export declare function depositableVaults(): VaultEntry[];
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { registrySchema, erc4626Chassis } from "./registry-schema.js";
|
|
4
|
+
import { EARN } from "./config/earn.js";
|
|
5
|
+
const REGISTRY_URL = new URL("../registry/vaults.json", import.meta.url);
|
|
6
|
+
let cached;
|
|
7
|
+
/** Parses and validates the shipped registry. Throws on any schema violation — a registry that does not parse is not a registry. */
|
|
8
|
+
export function loadRegistry() {
|
|
9
|
+
if (cached)
|
|
10
|
+
return cached;
|
|
11
|
+
const raw = JSON.parse(readFileSync(fileURLToPath(REGISTRY_URL), "utf8"));
|
|
12
|
+
cached = registrySchema.parse(raw);
|
|
13
|
+
return cached;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* TEST SEAM — `tests/fixtures/registry.ts` only, and NOT re-exported from `index.ts`, so no
|
|
17
|
+
* consumer of this package can reach it. The unit tiers need chassis the registry does not ship
|
|
18
|
+
* (18-decimal shares, a non-ERC-4626 chassis, a vault open to anyone); without this they would
|
|
19
|
+
* pin themselves to whichever vaults the fund happens to offer, and a registry edit — a product
|
|
20
|
+
* decision — would redden unrelated unit tests. `undefined` restores the shipped file.
|
|
21
|
+
*/
|
|
22
|
+
export function __setRegistryForTests(r) {
|
|
23
|
+
cached = r;
|
|
24
|
+
}
|
|
25
|
+
export function listVaults() {
|
|
26
|
+
return loadRegistry().vaults;
|
|
27
|
+
}
|
|
28
|
+
export function getVault(symbol) {
|
|
29
|
+
const v = loadRegistry().vaults.find((x) => x.symbol === symbol);
|
|
30
|
+
if (!v) {
|
|
31
|
+
const known = loadRegistry()
|
|
32
|
+
.vaults.map((x) => x.symbol)
|
|
33
|
+
.join(", ");
|
|
34
|
+
throw new Error(`unknown vault "${symbol}"; known: ${known}`);
|
|
35
|
+
}
|
|
36
|
+
return v;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The vault the tools use when the caller names none: `EARN.defaultVault` (config/earn.ts — the
|
|
40
|
+
* toggle), which the registry must also mark `isDefault` (the measurement; the schema guarantees exactly
|
|
41
|
+
* one and that it is open). A disagreement between the two is refused, never resolved silently.
|
|
42
|
+
*/
|
|
43
|
+
export function defaultVault() {
|
|
44
|
+
const v = getVault(EARN.defaultVault);
|
|
45
|
+
if (!v.isDefault) {
|
|
46
|
+
const marked = loadRegistry().vaults.find((x) => x.isDefault)?.symbol ?? "(none)";
|
|
47
|
+
throw new Error(`config/earn.ts names "${EARN.defaultVault}" as the default but the registry marks "${marked}"; change both or neither`);
|
|
48
|
+
}
|
|
49
|
+
return v;
|
|
50
|
+
}
|
|
51
|
+
/** Resolves an optional ticker to a vault: the named one, or the default. */
|
|
52
|
+
export function resolveVault(symbol) {
|
|
53
|
+
return symbol ? getVault(symbol) : defaultVault();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The vaults this client can put money into: ACTIVE, on an ERC-4626 chassis, and MEASURED open.
|
|
57
|
+
* This is the rule, not a list — the count moves as vaults are provisioned, gated, or retired.
|
|
58
|
+
*/
|
|
59
|
+
export function depositableVaults() {
|
|
60
|
+
return loadRegistry().vaults.filter((v) => erc4626Chassis.has(v.chassis) && v.depositOpen.open);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAkC,MAAM,sBAAsB,CAAC;AACtG,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEzE,IAAI,MAA4B,CAAC;AAEjC,oIAAoI;AACpI,MAAM,UAAU,YAAY;IAC1B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAY,CAAC;IACrF,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAuB;IAC3D,MAAM,GAAG,CAAC,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,YAAY,EAAE,CAAC,MAAM,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,MAAM,CAAC,GAAG,YAAY,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACjE,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,KAAK,GAAG,YAAY,EAAE;aACzB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;aAC3B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,aAAa,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACtC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,MAAM,IAAI,QAAQ,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,YAAY,4CAA4C,MAAM,2BAA2B,CAAC,CAAC;IAC3I,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAAC,MAAe;IAC1C,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,YAAY,EAAE,CAAC,MAAM,CAAC,MAAM,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,CAC3D,CAAC;AACJ,CAAC"}
|
package/dist/units.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amount parsing that refuses to guess. A human-readable decimal string is converted with the
|
|
3
|
+
* caller-supplied decimals; a string with more fractional digits than the token has is an error,
|
|
4
|
+
* not a silent truncation. `redeem` takes SHARES (shareDecimals); `deposit` takes ASSETS
|
|
5
|
+
* (asset.decimals). Mixing them is a 10^12 error on an 18/6 vault, and an easy one to ship.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseAmount(human: string, decimals: number, label: string): bigint;
|
|
8
|
+
export declare function formatAmount(raw: bigint, decimals: number): string;
|
package/dist/units.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { formatUnits, parseUnits } from "viem";
|
|
2
|
+
/**
|
|
3
|
+
* Amount parsing that refuses to guess. A human-readable decimal string is converted with the
|
|
4
|
+
* caller-supplied decimals; a string with more fractional digits than the token has is an error,
|
|
5
|
+
* not a silent truncation. `redeem` takes SHARES (shareDecimals); `deposit` takes ASSETS
|
|
6
|
+
* (asset.decimals). Mixing them is a 10^12 error on an 18/6 vault, and an easy one to ship.
|
|
7
|
+
*/
|
|
8
|
+
export function parseAmount(human, decimals, label) {
|
|
9
|
+
const s = human.trim();
|
|
10
|
+
if (!/^\d+(\.\d+)?$/.test(s))
|
|
11
|
+
throw new Error(`${label}: "${human}" is not a plain decimal amount`);
|
|
12
|
+
const frac = s.split(".")[1] ?? "";
|
|
13
|
+
if (frac.length > decimals) {
|
|
14
|
+
throw new Error(`${label}: "${human}" has ${frac.length} fractional digits but the token has ${decimals} decimals`);
|
|
15
|
+
}
|
|
16
|
+
const v = parseUnits(s, decimals);
|
|
17
|
+
if (v <= 0n)
|
|
18
|
+
throw new Error(`${label}: amount must be positive`);
|
|
19
|
+
return v;
|
|
20
|
+
}
|
|
21
|
+
export function formatAmount(raw, decimals) {
|
|
22
|
+
return formatUnits(raw, decimals);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=units.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"units.js","sourceRoot":"","sources":["../src/units.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,KAAa;IACxE,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,KAAK,iCAAiC,CAAC,CAAC;IACpG,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,wCAAwC,QAAQ,WAAW,CAAC,CAAC;IACtH,CAAC;IACD,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IAClE,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,QAAgB;IACxD,OAAO,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PACKAGE_VERSION: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package version, read from package.json — never a literal. The declared version follows the
|
|
3
|
+
* branch (v0.0.N ⇒ 0.0.N), and CI holds a version branch to that; anything that
|
|
4
|
+
* reports a version reads it from here so a branch bump cannot leave a stale "0.1.0" behind
|
|
5
|
+
* (a literal in server.ts once lagged package.json by two releases).
|
|
6
|
+
*
|
|
7
|
+
* Same resolution trick as registry.ts: this file sits at src/ top level and the bundle at dist/
|
|
8
|
+
* top level, so `../package.json` is the package manifest at the repository root from both.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
const PACKAGE_JSON_URL = new URL("../package.json", import.meta.url);
|
|
13
|
+
export const PACKAGE_VERSION = JSON.parse(readFileSync(fileURLToPath(PACKAGE_JSON_URL), "utf8")).version;
|
|
14
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAErE,MAAM,CAAC,MAAM,eAAe,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@temporalabs/treasury",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/TemporaLabs/treasury.git"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"NOTICE",
|
|
12
|
+
"THIRD_PARTY_NOTICES.md",
|
|
13
|
+
"dist",
|
|
14
|
+
"registry"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"provenance": true
|
|
19
|
+
},
|
|
20
|
+
"description": "Agent Treasury by Tempora Labs — a depositor client for ERC-4626 vaults: registry, pre-flight, and unsigned deposit/withdraw builders. Never signs, never holds a key.",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"bin": {
|
|
25
|
+
"treasury-mcp": "./dist/mcp-server.mjs"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=22"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"prepack": "npm run build && git diff --exit-code -- dist/mcp-server.mjs",
|
|
38
|
+
"build": "tsc -b && tsup && tsx scripts/third-party-notices.ts --write",
|
|
39
|
+
"typecheck": "tsc -b && tsc -p tsconfig.scripts.json",
|
|
40
|
+
"test": "tsc -b && tsc -p tsconfig.scripts.json && vitest run",
|
|
41
|
+
"test:fork": "tsc -b && TREASURY_FORK=1 vitest run --testTimeout 300000 tests/fork.test.ts",
|
|
42
|
+
"mcp": "tsx src/mcp/server.ts",
|
|
43
|
+
"registry:check": "tsx scripts/registry-check.ts",
|
|
44
|
+
"notices:check": "tsx scripts/third-party-notices.ts"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
48
|
+
"viem": "^2.37.0",
|
|
49
|
+
"zod": "^4.1.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.15.0",
|
|
53
|
+
"tsup": "^8.5.0",
|
|
54
|
+
"tsx": "^4.20.0",
|
|
55
|
+
"typescript": "^5.9.0",
|
|
56
|
+
"vitest": "^5.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"reconciledAtIso": "2026-09-18T16:25:46Z",
|
|
4
|
+
"vaults": [
|
|
5
|
+
{
|
|
6
|
+
"symbol": "tlCashPlusUSDC2",
|
|
7
|
+
"name": "Tempora Labs Cash Plus USDC (Test 2)",
|
|
8
|
+
"warning": "TEST VAULT — unproven. It exists to exercise this product, not to hold savings. Deposit only an amount you are fully prepared to lose entirely, and do not move significant funds into it. Show this warning before preparing any deposit.",
|
|
9
|
+
"chainId": 8453,
|
|
10
|
+
"address": "0x040fCA12673778FEED5DA7b2ccFbbAb0cc0134Cf",
|
|
11
|
+
"chassis": "morpho-v2",
|
|
12
|
+
"backend": "tempora",
|
|
13
|
+
"isDefault": true,
|
|
14
|
+
"asset": {
|
|
15
|
+
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
16
|
+
"symbol": "USDC",
|
|
17
|
+
"decimals": 6
|
|
18
|
+
},
|
|
19
|
+
"shareDecimals": 18,
|
|
20
|
+
"depositOpen": {
|
|
21
|
+
"open": true,
|
|
22
|
+
"method": "simulated-deposit-from-stranger",
|
|
23
|
+
"measuredAtBlock": 51372415,
|
|
24
|
+
"measuredAtIso": "2026-09-16T04:49:00Z",
|
|
25
|
+
"detail": "deposit(1 USDC, 0x…dEaD) from 0x…dEaD reverted 0xe65b7a77 TransferFromReverted at block 51372415 — it reached the token pull, so access is open; maxDeposit() reads 0 to every caller on this chassis by design and is not consulted."
|
|
26
|
+
},
|
|
27
|
+
"deployedAtBlock": 51371133,
|
|
28
|
+
"notes": [
|
|
29
|
+
"18-decimal shares against a 6-decimal asset — a share amount is never an asset amount here.",
|
|
30
|
+
"Morpho Vault V2. maxDeposit() and maxWithdraw() return 0 to every caller by protocol design; the pre-flight simulates the real call and earn_balance simulates the exit. Neither reads those views.",
|
|
31
|
+
"Withdrawals are served from idle USDC, then through the vault's liquidity adapter; a withdrawal larger than that reverts until positions are unwound. earn_balance reports exitableNow by simulating the withdrawal.",
|
|
32
|
+
"No fee is set: performanceFee() = 0 and managementFee() = 0 at block 51372415. The curator can introduce one, and the vault's fee timelocks are 0 (timelock(setPerformanceFee) = timelock(setManagementFee) = 0 at block 51404161), so a change needs no notice. Read both fees before depositing.",
|
|
33
|
+
"The DEFAULT vault, and open to any account: it is in depositable."
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"symbol": "tlCashPlusUSDC2A",
|
|
38
|
+
"name": "Tempora Labs Cash Plus USDC (Test 2A)",
|
|
39
|
+
"warning": "TEST VAULT — unproven. It exists to exercise this product, not to hold savings. Deposit only an amount you are fully prepared to lose entirely, and do not move significant funds into it. Show this warning before preparing any deposit.",
|
|
40
|
+
"chainId": 8453,
|
|
41
|
+
"address": "0x1516D2c082b9cc9af852B1Ebc828f168F27299ef",
|
|
42
|
+
"chassis": "fusion",
|
|
43
|
+
"backend": "tempora",
|
|
44
|
+
"isDefault": false,
|
|
45
|
+
"asset": {
|
|
46
|
+
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
47
|
+
"symbol": "USDC",
|
|
48
|
+
"decimals": 6
|
|
49
|
+
},
|
|
50
|
+
"shareDecimals": 8,
|
|
51
|
+
"depositOpen": {
|
|
52
|
+
"open": false,
|
|
53
|
+
"method": "simulated-deposit-from-stranger",
|
|
54
|
+
"measuredAtBlock": 51361109,
|
|
55
|
+
"measuredAtIso": "2026-09-15T22:32:47Z",
|
|
56
|
+
"reason": "WHITELIST_GATED",
|
|
57
|
+
"detail": "deposit(1 USDC, 0x…dEaD) from 0x…dEaD reverted 0x068ca9d8 AccessManagedUnauthorized at block 51361109. The AccessManager at 0xD76eadf8451eB94df0eADd8BfB01C66EF3182ECC returns getTargetFunctionRole(vault, deposit) = 800; withdraw and redeem are PUBLIC_ROLE (2^64-1), so an account that holds shares can always leave."
|
|
58
|
+
},
|
|
59
|
+
"deployedAtBlock": 51359816,
|
|
60
|
+
"notes": [
|
|
61
|
+
"8-decimal shares against a 6-decimal asset — a share amount is never an asset amount here.",
|
|
62
|
+
"getInstantWithdrawalFuses() returns three fuses (measured at block 51361109), so a withdrawal is served from the vault's liquid balance and then, in fuse order, by unwinding its positions in the same transaction. A withdrawal larger than what those positions can release in-block still reverts; earn_balance reports exitableNow, which simulates the withdrawal rather than reading maxWithdraw.",
|
|
63
|
+
"Deposits need role 800 on the AccessManager. An account without it gets WHITELIST_GATED from earn_status and earn_quote, and this client refuses to build a deposit for it. Withdrawals are public.",
|
|
64
|
+
"Whitelist-gated (role 800 on its AccessManager): not in depositable. Listed so that an admitted account's position can be read and exited; a non-member gets WHITELIST_GATED from earn_status before anything is built."
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|