@muhaven/mcp 0.1.5 → 0.1.7
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/CHANGELOG.md +155 -0
- package/dist/broker.cjs +168 -5
- package/dist/broker.js +168 -5
- package/dist/index.cjs +116 -110
- package/dist/index.d.cts +1 -21
- package/dist/index.d.ts +1 -21
- package/dist/index.js +116 -110
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/tool-hashes.json +9 -5
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { z, ZodError } from 'zod';
|
|
|
9
9
|
import { platform, homedir } from 'os';
|
|
10
10
|
import { connect, createServer } from 'net';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
-
import
|
|
12
|
+
import 'viem';
|
|
13
13
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
14
14
|
|
|
15
15
|
// src/server.ts
|
|
@@ -463,25 +463,32 @@ var TOOL_DESCRIPTORS = [
|
|
|
463
463
|
{
|
|
464
464
|
name: "muhaven.position.buy",
|
|
465
465
|
group: "position",
|
|
466
|
-
description:
|
|
466
|
+
description: `Prepare a Subscription buy. Returns a dashboard deep-link URL (muhaven.app/trade?mode=buy&...) the user opens to review the pre-filled form, then taps Authorize. The user's passkey + ZeroDev kernel sign on the dashboard \u2014 this MCP tool never holds or submits a signing key. Use after the user names a clear amount + token (e.g. "Buy 5 mhUSDC of TBILL1"). Token accepts either a symbol ("TBILL1") or 0x-address. Settlement is NOT observable from MCP \u2014 verify by calling muhaven.read.portfolio after the user confirms done.`,
|
|
467
467
|
sensitive: true
|
|
468
468
|
},
|
|
469
469
|
{
|
|
470
470
|
name: "muhaven.position.sell",
|
|
471
471
|
group: "position",
|
|
472
|
-
description: "
|
|
472
|
+
description: "Prepare a Subscription sell. Returns a dashboard deep-link URL (muhaven.app/trade?mode=sell&...) with the form pre-filled. Same passkey + verify-after pattern as muhaven.position.buy. Input is amountShares (raw share count), NOT mhUSDC notional.",
|
|
473
473
|
sensitive: true
|
|
474
474
|
},
|
|
475
475
|
{
|
|
476
476
|
name: "muhaven.position.claim",
|
|
477
477
|
group: "position",
|
|
478
|
-
description: "
|
|
478
|
+
description: "Prepare a yield claim. Returns a dashboard deep-link URL (muhaven.app/yields?...) pointing at the YieldsPage. When escrowId is set, the matching epoch row is highlighted + scrolled into view; when omitted, the page renders the user's full claimable list and they pick. User passkey-signs on the dashboard.",
|
|
479
479
|
sensitive: true
|
|
480
480
|
},
|
|
481
481
|
{
|
|
482
482
|
name: "muhaven.position.rebalance",
|
|
483
483
|
group: "position",
|
|
484
|
-
description: "
|
|
484
|
+
description: "NOT IMPLEMENTED in this release \u2014 returns an error pointing the user at single-leg position.buy / position.sell or the dashboard /trade page. Multi-leg execute_plan lands in Wave 5 with a composite preview UI + executeBatch on the kernel.",
|
|
485
|
+
sensitive: true
|
|
486
|
+
},
|
|
487
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
488
|
+
{
|
|
489
|
+
name: "muhaven.cash.wrap",
|
|
490
|
+
group: "cash",
|
|
491
|
+
description: 'Prepare a USDC \u2192 mhUSDC wrap (the encrypted-balance conversion that funds buys). Returns a dashboard deep-link URL (muhaven.app/cash?action=wrap&...) with the amount pre-filled. Input amountUsdc is human-readable USDC ("100" = $100). Common LLM chain: read.portfolio \u2192 notice 0 mhUSDC \u2192 cash.wrap \u2192 then position.buy (each is its own user-confirmed deep-link). Settlement not observable from MCP \u2014 re-call read.portfolio to verify.',
|
|
485
492
|
sensitive: true
|
|
486
493
|
},
|
|
487
494
|
{
|
|
@@ -607,6 +614,13 @@ function verifyDescriptorAgainstPin(descriptor, pinnedSha256) {
|
|
|
607
614
|
}
|
|
608
615
|
var HEX_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
609
616
|
var addressSchema = z.string().regex(HEX_ADDRESS_RE, "must be a 0x-prefixed 20-byte hex address");
|
|
617
|
+
var TOKEN_SYMBOL_RE = /^[A-Za-z][A-Za-z0-9]{0,11}$/;
|
|
618
|
+
var tokenIdentifierSchema = z.string().refine(
|
|
619
|
+
(v) => HEX_ADDRESS_RE.test(v) || TOKEN_SYMBOL_RE.test(v),
|
|
620
|
+
{
|
|
621
|
+
message: "must be a 0x-prefixed 20-byte hex address OR a token symbol (1-12 alphanumeric chars, starting with a letter)"
|
|
622
|
+
}
|
|
623
|
+
);
|
|
610
624
|
var tierSchema = z.enum(["advisory", "confirm-per-action", "policy-bound", "paused"]);
|
|
611
625
|
var surfaceSchema = z.enum(["havenbot", "mcp", "openclaw", "checkout"]);
|
|
612
626
|
z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]);
|
|
@@ -643,29 +657,38 @@ var ReadAuditInputSchema = z.object({
|
|
|
643
657
|
limit: z.number().int().min(1).max(200).optional()
|
|
644
658
|
}).strict();
|
|
645
659
|
var PositionBuyInputSchema = z.object({
|
|
646
|
-
|
|
660
|
+
/** Symbol (e.g. "TBILL1") or 0x-address. Path C dashboard resolves either. */
|
|
661
|
+
token: tokenIdentifierSchema,
|
|
647
662
|
/** Investor candidate spend, denominated in USDC base units (uint64). */
|
|
648
663
|
amountUsdc6: z.string().regex(/^\d+$/, "must be a base-10 integer string")
|
|
649
664
|
}).strict();
|
|
650
665
|
var PositionSellInputSchema = z.object({
|
|
651
|
-
token:
|
|
666
|
+
token: tokenIdentifierSchema,
|
|
652
667
|
/** Encrypted-balance share count to redeem, denominated in fhERC-20 base units. */
|
|
653
668
|
amountShares: z.string().regex(/^\d+$/, "must be a base-10 integer string")
|
|
654
669
|
}).strict();
|
|
655
670
|
var PositionClaimInputSchema = z.object({
|
|
656
|
-
token:
|
|
657
|
-
/** When set,
|
|
671
|
+
token: tokenIdentifierSchema,
|
|
672
|
+
/** When set, deep-link highlights the specific epoch row; else /yields renders the full claimable list. */
|
|
658
673
|
escrowId: z.string().regex(/^\d+$/).optional()
|
|
659
674
|
}).strict();
|
|
660
675
|
var PositionRebalanceInputSchema = z.object({
|
|
661
676
|
legs: z.array(
|
|
662
677
|
z.object({
|
|
663
|
-
token:
|
|
678
|
+
token: tokenIdentifierSchema,
|
|
664
679
|
side: z.enum(["buy", "sell"]),
|
|
665
680
|
amount: z.string().regex(/^\d+$/)
|
|
666
681
|
}).strict()
|
|
667
682
|
).min(2).max(8)
|
|
668
683
|
}).strict();
|
|
684
|
+
var CashWrapInputSchema = z.object({
|
|
685
|
+
/**
|
|
686
|
+
* USDC amount in human-readable units ("100" for $100, "1.5" for
|
|
687
|
+
* $1.50). Forwarded verbatim to `/cash?amount=`. Decimal optional
|
|
688
|
+
* — CashPage parses both `\d+` and `\d+\.\d+`.
|
|
689
|
+
*/
|
|
690
|
+
amountUsdc: z.string().regex(/^\d+(\.\d+)?$/, "must be a positive decimal number string")
|
|
691
|
+
}).strict();
|
|
669
692
|
var PolicySetTierInputSchema = z.object({
|
|
670
693
|
targetTier: tierSchema,
|
|
671
694
|
/** Returned by an earlier `request` call. Omit for step-down or first-call. */
|
|
@@ -736,15 +759,6 @@ function authRequiredPayload() {
|
|
|
736
759
|
loginCommand: "muhaven-broker login"
|
|
737
760
|
};
|
|
738
761
|
}
|
|
739
|
-
function sessionKeyRequiredPayload(dashboardBaseUrl = "https://muhaven.app") {
|
|
740
|
-
const mintUrl = `${trimTrailingSlash(dashboardBaseUrl)}/agent/policy/transition`;
|
|
741
|
-
return {
|
|
742
|
-
ok: false,
|
|
743
|
-
code: "SESSION_KEY_REQUIRED",
|
|
744
|
-
message: `No session key loaded in broker (read-only posture). Mint one via the dashboard at ${mintUrl}, copy the 0x-prefixed hex into MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. Do NOT run \`muhaven-broker login\` for this \u2014 that mints a JWT, not a session key.`,
|
|
745
|
-
mintUrl
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
762
|
|
|
749
763
|
// src/tools/handlers.ts
|
|
750
764
|
function ok(data) {
|
|
@@ -761,11 +775,6 @@ function mapBackendError(e) {
|
|
|
761
775
|
if (e instanceof Error) return err("backend.network", e.message);
|
|
762
776
|
return err("backend.network", "unknown backend error");
|
|
763
777
|
}
|
|
764
|
-
function mapBrokerError(e) {
|
|
765
|
-
if (e instanceof BrokerClientError) return err(`broker.${e.code}`, e.message);
|
|
766
|
-
if (e instanceof Error) return err("broker.network", e.message);
|
|
767
|
-
return err("broker.network", "unknown broker error");
|
|
768
|
-
}
|
|
769
778
|
async function readPortfolio(_input, deps) {
|
|
770
779
|
try {
|
|
771
780
|
const data = await deps.backend.get("/api/v1/portfolio");
|
|
@@ -819,103 +828,95 @@ async function readAudit(input, deps) {
|
|
|
819
828
|
return mapBackendError(e);
|
|
820
829
|
}
|
|
821
830
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
831
|
+
function buildPositionDeeplink(dashboardBaseUrl, action, params) {
|
|
832
|
+
const base = dashboardBaseUrl.replace(/\/+$/, "");
|
|
833
|
+
const path = action === "buy" || action === "sell" ? "/trade" : action === "claim" ? "/yields" : "/cash";
|
|
834
|
+
const search = new URLSearchParams();
|
|
835
|
+
if (action === "buy" || action === "sell") search.set("mode", action);
|
|
836
|
+
for (const [k, v] of Object.entries(params)) search.set(k, v);
|
|
837
|
+
search.set("from", "mcp");
|
|
838
|
+
return `${base}${path}?${search.toString()}`;
|
|
826
839
|
}
|
|
827
|
-
function
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
const obj = value;
|
|
831
|
-
const sorted = {};
|
|
832
|
-
for (const k of Object.keys(obj).sort()) sorted[k] = sortKeys(obj[k]);
|
|
833
|
-
return sorted;
|
|
840
|
+
function formatUsdc6ToDecimal(amountUsdc6) {
|
|
841
|
+
if (!/^\d+$/.test(amountUsdc6)) {
|
|
842
|
+
throw new Error(`amountUsdc6 must be a non-negative integer string: ${amountUsdc6}`);
|
|
834
843
|
}
|
|
835
|
-
|
|
844
|
+
const padded = amountUsdc6.padStart(7, "0");
|
|
845
|
+
const intPart = padded.slice(0, -6).replace(/^0+(?=\d)/, "");
|
|
846
|
+
const fracPart = padded.slice(-6).replace(/0+$/, "");
|
|
847
|
+
return fracPart === "" ? intPart : `${intPart}.${fracPart}`;
|
|
836
848
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const intentHash = computeIntentHash(intent);
|
|
840
|
-
if (!deps.broker) {
|
|
841
|
-
return err(
|
|
842
|
-
"broker.unavailable",
|
|
843
|
-
"position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
|
|
844
|
-
);
|
|
845
|
-
}
|
|
846
|
-
const broker = deps.broker;
|
|
847
|
-
if (cachedHasSessionKeyProbe === null) {
|
|
848
|
-
cachedHasSessionKeyProbe = (async () => {
|
|
849
|
-
try {
|
|
850
|
-
const hello = await broker.hello();
|
|
851
|
-
return hello.hasSessionKey ?? true;
|
|
852
|
-
} catch (err2) {
|
|
853
|
-
cachedHasSessionKeyProbe = null;
|
|
854
|
-
throw err2;
|
|
855
|
-
}
|
|
856
|
-
})();
|
|
857
|
-
}
|
|
858
|
-
let hasSessionKey;
|
|
859
|
-
try {
|
|
860
|
-
hasSessionKey = await cachedHasSessionKeyProbe;
|
|
861
|
-
} catch (e) {
|
|
862
|
-
return mapBrokerError(e);
|
|
863
|
-
}
|
|
864
|
-
if (hasSessionKey === false) {
|
|
865
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
866
|
-
}
|
|
867
|
-
try {
|
|
868
|
-
const sig = await broker.signHash(intentHash, { tool: toolName, summary });
|
|
869
|
-
return ok({
|
|
870
|
-
intentHash,
|
|
871
|
-
unsignedUserOp: {
|
|
872
|
-
target: "see backend",
|
|
873
|
-
data: "see backend",
|
|
874
|
-
note: "P3 returns a placeholder envelope; P6 wires the canonical UserOp shape."
|
|
875
|
-
},
|
|
876
|
-
brokerSignature: sig.signature,
|
|
877
|
-
signerAddress: sig.signerAddress
|
|
878
|
-
});
|
|
879
|
-
} catch (e) {
|
|
880
|
-
if (e instanceof BrokerClientError && e.code === "broker_error" && /session_key_unavailable/.test(e.message)) {
|
|
881
|
-
cachedHasSessionKeyProbe = Promise.resolve(false);
|
|
882
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
883
|
-
}
|
|
884
|
-
return mapBrokerError(e);
|
|
885
|
-
}
|
|
849
|
+
function resolveDashboardBaseUrl(deps) {
|
|
850
|
+
return deps.dashboardBaseUrl ?? "https://muhaven.app";
|
|
886
851
|
}
|
|
887
852
|
async function positionBuy(input, deps) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
853
|
+
const amount = formatUsdc6ToDecimal(input.amountUsdc6);
|
|
854
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "buy", {
|
|
855
|
+
token: input.token,
|
|
856
|
+
amount
|
|
857
|
+
});
|
|
858
|
+
return ok({
|
|
859
|
+
dashboardUrl,
|
|
860
|
+
action: "buy",
|
|
861
|
+
instructions: `Open this link to review and authorize the buy of ${amount} mhUSDC of ${input.token}:
|
|
862
|
+
${dashboardUrl}`,
|
|
863
|
+
echo: { action: "buy", token: input.token, amount }
|
|
864
|
+
});
|
|
894
865
|
}
|
|
895
866
|
async function positionSell(input, deps) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
867
|
+
if (!/^\d+(\.\d+)?$/.test(input.amountShares)) {
|
|
868
|
+
return err(
|
|
869
|
+
"invalid_input",
|
|
870
|
+
`amountShares must be a non-negative number string: ${JSON.stringify(input.amountShares)}`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "sell", {
|
|
874
|
+
token: input.token,
|
|
875
|
+
shares: input.amountShares
|
|
876
|
+
});
|
|
877
|
+
return ok({
|
|
878
|
+
dashboardUrl,
|
|
879
|
+
action: "sell",
|
|
880
|
+
instructions: `Open this link to review and authorize the sale of ${input.amountShares} shares of ${input.token}:
|
|
881
|
+
${dashboardUrl}`,
|
|
882
|
+
echo: { action: "sell", token: input.token, shares: input.amountShares }
|
|
883
|
+
});
|
|
902
884
|
}
|
|
903
885
|
async function positionClaim(input, deps) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
886
|
+
const params = { token: input.token };
|
|
887
|
+
if (input.escrowId) params.epoch = input.escrowId;
|
|
888
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "claim", params);
|
|
889
|
+
const claimDescriptor = input.escrowId ? `the claim for epoch #${input.escrowId} of ${input.token}` : `your claimable epochs for ${input.token}`;
|
|
890
|
+
return ok({
|
|
891
|
+
dashboardUrl,
|
|
892
|
+
action: "claim",
|
|
893
|
+
instructions: `Open this link to review and authorize ${claimDescriptor}:
|
|
894
|
+
${dashboardUrl}`,
|
|
895
|
+
echo: {
|
|
896
|
+
action: "claim",
|
|
897
|
+
token: input.token,
|
|
898
|
+
...input.escrowId ? { epoch: input.escrowId } : {}
|
|
899
|
+
}
|
|
900
|
+
});
|
|
910
901
|
}
|
|
911
|
-
async function positionRebalance(
|
|
912
|
-
return
|
|
913
|
-
|
|
914
|
-
"
|
|
915
|
-
`rebalance ${input.legs.length} legs`,
|
|
916
|
-
deps
|
|
902
|
+
async function positionRebalance(_input, _deps) {
|
|
903
|
+
return err(
|
|
904
|
+
"not_implemented",
|
|
905
|
+
"position.rebalance is deferred to Wave 5. Today, ask the user to execute legs one at a time via position.buy / position.sell, or use the dashboard /trade page directly."
|
|
917
906
|
);
|
|
918
907
|
}
|
|
908
|
+
async function cashWrap(input, deps) {
|
|
909
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "wrap", {
|
|
910
|
+
amount: input.amountUsdc
|
|
911
|
+
});
|
|
912
|
+
return ok({
|
|
913
|
+
dashboardUrl,
|
|
914
|
+
action: "wrap",
|
|
915
|
+
instructions: `Open this link to review and authorize the conversion of ${input.amountUsdc} USDC into mhUSDC:
|
|
916
|
+
${dashboardUrl}`,
|
|
917
|
+
echo: { action: "wrap", amount: input.amountUsdc }
|
|
918
|
+
});
|
|
919
|
+
}
|
|
919
920
|
async function policySetTier(input, deps) {
|
|
920
921
|
try {
|
|
921
922
|
const data = await deps.backend.post("/api/v1/agent/policy/transition", {
|
|
@@ -1123,6 +1124,11 @@ var HANDLERS = {
|
|
|
1123
1124
|
schema: PositionRebalanceInputSchema,
|
|
1124
1125
|
handler: positionRebalance
|
|
1125
1126
|
},
|
|
1127
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
1128
|
+
"muhaven.cash.wrap": {
|
|
1129
|
+
schema: CashWrapInputSchema,
|
|
1130
|
+
handler: cashWrap
|
|
1131
|
+
},
|
|
1126
1132
|
"muhaven.policy.set_tier": {
|
|
1127
1133
|
schema: PolicySetTierInputSchema,
|
|
1128
1134
|
handler: policySetTier
|
|
@@ -1206,7 +1212,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
1206
1212
|
var SERVER_VERSION = resolveServerVersion();
|
|
1207
1213
|
function resolveServerVersion() {
|
|
1208
1214
|
{
|
|
1209
|
-
return "0.1.
|
|
1215
|
+
return "0.1.7";
|
|
1210
1216
|
}
|
|
1211
1217
|
}
|
|
1212
1218
|
function toJsonInputSchema(schema) {
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.2",
|
|
4
4
|
"name": "muhaven-mcp",
|
|
5
5
|
"display_name": "MuHaven (RWA portfolio)",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.7",
|
|
7
7
|
"description": "Confidential RWA portfolio management on Fhenix CoFHE. Read your encrypted balances, propose yield claims and policy changes — all signing happens in a sibling broker daemon, the LLM never sees your private key.",
|
|
8
8
|
"long_description": "MuHaven MCP exposes 22 tools across read.* / position.* / policy.* / issuer.* / governance.* groups for managing real-world asset (RWA) tokens with FHE-encrypted balances. Authentication uses a one-time device-code ceremony (run `muhaven-broker login`); subsequent tool calls fetch the JWT from the broker over a Unix socket. Position / governance tools return unsigned UserOps + broker signatures — they NEVER auto-submit to a bundler. The companion `muhaven-broker` daemon must be running before tools can be invoked. See README for setup.",
|
|
9
9
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhaven/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "MuHaven MCP server — read/position/policy toolsets bridging Claude Desktop / Cursor / Claude Code to the MuHaven backend, with a sibling muhaven-broker daemon holding the session-key private half over a local IPC socket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/tool-hashes.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-17T18:17:06.859Z",
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "muhaven.read.portfolio",
|
|
@@ -23,19 +23,23 @@
|
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"name": "muhaven.position.buy",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "f24c9628249e91ed485031f9114053fa6b174a5d55aedfb4109392eb5795c9e4"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"name": "muhaven.position.sell",
|
|
30
|
-
"sha256": "
|
|
30
|
+
"sha256": "707545106fdd505d7d942153bcb34d7ead53c2539b9699e0733159a57c72177d"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"name": "muhaven.position.claim",
|
|
34
|
-
"sha256": "
|
|
34
|
+
"sha256": "31a6b9472f8b34e69ca28b888e60178dafd01b8644e2a61e6f36515660b68838"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"name": "muhaven.position.rebalance",
|
|
38
|
-
"sha256": "
|
|
38
|
+
"sha256": "ad32f2e9c2b6aafb6bd3acd33079dc7541035f0f405045178f95489f80cc922e"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "muhaven.cash.wrap",
|
|
42
|
+
"sha256": "1ae4cb949e0d09e7fb7e8957e51bfc63b43397c253849c217b835aaa9505b8c9"
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
"name": "muhaven.policy.set_tier",
|