mofu-sdk 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mofu contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # mofu-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Robinhood Chain token and wallet checks for agents, bots and scripts, from [Mofu](https://mofu.works).
4
+
5
+ - **Token Scanner:** what a contract allows (mint, pause, blacklist, fees, upgrades, owner), a simulated buy and sell with taxes, how big one sale can be before the price drops 10%, copycats, and whether the code matches known honeypots.
6
+ - **Wallet Health:** every active approval, re-checked on chain, with risky spenders flagged.
7
+ - **Webhooks:** a signed POST when a watched token's owner, supply, code, pause state, liquidity or verdict changes.
8
+ - **A buy rule** you can tune, so an agent refuses what Mofu flags.
9
+
10
+ No dependencies. Works wherever `fetch` and WebCrypto exist: Node.js 18+, browsers, Cloudflare Workers, Deno, Bun. The API is free: 50 calls a day per IP, shared with Mofu's MCP server and `mofu-cli`.
11
+
12
+ ```sh
13
+ npm install mofu-sdk
14
+ ```
15
+
16
+ ## Check a token
17
+
18
+ ```ts
19
+ import { Mofu } from "mofu-sdk";
20
+
21
+ const mofu = new Mofu();
22
+ const { report, url } = await mofu.scanToken("0xf76593c1c321c2a2f77775025e9677999ce7ea8c");
23
+
24
+ report.verdict; // "clean" | "caution" | "risky"
25
+ report.sellTest?.status; // "passed" | "failed"
26
+ report.sellTest?.depth?.ethAt10Percent; // ETH one sale can take before the price drops 10%
27
+ report.codeMatch; // same code as tokens whose sell failed, if any
28
+ url; // the shareable report on mofu.works
29
+ ```
30
+
31
+ ## Gate a buy
32
+
33
+ `checkBeforeBuy` scans and applies a rule. If Mofu can't be reached, the answer is no.
34
+
35
+ ```ts
36
+ const d = await mofu.checkBeforeBuy(token, { maxSellTaxPercent: 5, minSellDepthEth: 0.5 });
37
+ if (!d.allowed) return console.log("skip:", d.reasons);
38
+ await yourBuy(token);
39
+ ```
40
+
41
+ | Option | Default | Blocks when |
42
+ | ------------------- | ------------ | -------------------------------------------------------------- |
43
+ | `blockVerdicts` | `["risky"]` | the verdict is in the list (any single risk check also blocks) |
44
+ | `requireSellTest` | `true` | no sell could be simulated |
45
+ | `maxBuyTaxPercent` | `10` | the simulated buy tax is higher |
46
+ | `maxSellTaxPercent` | `10` | the simulated sell tax is higher |
47
+ | `blockLookalikes` | `false` | another token uses the same name or ticker |
48
+ | `minSellDepthEth` | `null` (off) | a sale this size would move the price more than 10% |
49
+
50
+ `decide(report, policy)` is the same rule as a pure function, for reports you already have.
51
+
52
+ ## Check a wallet
53
+
54
+ ```ts
55
+ const { report } = await mofu.checkWallet("0x…");
56
+ report.approvals.filter((a) => a.risk === "risk");
57
+ ```
58
+
59
+ ## Webhooks
60
+
61
+ ```ts
62
+ const { webhook, secret } = await mofu.webhooks.create({
63
+ url: "https://your.domain/mofu",
64
+ tokens: ["0x…"],
65
+ events: ["owner_changed", "liquidity_dropped", "verdict_changed"], // leave out for all six
66
+ });
67
+ // Mofu pinged your URL first; keep `secret`, it isn't shown again.
68
+
69
+ await mofu.webhooks.get(webhook.id, secret); // status, and when each token was last read
70
+ await mofu.webhooks.test(webhook.id, secret);
71
+ await mofu.webhooks.remove(webhook.id, secret);
72
+ ```
73
+
74
+ In your handler, verify the signature against the raw body:
75
+
76
+ ```ts
77
+ import { parseWebhook } from "mofu-sdk";
78
+
79
+ const event = await parseWebhook(request.headers.get("Mofu-Signature"), rawBody, secret);
80
+ if (event.type === "owner_changed") pause(event.token!.address);
81
+ ```
82
+
83
+ `parseWebhook` throws when the signature is wrong or older than five minutes; `verifyWebhook` returns a boolean instead.
84
+
85
+ ## Errors
86
+
87
+ Failures throw `MofuError` with `status` and `code`: `daily_limit` (429), `bad_address` (400), `chain_unreachable` (503), `network` (Mofu couldn't be reached).
88
+
89
+ ## Not advice
90
+
91
+ Mofu reads what a contract's code allows and simulates trades; it can't see the future. A clean report doesn't make a token a good buy.
92
+
93
+ Docs: <https://mofu.works/docs/#api> · Source: <https://github.com/dev-mofu/mofu/tree/main/packages/sdk> · MIT
@@ -0,0 +1,100 @@
1
+ /**
2
+ * mofu-sdk: Mofu's Robinhood Chain checks for agents, bots and scripts.
3
+ * No dependencies; works wherever fetch and WebCrypto exist (Node 18+, browsers, Workers, Deno, Bun).
4
+ */
5
+ import type { Checked, TokenReport, Verdict, WalletReport, Webhook, WebhookEvent, WebhookEventType } from "./types.js";
6
+ export * from "./types.js";
7
+ export interface MofuOptions {
8
+ /** Defaults to https://mofu.works. */
9
+ baseUrl?: string;
10
+ /** Defaults to the global fetch. */
11
+ fetch?: typeof fetch;
12
+ /** Sent as User-Agent where the runtime allows it. */
13
+ userAgent?: string;
14
+ /** Per request, in milliseconds. Defaults to 90 000 (a first scan can take half a minute). */
15
+ timeoutMs?: number;
16
+ }
17
+ /** An answer from Mofu that isn't a report: a bad address, the daily limit, the chain unreachable. */
18
+ export declare class MofuError extends Error {
19
+ /** HTTP status, or 0 when Mofu couldn't be reached. */
20
+ readonly status: number;
21
+ /** Mofu's error code, e.g. "daily_limit", "bad_address", "network". */
22
+ readonly code: string;
23
+ name: string;
24
+ constructor(message: string,
25
+ /** HTTP status, or 0 when Mofu couldn't be reached. */
26
+ status: number,
27
+ /** Mofu's error code, e.g. "daily_limit", "bad_address", "network". */
28
+ code: string);
29
+ }
30
+ export declare class Mofu {
31
+ private readonly base;
32
+ private readonly fetchFn;
33
+ private readonly headers;
34
+ private readonly timeoutMs;
35
+ constructor(options?: MofuOptions);
36
+ private call;
37
+ private static address;
38
+ /** The Token Scanner report for a token contract on Robinhood Chain. */
39
+ scanToken(address: string): Promise<Checked<TokenReport>>;
40
+ /** Wallet Health: every active approval (risky ones flagged) and the tokens a wallet holds. */
41
+ checkWallet(address: string): Promise<Checked<WalletReport>>;
42
+ /** Scans a token and applies a buy rule. Says no, with a reason, when Mofu can't be reached. */
43
+ checkBeforeBuy(address: string, policy?: Partial<Policy>): Promise<Decision>;
44
+ readonly webhooks: {
45
+ /**
46
+ * Watches up to 10 tokens. Mofu pings the URL first and saves the webhook only if it answers
47
+ * 2xx. The secret comes back once: keep it.
48
+ */
49
+ create: (input: {
50
+ url: string;
51
+ tokens: string[];
52
+ events?: WebhookEventType[];
53
+ }) => Promise<{
54
+ webhook: Webhook;
55
+ secret: string;
56
+ }>;
57
+ get: (id: string, secret: string) => Promise<Webhook>;
58
+ test: (id: string, secret: string) => Promise<{
59
+ delivered: boolean;
60
+ result: string;
61
+ }>;
62
+ remove: (id: string, secret: string) => Promise<void>;
63
+ };
64
+ }
65
+ export interface Policy {
66
+ /** Verdicts that block a buy. Default: ["risky"]. */
67
+ blockVerdicts: Verdict[];
68
+ /** Block when no sell could be simulated. Default: true. */
69
+ requireSellTest: boolean;
70
+ /** Default: 10. */
71
+ maxBuyTaxPercent: number;
72
+ /** Default: 10. */
73
+ maxSellTaxPercent: number;
74
+ /** Block when another token uses the same name or ticker. Default: false. */
75
+ blockLookalikes: boolean;
76
+ /** Block when one sale of this much ETH would move the price more than 10%. Default: off. */
77
+ minSellDepthEth: number | null;
78
+ }
79
+ export declare const DEFAULT_POLICY: Policy;
80
+ export interface Decision {
81
+ allowed: boolean;
82
+ /** Why it was blocked; empty when allowed. */
83
+ reasons: string[];
84
+ report: TokenReport | null;
85
+ }
86
+ /** Applies a buy rule to a report. Pure: test it, tune it, log it. */
87
+ export declare function decide(r: TokenReport, policy?: Partial<Policy>): Decision;
88
+ /**
89
+ * Checks a Mofu-Signature header (t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">) against
90
+ * the raw request body. Rejects signatures older than `toleranceSeconds` (default 300).
91
+ */
92
+ export declare function verifyWebhook(signatureHeader: string | null | undefined, rawBody: string, secret: string, options?: {
93
+ toleranceSeconds?: number;
94
+ now?: number;
95
+ }): Promise<boolean>;
96
+ /** Verifies and parses a webhook delivery. Throws MofuError("bad_signature") when it doesn't check out. */
97
+ export declare function parseWebhook(signatureHeader: string | null | undefined, rawBody: string, secret: string, options?: {
98
+ toleranceSeconds?: number;
99
+ now?: number;
100
+ }): Promise<WebhookEvent>;
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ export * from "./types.js";
2
+ /** An answer from Mofu that isn't a report: a bad address, the daily limit, the chain unreachable. */
3
+ export class MofuError extends Error {
4
+ status;
5
+ code;
6
+ name = "MofuError";
7
+ constructor(message,
8
+ /** HTTP status, or 0 when Mofu couldn't be reached. */
9
+ status,
10
+ /** Mofu's error code, e.g. "daily_limit", "bad_address", "network". */
11
+ code) {
12
+ super(message);
13
+ this.status = status;
14
+ this.code = code;
15
+ }
16
+ }
17
+ const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
18
+ export class Mofu {
19
+ base;
20
+ fetchFn;
21
+ headers;
22
+ timeoutMs;
23
+ constructor(options = {}) {
24
+ this.base = (options.baseUrl ?? "https://mofu.works").replace(/\/+$/, "");
25
+ this.fetchFn = options.fetch ?? ((input, init) => fetch(input, init));
26
+ this.headers = {
27
+ Accept: "application/json",
28
+ "User-Agent": options.userAgent ?? "mofu-sdk/0.1",
29
+ };
30
+ this.timeoutMs = options.timeoutMs ?? 90_000;
31
+ }
32
+ async call(path, init = {}) {
33
+ let res;
34
+ try {
35
+ res = await this.fetchFn(`${this.base}${path}`, {
36
+ ...init,
37
+ headers: {
38
+ ...this.headers,
39
+ ...(init.body ? { "Content-Type": "application/json" } : {}),
40
+ ...init.headers,
41
+ },
42
+ signal: AbortSignal.timeout(this.timeoutMs),
43
+ });
44
+ }
45
+ catch (err) {
46
+ throw new MofuError(`Couldn't reach Mofu: ${err.message}`, 0, "network");
47
+ }
48
+ const body = (await res.json().catch(() => null));
49
+ if (!res.ok || !body) {
50
+ throw new MofuError(body?.error?.message ?? `Mofu answered HTTP ${res.status}`, res.status, body?.error?.code ?? "http_error");
51
+ }
52
+ return body;
53
+ }
54
+ static address(a, what) {
55
+ if (!ADDRESS.test(a))
56
+ throw new MofuError(`${what} must be 0x followed by 40 hex characters`, 400, "bad_address");
57
+ return a.toLowerCase();
58
+ }
59
+ /** The Token Scanner report for a token contract on Robinhood Chain. */
60
+ scanToken(address) {
61
+ return this.call(`/api/v1/token/${Mofu.address(address, "address")}`);
62
+ }
63
+ /** Wallet Health: every active approval (risky ones flagged) and the tokens a wallet holds. */
64
+ checkWallet(address) {
65
+ return this.call(`/api/v1/wallet/${Mofu.address(address, "address")}`);
66
+ }
67
+ /** Scans a token and applies a buy rule. Says no, with a reason, when Mofu can't be reached. */
68
+ async checkBeforeBuy(address, policy = {}) {
69
+ try {
70
+ return decide((await this.scanToken(address)).report, policy);
71
+ }
72
+ catch (err) {
73
+ return {
74
+ allowed: false,
75
+ reasons: [`no answer from Mofu: ${err.message}`],
76
+ report: null,
77
+ };
78
+ }
79
+ }
80
+ webhooks = {
81
+ /**
82
+ * Watches up to 10 tokens. Mofu pings the URL first and saves the webhook only if it answers
83
+ * 2xx. The secret comes back once: keep it.
84
+ */
85
+ create: (input) => this.call("/api/v1/webhooks", {
86
+ method: "POST",
87
+ body: JSON.stringify(input),
88
+ }),
89
+ get: async (id, secret) => (await this.call(`/api/v1/webhooks/${id}`, { headers: auth(secret) }))
90
+ .webhook,
91
+ test: (id, secret) => this.call(`/api/v1/webhooks/${id}/test`, {
92
+ method: "POST",
93
+ headers: auth(secret),
94
+ }),
95
+ remove: async (id, secret) => {
96
+ await this.call(`/api/v1/webhooks/${id}`, { method: "DELETE", headers: auth(secret) });
97
+ },
98
+ };
99
+ }
100
+ const auth = (secret) => ({ Authorization: `Bearer ${secret}` });
101
+ export const DEFAULT_POLICY = {
102
+ blockVerdicts: ["risky"],
103
+ requireSellTest: true,
104
+ maxBuyTaxPercent: 10,
105
+ maxSellTaxPercent: 10,
106
+ blockLookalikes: false,
107
+ minSellDepthEth: null,
108
+ };
109
+ /** Applies a buy rule to a report. Pure: test it, tune it, log it. */
110
+ export function decide(r, policy = {}) {
111
+ const p = { ...DEFAULT_POLICY, ...policy };
112
+ const reasons = [];
113
+ if (!r.isContract)
114
+ reasons.push("not a token contract");
115
+ if (p.blockVerdicts.includes(r.verdict))
116
+ reasons.push(`Mofu's verdict is ${r.verdict}`);
117
+ for (const c of r.checks)
118
+ if (c.status === "risk")
119
+ reasons.push(`${c.label} ${c.detail}`);
120
+ const t = r.sellTest;
121
+ if (t?.status === "failed")
122
+ reasons.push("a simulated sell failed (possible honeypot)");
123
+ else if (!t || t.status !== "passed") {
124
+ if (p.requireSellTest)
125
+ reasons.push("Mofu couldn't test selling it");
126
+ }
127
+ else {
128
+ if ((t.buyTaxPercent ?? 0) > p.maxBuyTaxPercent)
129
+ reasons.push(`buy tax ${t.buyTaxPercent}% is over ${p.maxBuyTaxPercent}%`);
130
+ if ((t.sellTaxPercent ?? 0) > p.maxSellTaxPercent)
131
+ reasons.push(`sell tax ${t.sellTaxPercent}% is over ${p.maxSellTaxPercent}%`);
132
+ if (p.minSellDepthEth !== null) {
133
+ const d = t.depth;
134
+ if (!d)
135
+ reasons.push("sell depth couldn't be measured");
136
+ else if (!d.moreThan && (d.ethAt10Percent ?? 0) < p.minSellDepthEth)
137
+ reasons.push(`a ${p.minSellDepthEth} ETH sale would move the price more than 10% (depth about ${d.ethAt10Percent ?? 0} ETH)`);
138
+ }
139
+ }
140
+ if (p.blockLookalikes && r.lookalikes.length)
141
+ reasons.push(`${r.lookalikes.length} other token(s) use the same name or ticker`);
142
+ return { allowed: reasons.length === 0, reasons: [...new Set(reasons)], report: r };
143
+ }
144
+ // ---------------------------------------------------------------- webhooks
145
+ /**
146
+ * Checks a Mofu-Signature header (t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">) against
147
+ * the raw request body. Rejects signatures older than `toleranceSeconds` (default 300).
148
+ */
149
+ export async function verifyWebhook(signatureHeader, rawBody, secret, options = {}) {
150
+ const parts = Object.fromEntries((signatureHeader ?? "").split(",").map((p) => p.trim().split("=", 2)));
151
+ const t = Number(parts.t);
152
+ const now = Math.floor((options.now ?? Date.now()) / 1000);
153
+ if (!Number.isFinite(t) || !parts.v1 || Math.abs(now - t) > (options.toleranceSeconds ?? 300))
154
+ return false;
155
+ const enc = new TextEncoder();
156
+ const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
157
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, enc.encode(`${t}.${rawBody}`)));
158
+ const expected = [...mac].map((b) => b.toString(16).padStart(2, "0")).join("");
159
+ const given = parts.v1.toLowerCase();
160
+ if (given.length !== expected.length)
161
+ return false;
162
+ let diff = 0;
163
+ for (let i = 0; i < expected.length; i++)
164
+ diff |= expected.charCodeAt(i) ^ given.charCodeAt(i);
165
+ return diff === 0;
166
+ }
167
+ /** Verifies and parses a webhook delivery. Throws MofuError("bad_signature") when it doesn't check out. */
168
+ export async function parseWebhook(signatureHeader, rawBody, secret, options) {
169
+ if (!(await verifyWebhook(signatureHeader, rawBody, secret, options)))
170
+ throw new MofuError("The webhook signature doesn't match", 401, "bad_signature");
171
+ return JSON.parse(rawBody);
172
+ }
@@ -0,0 +1,177 @@
1
+ /** Report shapes returned by Mofu's API (mofu.works/api/v1). */
2
+ export type Verdict = "clean" | "caution" | "risky";
3
+ export interface TokenCheck {
4
+ id: string;
5
+ label: string;
6
+ status: "ok" | "warn" | "risk" | "info";
7
+ detail: string;
8
+ }
9
+ /** The biggest single sale before the price drops 10%. */
10
+ export interface SellDepth {
11
+ method: "simulated" | "reserves";
12
+ /** ETH that sale returns; null when even the smallest size moved the price more than 10%. */
13
+ ethAt10Percent: number | null;
14
+ /** True when every size tried stayed under 10%: the real depth is bigger. */
15
+ moreThan: boolean;
16
+ steps: Array<{
17
+ supplyPercent: number | null;
18
+ ethOut: number;
19
+ impactPercent: number;
20
+ }>;
21
+ detail: string;
22
+ }
23
+ /** A buy and a sell simulated on Robinhood Chain; nothing is sent. */
24
+ export interface SellTest {
25
+ status: "passed" | "failed" | "unknown";
26
+ method: "curve" | "transfer";
27
+ venue: string;
28
+ buyTaxPercent: number | null;
29
+ sellTaxPercent: number | null;
30
+ roundTripPercent: number | null;
31
+ depth: SellDepth | null;
32
+ detail: string;
33
+ }
34
+ /** Same code as tokens whose simulated sell failed. */
35
+ export interface CodeMatch {
36
+ flagged: number;
37
+ passed: number;
38
+ examples: Array<{
39
+ address: string;
40
+ symbol: string | null;
41
+ }>;
42
+ }
43
+ export interface TokenReport {
44
+ address: string;
45
+ chainName: string;
46
+ isContract: boolean;
47
+ codeSize: number;
48
+ token: {
49
+ name: string | null;
50
+ symbol: string | null;
51
+ decimals: number | null;
52
+ totalSupply: string | null;
53
+ };
54
+ owner: {
55
+ status: "none" | "renounced" | "active";
56
+ address: string | null;
57
+ holdsPercent: number | null;
58
+ };
59
+ proxy: {
60
+ kind: "none" | "eip1967" | "beacon" | "minimal";
61
+ implementation: string | null;
62
+ };
63
+ burnedPercent: number | null;
64
+ checks: TokenCheck[];
65
+ verdict: Verdict;
66
+ distribution: {
67
+ status: "full" | "summary" | "unavailable";
68
+ top10Percent: number | null;
69
+ largestWallet: {
70
+ address: string;
71
+ percent: number;
72
+ } | null;
73
+ holders: Array<{
74
+ address: string;
75
+ balance: string;
76
+ percent: number;
77
+ kind: "wallet" | "contract" | "burn";
78
+ }>;
79
+ };
80
+ sellTest: SellTest | null;
81
+ lookalikes: Array<{
82
+ address: string;
83
+ name: string | null;
84
+ symbol: string | null;
85
+ createdAt: string | null;
86
+ }>;
87
+ fingerprint: string | null;
88
+ codeMatch: CodeMatch | null;
89
+ limits: string[];
90
+ }
91
+ export interface AddressInfo {
92
+ address: string;
93
+ kind: "wallet" | "burn" | "token" | "curve" | "pool" | "contract";
94
+ label: string | null;
95
+ known: boolean;
96
+ txCount: number | null;
97
+ firstSeen: string | null;
98
+ ageDays: number | null;
99
+ }
100
+ export interface WalletApproval {
101
+ token: string;
102
+ tokenName: string | null;
103
+ tokenSymbol: string | null;
104
+ balance: string | null;
105
+ spender: AddressInfo;
106
+ unlimited: boolean;
107
+ allowance: string;
108
+ approvedAt: string | null;
109
+ txHash: string | null;
110
+ risk: "ok" | "warn" | "risk";
111
+ reasons: string[];
112
+ }
113
+ export interface WalletReport {
114
+ address: string;
115
+ isContract: boolean;
116
+ eth: string | null;
117
+ holdings: Array<{
118
+ token: string;
119
+ name: string | null;
120
+ symbol: string | null;
121
+ balance: string;
122
+ }>;
123
+ holdingsTotal: number;
124
+ approvals: WalletApproval[];
125
+ status: {
126
+ holdings: "ok" | "unavailable";
127
+ approvals: "ok" | "unavailable";
128
+ };
129
+ checkedAt: string;
130
+ limits: string[];
131
+ }
132
+ /** A report plus where to see it and how many calls are left today. */
133
+ export interface Checked<T> {
134
+ report: T;
135
+ url: string;
136
+ remaining: number;
137
+ limit: number;
138
+ }
139
+ export declare const WEBHOOK_EVENTS: readonly ["owner_changed", "supply_changed", "upgraded", "pause_changed", "liquidity_dropped", "verdict_changed"];
140
+ export type WebhookEventType = (typeof WEBHOOK_EVENTS)[number];
141
+ export interface Webhook {
142
+ id: string;
143
+ url: string;
144
+ tokens: string[];
145
+ events: WebhookEventType[];
146
+ createdAt: string;
147
+ status: "active" | "disabled";
148
+ disabledAt: string | null;
149
+ failures: number;
150
+ delivered: number;
151
+ lastDeliveryAt: string | null;
152
+ lastResult: string | null;
153
+ /** In a status lookup: when Mofu last read each token. */
154
+ watching?: Array<{
155
+ address: string;
156
+ symbol: string | null;
157
+ verdict: Verdict | null;
158
+ lastChecked: string | null;
159
+ lastScanned: string | null;
160
+ }>;
161
+ }
162
+ /** What Mofu POSTs to a webhook URL. */
163
+ export interface WebhookEvent {
164
+ id: string;
165
+ type: WebhookEventType | "ping" | "test";
166
+ createdAt: string;
167
+ token: {
168
+ address: string;
169
+ name: string | null;
170
+ symbol: string | null;
171
+ } | null;
172
+ summary: string;
173
+ before: unknown;
174
+ after: unknown;
175
+ report: string | null;
176
+ webhook: string;
177
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /** Report shapes returned by Mofu's API (mofu.works/api/v1). */
2
+ export const WEBHOOK_EVENTS = [
3
+ "owner_changed",
4
+ "supply_changed",
5
+ "upgraded",
6
+ "pause_changed",
7
+ "liquidity_dropped",
8
+ "verdict_changed",
9
+ ];
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "mofu-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Robinhood Chain token and wallet checks for agents, bots and scripts: Mofu's Token Scanner, Wallet Health, webhooks and a buy rule.",
5
+ "license": "MIT",
6
+ "homepage": "https://mofu.works/docs/#sdk",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/dev-mofu/mofu.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/dev-mofu/mofu/issues"
14
+ },
15
+ "keywords": [
16
+ "robinhood-chain",
17
+ "token-scanner",
18
+ "honeypot",
19
+ "web3",
20
+ "trading-agent",
21
+ "webhooks",
22
+ "security"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.build.json",
41
+ "prepack": "tsc -p tsconfig.build.json"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.17"
45
+ }
46
+ }