Skip to main content
Glama

write.account.stake

Idempotent

Stake, unstake, or claim rewards for Uniswap/Aerodrome LP positions in one atomic transaction using Arcadia Finance's position management system.

Instructions

Flash-action: stake, unstake, or claim rewards for an LP position in one atomic transaction. Use the action parameter to select the operation. asset_address is the position manager contract — pass the non-staked PM address when staking, or the staked PM address when unstaking. The returned calldata is time-sensitive — sign and broadcast within 30 seconds. If the transaction reverts due to price movement, rebuild and sign again immediately (retry at least once before giving up). Tenderly simulation may not be available for this endpoint — verify the position exists with read.account.info before signing.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
account_addressYesArcadia account address
actionYesAction to perform
asset_addressYesPosition manager contract address
asset_idYesNFT token ID of the LP position
chain_idNoChain ID: 8453 (Base) or 130 (Unichain)

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
afterNo
beforeNo
descriptionNo
transactionYes
tenderly_sim_urlNo
tenderly_sim_statusNo
expected_value_changeNo

Implementation Reference

  • The 'write.account.stake' tool is registered via registerStakeTool. The handler logic is defined directly within the registration, processing the stake/unstake/claim actions and interacting with the Arcadia API.
    export function registerStakeTool(server: McpServer, api: ArcadiaApiClient) {
      server.registerTool(
        "write.account.stake",
        {
          annotations: {
            title: "Build Position Action Transaction",
            readOnlyHint: false,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
          outputSchema: BatchedTransactionOutput,
          description:
            "Flash-action: stake, unstake, or claim rewards for an LP position in one atomic transaction. Use the `action` parameter to select the operation. `asset_address` is the position manager contract — pass the non-staked PM address when staking, or the staked PM address when unstaking. The returned calldata is time-sensitive — sign and broadcast within 30 seconds. If the transaction reverts due to price movement, rebuild and sign again immediately (retry at least once before giving up). Tenderly simulation may not be available for this endpoint — verify the position exists with read.account.info before signing.",
          inputSchema: {
            account_address: z.string().describe("Arcadia account address"),
            action: z.enum(["stake", "unstake", "claim"]).describe("Action to perform"),
            asset_address: z.string().describe("Position manager contract address"),
            asset_id: z.number().describe("NFT token ID of the LP position"),
            chain_id: z.number().default(8453).describe("Chain ID: 8453 (Base) or 130 (Unichain)"),
          },
        },
        async ({ account_address, action, asset_address, asset_id, chain_id }) => {
          try {
            validateAddress(account_address, "account_address");
            validateAddress(asset_address, "asset_address");
            if (action === "claim") {
              const result = await api.getClaimCalldata({
                chain_id,
                account_address,
                asset_address,
                asset_id,
              });
    
              const claimRes = result as Record<string, unknown>;
              if (claimRes.tenderly_sim_status === "false") {
                const simUrl = claimRes.tenderly_sim_url
                  ? `\nTenderly simulation: ${claimRes.tenderly_sim_url}`
                  : "";
                const simError = claimRes.tenderly_sim_error
                  ? `\nRevert reason: ${claimRes.tenderly_sim_error}`
                  : "";
                return {
                  content: [
                    {
                      type: "text" as const,
                      text: `Error: Transaction simulation FAILED — do NOT broadcast.${simError}${simUrl}`,
                    },
                  ],
                  isError: true,
                };
              }
    
              const claimResponse = formatBatchedResponse(claimRes, chain_id, "Claim staking rewards");
              return {
                content: [
                  {
                    type: "text" as const,
                    text: JSON.stringify(claimResponse, null, 2),
                  },
                ],
                structuredContent: claimResponse,
              };
            }
    
            const result = await api.getStakeCalldata({
              chain_id,
              account_address,
              asset_address,
              asset_id,
            });
    
            const res = result as Record<string, unknown>;
            if (res.tenderly_sim_status === "false") {
              const simUrl = res.tenderly_sim_url
                ? `\nTenderly simulation: ${res.tenderly_sim_url}`
                : "";
              const simError = res.tenderly_sim_error
                ? `\nRevert reason: ${res.tenderly_sim_error}`
                : "";
              return {
                content: [
                  {
                    type: "text" as const,
                    text: `Error: Transaction simulation FAILED — do NOT broadcast.${simError}${simUrl}`,
                  },
                ],
                isError: true,
              };
            }
    
            const response = formatBatchedResponse(res, chain_id, "Stake LP position");
            return {
              content: [
                {
                  type: "text" as const,
                  text: JSON.stringify(response, null, 2),
                },
              ],
              structuredContent: response,
            };
          } catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            const hint =
              msg.includes("500") || msg.includes("Web3")
                ? " This usually means the position (asset_id) does not exist in the account. Verify with read.account.info first."
                : "";
            return {
              content: [{ type: "text" as const, text: `Error: ${msg}${hint}` }],
              isError: true,
            };
          }
        },
      );
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

While annotations declare idempotentHint=true and readOnlyHint=false, the description adds critical operational details: 30-second time-sensitivity for calldata, specific failure modes (price movement reverts), simulation unavailability warning, and atomic transaction guarantees. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four dense sentences with zero waste. Front-loaded with operation definition, followed by parameter semantics, time constraints, retry logic, and prerequisites. Every clause provides actionable information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the presence of output schema, complex multi-state operation (stake/unstake/claim), and annotations, the description achieves completeness by covering operational constraints, failure modes, and prerequisites without needing to describe return values.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema coverage, baseline is 3. The description adds crucial semantic mapping for asset_address (distinguishing staked vs non-staked PM addresses based on action) and clarifies the action parameter's role in determining the operation type. Could add context on chain_id defaults but otherwise excellent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with specific verbs (stake, unstake, claim) and resource (LP position), clearly defining the scope. The 'Flash-action' and 'atomic transaction' qualifiers distinguish it from standard position management tools like add_liquidity or remove_liquidity in the sibling list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit parameter semantics for asset_address (staked vs non-staked PM addresses), names a prerequisite tool (verify with read.account.info before signing), and specifies retry behavior ('retry at least once before giving up'). This creates clear decision boundaries versus alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arcadia-finance/arcadia-finance-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server