@solworks/poll-mcp 0.1.20 → 0.1.26
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/dist/index.js
CHANGED
|
@@ -3,9 +3,12 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { PollClient } from '@solworks/poll-api-client';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import { TOOL_DEFINITIONS } from './tools/index.js';
|
|
7
8
|
import { RESOURCE_DEFINITIONS } from './resources/index.js';
|
|
8
9
|
import { PROMPT_DEFINITIONS } from './prompts/index.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version: PKG_VERSION } = require('../package.json');
|
|
9
12
|
/**
|
|
10
13
|
* Convert a JSON Schema property type to a Zod schema.
|
|
11
14
|
*/
|
|
@@ -23,7 +26,7 @@ function jsonPropToZod(prop) {
|
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
export function createServer(config, clientOverride) {
|
|
26
|
-
const server = new McpServer({ name: 'poll-fun', version:
|
|
29
|
+
const server = new McpServer({ name: 'poll-fun', version: PKG_VERSION }, { capabilities: { tools: {}, resources: { listChanged: true }, prompts: {} } });
|
|
27
30
|
const client = clientOverride ??
|
|
28
31
|
new PollClient({
|
|
29
32
|
apiUrl: config?.apiUrl ?? 'https://api.poll.fun',
|
package/dist/tools/index.js
CHANGED
|
@@ -437,6 +437,10 @@ export const TOOL_DEFINITIONS = {
|
|
|
437
437
|
if (!betAddress)
|
|
438
438
|
return errorResult('Missing required parameter: betAddress');
|
|
439
439
|
try {
|
|
440
|
+
const bet = await client.getBet(betAddress);
|
|
441
|
+
if (!bet.isPublic) {
|
|
442
|
+
return errorResult('This is a private bet. You can only join private bets via the share link in the app.');
|
|
443
|
+
}
|
|
440
444
|
await client.joinBet(betAddress);
|
|
441
445
|
return textResult(`Successfully joined bet: ${betAddress}`);
|
|
442
446
|
}
|
|
@@ -486,19 +490,23 @@ export const TOOL_DEFINITIONS = {
|
|
|
486
490
|
type: 'object',
|
|
487
491
|
properties: {
|
|
488
492
|
betAddress: { type: 'string', description: 'Address of the bet' },
|
|
489
|
-
wagerAddress: { type: 'string', description: 'Address of the wager to cancel' },
|
|
490
493
|
},
|
|
491
|
-
required: ['betAddress'
|
|
494
|
+
required: ['betAddress'],
|
|
492
495
|
},
|
|
493
496
|
requiredScope: 'bet:write',
|
|
494
497
|
async handler(args, client) {
|
|
495
498
|
const betAddress = args.betAddress;
|
|
496
|
-
const wagerAddress = args.wagerAddress;
|
|
497
499
|
if (!betAddress)
|
|
498
500
|
return errorResult('Missing required parameter: betAddress');
|
|
499
|
-
if (!wagerAddress)
|
|
500
|
-
return errorResult('Missing required parameter: wagerAddress');
|
|
501
501
|
try {
|
|
502
|
+
const wagers = await client.getMyWagers();
|
|
503
|
+
const activeWager = wagers.find((w) => {
|
|
504
|
+
const addr = w.betAddress ?? w.programBetBetAddress;
|
|
505
|
+
return addr === betAddress && w.amount > 0;
|
|
506
|
+
});
|
|
507
|
+
if (!activeWager) {
|
|
508
|
+
return errorResult(`You don't have an active wager on bet: ${betAddress}`);
|
|
509
|
+
}
|
|
502
510
|
await client.cancelWager({ marketPubkey: betAddress });
|
|
503
511
|
return textResult(`Wager cancelled successfully for bet: ${betAddress}`);
|
|
504
512
|
}
|
|
@@ -523,6 +531,11 @@ export const TOOL_DEFINITIONS = {
|
|
|
523
531
|
if (!betAddress)
|
|
524
532
|
return errorResult('Missing required parameter: betAddress');
|
|
525
533
|
try {
|
|
534
|
+
const bet = await client.getBet(betAddress);
|
|
535
|
+
const status = (bet.status ?? '').toLowerCase();
|
|
536
|
+
if (status === 'resolving' || status === 'resolved' || status === 'distributed') {
|
|
537
|
+
return errorResult(`Vote has already been initiated for this bet (status: ${bet.status})`);
|
|
538
|
+
}
|
|
526
539
|
await client.initiateVote(betAddress);
|
|
527
540
|
return textResult(`Vote initiated for bet: ${betAddress}`);
|
|
528
541
|
}
|
|
@@ -551,6 +564,18 @@ export const TOOL_DEFINITIONS = {
|
|
|
551
564
|
if (optionIndex === undefined || optionIndex === null)
|
|
552
565
|
return errorResult('Missing required parameter: optionIndex');
|
|
553
566
|
try {
|
|
567
|
+
const [bet, account] = await Promise.all([
|
|
568
|
+
client.getBet(betAddress),
|
|
569
|
+
client.getAccount(),
|
|
570
|
+
]);
|
|
571
|
+
const status = (bet.status ?? '').toLowerCase();
|
|
572
|
+
if (status !== 'resolving') {
|
|
573
|
+
return errorResult(`Cannot vote: voting has not been initiated for this bet (status: ${bet.status}). Use initiate_vote first.`);
|
|
574
|
+
}
|
|
575
|
+
const alreadyVoted = Array.isArray(bet.votes) && bet.votes.some((v) => v.user === account.uuid || v.userUuid === account.uuid);
|
|
576
|
+
if (alreadyVoted) {
|
|
577
|
+
return errorResult('You have already voted on this bet');
|
|
578
|
+
}
|
|
554
579
|
const outcome = optionIndex === 0 ? 'for' : 'against';
|
|
555
580
|
await client.vote({ marketPubkey: betAddress, outcome: outcome });
|
|
556
581
|
return textResult(`Vote cast successfully on bet ${betAddress} for option ${optionIndex} (${outcome})`);
|
|
@@ -576,6 +601,24 @@ export const TOOL_DEFINITIONS = {
|
|
|
576
601
|
if (!betAddress)
|
|
577
602
|
return errorResult('Missing required parameter: betAddress');
|
|
578
603
|
try {
|
|
604
|
+
const bet = await client.getBet(betAddress);
|
|
605
|
+
const status = (bet.status ?? '').toLowerCase();
|
|
606
|
+
if (status === 'distributed') {
|
|
607
|
+
return errorResult('This bet has already been settled and distributed');
|
|
608
|
+
}
|
|
609
|
+
if (status !== 'resolving' && status !== 'resolved') {
|
|
610
|
+
return errorResult(`Bet cannot be settled in its current status: ${bet.status}. Vote must be initiated first.`);
|
|
611
|
+
}
|
|
612
|
+
const wagerUsers = new Set((bet.wagers ?? []).filter((w) => w.amount > 0).map((w) => w.user));
|
|
613
|
+
const votedUsers = new Set((bet.votes ?? []).map((v) => v.user));
|
|
614
|
+
const notVoted = [...wagerUsers].filter((u) => !votedUsers.has(u));
|
|
615
|
+
if (notVoted.length > 0) {
|
|
616
|
+
const names = notVoted.map((u) => {
|
|
617
|
+
const wager = bet.wagers.find((w) => w.user === u);
|
|
618
|
+
return wager?.userDisplayName ?? u;
|
|
619
|
+
});
|
|
620
|
+
return errorResult(`Cannot settle: ${notVoted.length} participant(s) have not voted yet: ${names.join(', ')}`);
|
|
621
|
+
}
|
|
579
622
|
await client.settleBet(betAddress);
|
|
580
623
|
return textResult(`Bet settled successfully: ${betAddress}`);
|
|
581
624
|
}
|
|
@@ -610,6 +653,36 @@ export const TOOL_DEFINITIONS = {
|
|
|
610
653
|
},
|
|
611
654
|
},
|
|
612
655
|
// ── Social write tools (scope: social:write) ──────────────────────
|
|
656
|
+
get_friend_requests: {
|
|
657
|
+
name: 'get_friend_requests',
|
|
658
|
+
description: 'Get your pending friend requests (received and sent)',
|
|
659
|
+
inputSchema: { type: 'object', properties: {} },
|
|
660
|
+
requiredScope: 'read',
|
|
661
|
+
async handler(_args, client) {
|
|
662
|
+
try {
|
|
663
|
+
const [received, sent] = await Promise.all([
|
|
664
|
+
client.getReceivedFriendRequests(),
|
|
665
|
+
client.getSentFriendRequests(),
|
|
666
|
+
]);
|
|
667
|
+
const receivedLines = received.length > 0
|
|
668
|
+
? received.map((r, i) => ` ${i + 1}. From: ${r.sender?.displayName ?? 'Unknown'} (${r.sender?.uuid ?? '?'}) — Request ID: ${r.id} — ${r.createdAt}`)
|
|
669
|
+
: [' None'];
|
|
670
|
+
const sentLines = sent.length > 0
|
|
671
|
+
? sent.map((r, i) => ` ${i + 1}. To: ${r.receiver?.displayName ?? 'Unknown'} (${r.receiver?.uuid ?? '?'}) — Request ID: ${r.id} — ${r.createdAt}`)
|
|
672
|
+
: [' None'];
|
|
673
|
+
return textResult([
|
|
674
|
+
`Received Friend Requests (${received.length}):`,
|
|
675
|
+
...receivedLines,
|
|
676
|
+
'',
|
|
677
|
+
`Sent Friend Requests (${sent.length}):`,
|
|
678
|
+
...sentLines,
|
|
679
|
+
].join('\n'));
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
return errorResult(err.message);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
},
|
|
613
686
|
send_friend_request: {
|
|
614
687
|
name: 'send_friend_request',
|
|
615
688
|
description: 'Send a friend request to another user',
|
|
@@ -678,6 +751,10 @@ export const TOOL_DEFINITIONS = {
|
|
|
678
751
|
if (!betAddress)
|
|
679
752
|
return errorResult('Missing required parameter: betAddress');
|
|
680
753
|
try {
|
|
754
|
+
const alreadyFavourited = await client.isBetFavourited(betAddress);
|
|
755
|
+
if (alreadyFavourited) {
|
|
756
|
+
return textResult(`Bet is already in your favourites: ${betAddress}`);
|
|
757
|
+
}
|
|
681
758
|
await client.favouriteBet(betAddress);
|
|
682
759
|
return textResult(`Bet added to favourites: ${betAddress}`);
|
|
683
760
|
}
|
|
@@ -702,6 +779,10 @@ export const TOOL_DEFINITIONS = {
|
|
|
702
779
|
if (!betAddress)
|
|
703
780
|
return errorResult('Missing required parameter: betAddress');
|
|
704
781
|
try {
|
|
782
|
+
const isFavourited = await client.isBetFavourited(betAddress);
|
|
783
|
+
if (!isFavourited) {
|
|
784
|
+
return textResult(`Bet is not in your favourites: ${betAddress}`);
|
|
785
|
+
}
|
|
705
786
|
await client.unfavouriteBet(betAddress);
|
|
706
787
|
return textResult(`Bet removed from favourites: ${betAddress}`);
|
|
707
788
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solworks/poll-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "MCP server for Poll.fun. See documentation at https://dev.poll.fun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
21
|
-
"@solworks/poll-api-client": "
|
|
21
|
+
"@solworks/poll-api-client": "^0.1.17",
|
|
22
22
|
"zod": "^3.24.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
@@ -19,13 +19,14 @@ function mockClient(overrides: Partial<PollClient> = {}): PollClient {
|
|
|
19
19
|
getXpBalance: vi.fn().mockResolvedValue({ xp: 1000, error: null }),
|
|
20
20
|
getUsdcBalance: vi.fn().mockResolvedValue({ usdc: 50, error: null }),
|
|
21
21
|
getTrendingBets: vi.fn().mockResolvedValue([
|
|
22
|
-
{ question: 'Trending bet 1',
|
|
22
|
+
{ question: 'Trending bet 1', totalOiFor: 50, totalOiAgainst: 50, betAddress: 'addr1' },
|
|
23
23
|
]),
|
|
24
24
|
getLeaderboard: vi.fn().mockResolvedValue([
|
|
25
25
|
{ userId: 1, uuid: 'u1', displayName: 'Top', rank: 1, points: 999 },
|
|
26
26
|
]),
|
|
27
27
|
getMyBets: vi.fn().mockResolvedValue([]),
|
|
28
28
|
getMyWagers: vi.fn().mockResolvedValue([]),
|
|
29
|
+
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
29
30
|
...overrides,
|
|
30
31
|
} as unknown as PollClient;
|
|
31
32
|
}
|
|
@@ -81,6 +82,7 @@ describe('MCP Server integration', () => {
|
|
|
81
82
|
'update_display_name',
|
|
82
83
|
'send_friend_request',
|
|
83
84
|
'respond_friend_request',
|
|
85
|
+
'get_friend_requests',
|
|
84
86
|
'favourite_bet',
|
|
85
87
|
'unfavourite_bet',
|
|
86
88
|
];
|
|
@@ -88,7 +90,7 @@ describe('MCP Server integration', () => {
|
|
|
88
90
|
for (const name of expectedTools) {
|
|
89
91
|
expect(toolNames).toContain(name);
|
|
90
92
|
}
|
|
91
|
-
expect(tools.length).toBe(
|
|
93
|
+
expect(tools.length).toBe(28);
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
it('does NOT expose excluded tools', async () => {
|
|
@@ -138,6 +140,17 @@ describe('MCP Server integration', () => {
|
|
|
138
140
|
expect(prompts.length).toBe(3);
|
|
139
141
|
});
|
|
140
142
|
|
|
143
|
+
it('can read a resource and re-read returns fresh data', async () => {
|
|
144
|
+
const result1 = await mcpClient.readResource({ uri: 'poll://user/balances' });
|
|
145
|
+
expect(result1.contents[0].text).toContain('XP');
|
|
146
|
+
|
|
147
|
+
const result2 = await mcpClient.readResource({ uri: 'poll://user/balances' });
|
|
148
|
+
expect(result2.contents[0].text).toContain('XP');
|
|
149
|
+
|
|
150
|
+
// Verify handler was called twice (no server-side caching)
|
|
151
|
+
expect(client.getXpBalance).toHaveBeenCalledTimes(2);
|
|
152
|
+
});
|
|
153
|
+
|
|
141
154
|
it('handles API errors gracefully', async () => {
|
|
142
155
|
const errorClient = mockClient({
|
|
143
156
|
getAccount: vi.fn().mockRejectedValue(new Error('API Error 401: Unauthorized')),
|
|
@@ -10,8 +10,8 @@ describe('Bet tool handlers', () => {
|
|
|
10
10
|
it('get_trending_bets returns formatted list', async () => {
|
|
11
11
|
const client = mockClient({
|
|
12
12
|
getTrendingBets: vi.fn().mockResolvedValue([
|
|
13
|
-
{ question: 'Will BTC hit 100k?',
|
|
14
|
-
{ question: 'ETH above 5k?',
|
|
13
|
+
{ question: 'Will BTC hit 100k?', totalOiFor: 3000, totalOiAgainst: 2000, betAddress: 'addr1' },
|
|
14
|
+
{ question: 'ETH above 5k?', totalOiFor: 2000, totalOiAgainst: 1000, betAddress: 'addr2' },
|
|
15
15
|
]),
|
|
16
16
|
});
|
|
17
17
|
const result = await TOOL_DEFINITIONS.get_trending_bets.handler({}, client);
|
|
@@ -28,7 +28,8 @@ describe('Bet tool handlers', () => {
|
|
|
28
28
|
question: 'Will it rain tomorrow?',
|
|
29
29
|
status: 'OPEN',
|
|
30
30
|
options: ['Yes', 'No'],
|
|
31
|
-
|
|
31
|
+
totalOiFor: 600,
|
|
32
|
+
totalOiAgainst: 400,
|
|
32
33
|
createdAt: '2026-01-01T00:00:00Z',
|
|
33
34
|
betAddress: 'addr123',
|
|
34
35
|
}),
|
|
@@ -80,7 +81,8 @@ describe('Bet tool handlers', () => {
|
|
|
80
81
|
|
|
81
82
|
it('place_wager with valid params', async () => {
|
|
82
83
|
const placeWagerFn = vi.fn().mockResolvedValue({});
|
|
83
|
-
const
|
|
84
|
+
const validateWagerSideFn = vi.fn().mockResolvedValue(undefined);
|
|
85
|
+
const client = mockClient({ placeWager: placeWagerFn, validateWagerSide: validateWagerSideFn });
|
|
84
86
|
const result = await TOOL_DEFINITIONS.place_wager.handler(
|
|
85
87
|
{ betAddress: 'addr1', optionIndex: 0, amount: 50 },
|
|
86
88
|
client
|
|
@@ -88,9 +90,9 @@ describe('Bet tool handlers', () => {
|
|
|
88
90
|
expect(result.isError).toBeUndefined();
|
|
89
91
|
expect(result.content[0].text).toContain('Wager placed successfully');
|
|
90
92
|
expect(placeWagerFn).toHaveBeenCalledWith({
|
|
91
|
-
|
|
92
|
-
optionIndex: 0,
|
|
93
|
+
marketPubkey: 'addr1',
|
|
93
94
|
amount: 50,
|
|
95
|
+
side: 'for',
|
|
94
96
|
});
|
|
95
97
|
});
|
|
96
98
|
|
|
@@ -104,6 +106,41 @@ describe('Bet tool handlers', () => {
|
|
|
104
106
|
expect(result.content[0].text).toContain('Missing required parameter: betAddress');
|
|
105
107
|
});
|
|
106
108
|
|
|
109
|
+
it('vote returns error when voting not initiated', async () => {
|
|
110
|
+
const client = mockClient({
|
|
111
|
+
getBet: vi.fn().mockResolvedValue({
|
|
112
|
+
status: 'Pending',
|
|
113
|
+
votes: [],
|
|
114
|
+
}),
|
|
115
|
+
getAccount: vi.fn().mockResolvedValue({ uuid: 'user-1' }),
|
|
116
|
+
});
|
|
117
|
+
const result = await TOOL_DEFINITIONS.vote.handler(
|
|
118
|
+
{ betAddress: 'addr1', optionIndex: 0 },
|
|
119
|
+
client
|
|
120
|
+
);
|
|
121
|
+
expect(result.isError).toBe(true);
|
|
122
|
+
expect(result.content[0].text).toContain('voting has not been initiated');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('vote succeeds when bet is in Resolving status', async () => {
|
|
126
|
+
const voteFn = vi.fn().mockResolvedValue({});
|
|
127
|
+
const client = mockClient({
|
|
128
|
+
getBet: vi.fn().mockResolvedValue({
|
|
129
|
+
status: 'Resolving',
|
|
130
|
+
votes: [],
|
|
131
|
+
}),
|
|
132
|
+
getAccount: vi.fn().mockResolvedValue({ uuid: 'user-1' }),
|
|
133
|
+
vote: voteFn,
|
|
134
|
+
});
|
|
135
|
+
const result = await TOOL_DEFINITIONS.vote.handler(
|
|
136
|
+
{ betAddress: 'addr1', optionIndex: 0 },
|
|
137
|
+
client
|
|
138
|
+
);
|
|
139
|
+
expect(result.isError).toBeUndefined();
|
|
140
|
+
expect(result.content[0].text).toContain('Vote cast successfully');
|
|
141
|
+
expect(voteFn).toHaveBeenCalledWith({ marketPubkey: 'addr1', outcome: 'for' });
|
|
142
|
+
});
|
|
143
|
+
|
|
107
144
|
it('create_bet handles API error', async () => {
|
|
108
145
|
const client = mockClient({
|
|
109
146
|
createBet: vi.fn().mockRejectedValue(new Error('API Error 400: Invalid question')),
|
|
@@ -23,7 +23,10 @@ describe('Social tool handlers', () => {
|
|
|
23
23
|
|
|
24
24
|
it('favourite_bet calls client', async () => {
|
|
25
25
|
const favouriteFn = vi.fn().mockResolvedValue({});
|
|
26
|
-
const client = mockClient({
|
|
26
|
+
const client = mockClient({
|
|
27
|
+
isBetFavourited: vi.fn().mockResolvedValue(false),
|
|
28
|
+
favouriteBet: favouriteFn,
|
|
29
|
+
});
|
|
27
30
|
const result = await TOOL_DEFINITIONS.favourite_bet.handler(
|
|
28
31
|
{ betAddress: 'addr-fav' },
|
|
29
32
|
client
|
|
@@ -33,6 +36,51 @@ describe('Social tool handlers', () => {
|
|
|
33
36
|
expect(favouriteFn).toHaveBeenCalledWith('addr-fav');
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
it('favourite_bet returns already-favourited message', async () => {
|
|
40
|
+
const favouriteFn = vi.fn();
|
|
41
|
+
const client = mockClient({
|
|
42
|
+
isBetFavourited: vi.fn().mockResolvedValue(true),
|
|
43
|
+
favouriteBet: favouriteFn,
|
|
44
|
+
});
|
|
45
|
+
const result = await TOOL_DEFINITIONS.favourite_bet.handler(
|
|
46
|
+
{ betAddress: 'addr-fav' },
|
|
47
|
+
client
|
|
48
|
+
);
|
|
49
|
+
expect(result.isError).toBeUndefined();
|
|
50
|
+
expect(result.content[0].text).toContain('Bet is already in your favourites: addr-fav');
|
|
51
|
+
expect(favouriteFn).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('unfavourite_bet removes favourite', async () => {
|
|
55
|
+
const unfavouriteFn = vi.fn().mockResolvedValue({});
|
|
56
|
+
const client = mockClient({
|
|
57
|
+
isBetFavourited: vi.fn().mockResolvedValue(true),
|
|
58
|
+
unfavouriteBet: unfavouriteFn,
|
|
59
|
+
});
|
|
60
|
+
const result = await TOOL_DEFINITIONS.unfavourite_bet.handler(
|
|
61
|
+
{ betAddress: 'addr-fav' },
|
|
62
|
+
client
|
|
63
|
+
);
|
|
64
|
+
expect(result.isError).toBeUndefined();
|
|
65
|
+
expect(result.content[0].text).toContain('Bet removed from favourites: addr-fav');
|
|
66
|
+
expect(unfavouriteFn).toHaveBeenCalledWith('addr-fav');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('unfavourite_bet returns not-favourited message', async () => {
|
|
70
|
+
const unfavouriteFn = vi.fn();
|
|
71
|
+
const client = mockClient({
|
|
72
|
+
isBetFavourited: vi.fn().mockResolvedValue(false),
|
|
73
|
+
unfavouriteBet: unfavouriteFn,
|
|
74
|
+
});
|
|
75
|
+
const result = await TOOL_DEFINITIONS.unfavourite_bet.handler(
|
|
76
|
+
{ betAddress: 'addr-fav' },
|
|
77
|
+
client
|
|
78
|
+
);
|
|
79
|
+
expect(result.isError).toBeUndefined();
|
|
80
|
+
expect(result.content[0].text).toContain('Bet is not in your favourites: addr-fav');
|
|
81
|
+
expect(unfavouriteFn).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
36
84
|
it('send_friend_request calls client', async () => {
|
|
37
85
|
const sendFn = vi.fn().mockResolvedValue({});
|
|
38
86
|
const client = mockClient({ sendFriendRequest: sendFn });
|