@elizaos/plugin-wallet 2.0.0-beta.1
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 +21 -0
- package/README.md +64 -0
- package/auto-enable.ts +76 -0
- package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
- package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
- package/dist/aerodrome-CfnESC32.mjs +890 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/index.d.mts +34727 -0
- package/dist/index.mjs +21590 -0
- package/dist/lib/server-wallet-trade.d.mts +34 -0
- package/dist/lib/server-wallet-trade.mjs +306 -0
- package/dist/meteora-BPX39hZo.mjs +22640 -0
- package/dist/orca-Bybp1HXO.mjs +249 -0
- package/dist/pancakeswp-CkEXlXti.mjs +604 -0
- package/dist/plugin-ZO_MTyd0.mjs +529 -0
- package/dist/raydium-rfaM9yEf.mjs +539 -0
- package/dist/sdk/index.d.mts +32492 -0
- package/dist/sdk/index.mjs +6415 -0
- package/dist/types-D5252NZk.mjs +487 -0
- package/dist/uniswap-CReXgXVN.mjs +573 -0
- package/dist/wallet-action.d.mts +6 -0
- package/dist/wallet-action.mjs +820 -0
- package/package.json +152 -0
- package/src/actions/failure-codes.ts +79 -0
- package/src/actions/index.ts +1 -0
- package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
- package/src/analytics/birdeye/birdeye-task.ts +175 -0
- package/src/analytics/birdeye/birdeye.ts +813 -0
- package/src/analytics/birdeye/constants.ts +74 -0
- package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
- package/src/analytics/birdeye/providers/market.ts +227 -0
- package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
- package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
- package/src/analytics/birdeye/providers/trending.ts +365 -0
- package/src/analytics/birdeye/providers/wallet.ts +14 -0
- package/src/analytics/birdeye/search-category.test.ts +207 -0
- package/src/analytics/birdeye/search-category.ts +506 -0
- package/src/analytics/birdeye/service.ts +992 -0
- package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
- package/src/analytics/birdeye/types/api/common.ts +305 -0
- package/src/analytics/birdeye/types/api/defi.ts +220 -0
- package/src/analytics/birdeye/types/api/pair.ts +200 -0
- package/src/analytics/birdeye/types/api/search.ts +86 -0
- package/src/analytics/birdeye/types/api/token.ts +635 -0
- package/src/analytics/birdeye/types/api/trader.ts +76 -0
- package/src/analytics/birdeye/types/api/wallet.ts +181 -0
- package/src/analytics/birdeye/types/shared.ts +106 -0
- package/src/analytics/birdeye/utils.ts +700 -0
- package/src/analytics/dexscreener/errors.ts +28 -0
- package/src/analytics/dexscreener/index.ts +3 -0
- package/src/analytics/dexscreener/search-category.test.ts +49 -0
- package/src/analytics/dexscreener/search-category.ts +42 -0
- package/src/analytics/dexscreener/service.ts +595 -0
- package/src/analytics/dexscreener/types.ts +128 -0
- package/src/analytics/lpinfo/index.d.ts +7 -0
- package/src/analytics/lpinfo/index.ts +52 -0
- package/src/analytics/lpinfo/kamino/README.md +102 -0
- package/src/analytics/lpinfo/kamino/index.ts +24 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
- package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
- package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
- package/src/analytics/lpinfo/steer/README.md +169 -0
- package/src/analytics/lpinfo/steer/index.ts +23 -0
- package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
- package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
- package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
- package/src/analytics/news/index.ts +52 -0
- package/src/analytics/news/interfaces/types.ts +222 -0
- package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
- package/src/analytics/news/services/newsDataService.ts +332 -0
- package/src/analytics/news/utils/formatters.ts +151 -0
- package/src/analytics/token-info/action.ts +240 -0
- package/src/analytics/token-info/index.ts +3 -0
- package/src/analytics/token-info/params.ts +215 -0
- package/src/analytics/token-info/providers.ts +681 -0
- package/src/analytics/token-info/service.ts +168 -0
- package/src/analytics/token-info/types.ts +74 -0
- package/src/audit/audit-log.ts +45 -0
- package/src/browser-shim/build-shim.ts +123 -0
- package/src/browser-shim/index.ts +5 -0
- package/src/browser-shim/shim.template.js +563 -0
- package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
- package/src/chains/evm/LICENSE +21 -0
- package/src/chains/evm/README.md +106 -0
- package/src/chains/evm/actions/helpers.ts +147 -0
- package/src/chains/evm/actions/swap.ts +839 -0
- package/src/chains/evm/actions/transfer.ts +254 -0
- package/src/chains/evm/biome.json +61 -0
- package/src/chains/evm/bridge-router.ts +660 -0
- package/src/chains/evm/build.ts +89 -0
- package/src/chains/evm/chain-handler.ts +416 -0
- package/src/chains/evm/constants.ts +23 -0
- package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
- package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
- package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
- package/src/chains/evm/dex/aerodrome/index.ts +34 -0
- package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
- package/src/chains/evm/dex/aerodrome/types.ts +318 -0
- package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
- package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
- package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
- package/src/chains/evm/dex/uniswap/index.ts +35 -0
- package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
- package/src/chains/evm/dex/uniswap/types.ts +390 -0
- package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
- package/src/chains/evm/generated/specs/specs.ts +151 -0
- package/src/chains/evm/gov-router.ts +250 -0
- package/src/chains/evm/index.browser.ts +16 -0
- package/src/chains/evm/index.ts +31 -0
- package/src/chains/evm/prompts.ts +193 -0
- package/src/chains/evm/providers/get-balance.ts +123 -0
- package/src/chains/evm/providers/wallet.ts +715 -0
- package/src/chains/evm/routes/sign.ts +333 -0
- package/src/chains/evm/rpc-providers.ts +410 -0
- package/src/chains/evm/service.ts +140 -0
- package/src/chains/evm/templates/index.ts +10 -0
- package/src/chains/evm/types/index.ts +432 -0
- package/src/chains/evm/vitest.config.ts +18 -0
- package/src/chains/registry.ts +668 -0
- package/src/chains/solana/README.md +367 -0
- package/src/chains/wallet-action.ts +533 -0
- package/src/chains/wallet-router.test.ts +296 -0
- package/src/contracts.ts +65 -0
- package/src/core-augmentation.ts +10 -0
- package/src/index.ts +71 -0
- package/src/lib/server-wallet-trade.ts +192 -0
- package/src/lib/wallet-export-guard.ts +330 -0
- package/src/lp/actions/liquidity.ts +827 -0
- package/src/lp/e2e/real-token-tests.ts +428 -0
- package/src/lp/e2e/scenarios.ts +470 -0
- package/src/lp/e2e/test-utils.ts +145 -0
- package/src/lp/lp-manager-entry.ts +303 -0
- package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
- package/src/lp/services/DexInteractionService.ts +226 -0
- package/src/lp/services/LpManagementService.test.ts +148 -0
- package/src/lp/services/LpManagementService.ts +632 -0
- package/src/lp/services/UserLpProfileService.ts +163 -0
- package/src/lp/services/VaultService.ts +153 -0
- package/src/lp/services/YieldOptimizationService.ts +344 -0
- package/src/lp/services/__tests__/MockLpService.ts +146 -0
- package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
- package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
- package/src/lp/types.ts +582 -0
- package/src/lp/utils/solanaClient.ts +143 -0
- package/src/plugin.ts +125 -0
- package/src/policy/policy.ts +19 -0
- package/src/providers/canonical-provider.ts +27 -0
- package/src/providers/unified-wallet-provider.ts +79 -0
- package/src/register-routes.ts +11 -0
- package/src/routes/plugin.ts +47 -0
- package/src/routes/wallet-market-overview-route.ts +869 -0
- package/src/sdk/abi.ts +258 -0
- package/src/sdk/bridge/abis.ts +126 -0
- package/src/sdk/bridge/client.ts +518 -0
- package/src/sdk/bridge/index.ts +56 -0
- package/src/sdk/bridge/solana.ts +604 -0
- package/src/sdk/bridge/types.ts +202 -0
- package/src/sdk/convenience.ts +347 -0
- package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
- package/src/sdk/escrow/types.ts +64 -0
- package/src/sdk/escrow/verifiers.ts +73 -0
- package/src/sdk/identity/erc8004.ts +692 -0
- package/src/sdk/identity/reputation.ts +449 -0
- package/src/sdk/identity/uaid.ts +497 -0
- package/src/sdk/identity/validation.ts +372 -0
- package/src/sdk/index.ts +763 -0
- package/src/sdk/policy/SpendingPolicy.ts +260 -0
- package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
- package/src/sdk/router/PaymentRouter.ts +215 -0
- package/src/sdk/router/index.ts +8 -0
- package/src/sdk/swap/SwapModule.ts +310 -0
- package/src/sdk/swap/abi.ts +117 -0
- package/src/sdk/swap/index.ts +34 -0
- package/src/sdk/swap/types.ts +135 -0
- package/src/sdk/tokens/decimals.ts +140 -0
- package/src/sdk/tokens/registry.ts +911 -0
- package/src/sdk/tokens/solana.ts +419 -0
- package/src/sdk/tokens/transfers.ts +327 -0
- package/src/sdk/types.ts +158 -0
- package/src/sdk/wallet-core.ts +115 -0
- package/src/sdk/x402/budget.ts +168 -0
- package/src/sdk/x402/chains/abstract/index.ts +280 -0
- package/src/sdk/x402/client.ts +320 -0
- package/src/sdk/x402/index.ts +46 -0
- package/src/sdk/x402/middleware.ts +92 -0
- package/src/sdk/x402/multi-asset.ts +144 -0
- package/src/sdk/x402/types.ts +156 -0
- package/src/services/wallet-backend-service.ts +328 -0
- package/src/types/wallet-router.ts +227 -0
- package/src/utils/intent-trajectory.ts +106 -0
- package/src/wallet/backend.ts +62 -0
- package/src/wallet/errors.ts +49 -0
- package/src/wallet/index.ts +27 -0
- package/src/wallet/local-eoa-backend.ts +201 -0
- package/src/wallet/pending.ts +60 -0
- package/src/wallet/select-backend.ts +47 -0
- package/src/wallet/steward-backend.ts +161 -0
- package/src/wallet-action.ts +1 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
|
|
2
|
+
import type { Plugin } from "@elizaos/core";
|
|
3
|
+
// Kamino Protocol Plugin
|
|
4
|
+
import { kaminoPlugin } from "./kamino";
|
|
5
|
+
// Steer Finance Plugin
|
|
6
|
+
import { steerPlugin } from "./steer";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Liquidity Pool Information Plugin
|
|
10
|
+
*
|
|
11
|
+
* A comprehensive plugin that provides liquidity pool management and analytics
|
|
12
|
+
* for multiple DeFi protocols on Solana.
|
|
13
|
+
*
|
|
14
|
+
* Supported Protocols:
|
|
15
|
+
* - Steer Finance: Vault and staking pool management
|
|
16
|
+
* - Kamino Protocol: Lending and liquidity pool management
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Multi-protocol liquidity pool tracking
|
|
20
|
+
* - Yield optimization analytics
|
|
21
|
+
* - Position tracking and management
|
|
22
|
+
* - Market data and statistics
|
|
23
|
+
*
|
|
24
|
+
* @author ElizaOS
|
|
25
|
+
* @version 1.0.0
|
|
26
|
+
*/
|
|
27
|
+
export const lpinfoPlugin: Plugin = {
|
|
28
|
+
name: "lpinfo",
|
|
29
|
+
description:
|
|
30
|
+
"Comprehensive liquidity pool information plugin supporting Steer Finance and Kamino Protocol for pool tracking, yield optimization, and position management",
|
|
31
|
+
providers: [
|
|
32
|
+
...(steerPlugin.providers || []),
|
|
33
|
+
...(kaminoPlugin.providers || []),
|
|
34
|
+
],
|
|
35
|
+
actions: [...(steerPlugin.actions || []), ...(kaminoPlugin.actions || [])],
|
|
36
|
+
services: [...(steerPlugin.services || []), ...(kaminoPlugin.services || [])],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default lpinfoPlugin;
|
|
40
|
+
|
|
41
|
+
export { kaminoPlugin } from "./kamino";
|
|
42
|
+
export * from "./kamino/providers/kaminoLiquidityProvider";
|
|
43
|
+
export * from "./kamino/providers/kaminoPoolProvider";
|
|
44
|
+
// Export Kamino components
|
|
45
|
+
export * from "./kamino/providers/kaminoProvider";
|
|
46
|
+
export * from "./kamino/services/kaminoLiquidityService";
|
|
47
|
+
export * from "./kamino/services/kaminoService";
|
|
48
|
+
// Re-export sub-plugins for granular control if needed
|
|
49
|
+
export { steerPlugin } from "./steer";
|
|
50
|
+
// Export Steer components
|
|
51
|
+
export * from "./steer/providers/steerLiquidityProvider";
|
|
52
|
+
export * from "./steer/services/steerLiquidityService";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Kamino Lending Protocol Plugin
|
|
2
|
+
|
|
3
|
+
This plugin provides comprehensive integration with the Kamino lending protocol on Solana, allowing users to view their lending and borrowing positions, market data, and available opportunities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### 📊 Position Tracking
|
|
8
|
+
|
|
9
|
+
- **Lending Positions**: View all your active lending positions across Kamino markets
|
|
10
|
+
- **Borrowing Positions**: Track your borrowing positions and interest rates
|
|
11
|
+
- **Portfolio Value**: Calculate total portfolio value including lending and borrowing
|
|
12
|
+
- **Multi-Wallet Support**: View positions for all Solana wallets in your account
|
|
13
|
+
|
|
14
|
+
### 🏦 Market Data
|
|
15
|
+
|
|
16
|
+
- **Available Reserves**: Browse all available lending and borrowing opportunities
|
|
17
|
+
- **APY Rates**: View current supply and borrow APY rates
|
|
18
|
+
- **Market Overview**: Get comprehensive market statistics and TVL data
|
|
19
|
+
- **Top Opportunities**: Identify the best lending opportunities by APY
|
|
20
|
+
|
|
21
|
+
### 📈 Analytics
|
|
22
|
+
|
|
23
|
+
- **Market Utilization**: Track utilization rates across different markets
|
|
24
|
+
- **Total Value Locked**: Monitor TVL across all Kamino markets
|
|
25
|
+
- **Borrowing Activity**: View total borrowed amounts and market health
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
The Kamino plugin is designed to work in private messages (DMs) for security. When you send a message in a DM, the plugin will automatically:
|
|
30
|
+
|
|
31
|
+
1. **Extract your wallet addresses** from your connected Solana wallets
|
|
32
|
+
2. **Fetch your positions** from all Kamino markets
|
|
33
|
+
3. **Display a comprehensive report** including:
|
|
34
|
+
- Your lending and borrowing positions
|
|
35
|
+
- Available lending opportunities
|
|
36
|
+
- Market overview and statistics
|
|
37
|
+
|
|
38
|
+
## Provider Information
|
|
39
|
+
|
|
40
|
+
The plugin provides the following information through the `KAMINO_LENDING` provider:
|
|
41
|
+
|
|
42
|
+
### User Positions
|
|
43
|
+
|
|
44
|
+
- **Lending Positions**: Token, amount, value, APY, and market for each position
|
|
45
|
+
- **Borrowing Positions**: Token, amount, value, APY, and market for each position
|
|
46
|
+
- **Total Portfolio Value**: Net value of all positions
|
|
47
|
+
|
|
48
|
+
### Available Reserves
|
|
49
|
+
|
|
50
|
+
- **Top Lending Opportunities**: Highest APY lending options
|
|
51
|
+
- **Reserve Details**: Supply/borrow APY, total supply/borrow, utilization rates
|
|
52
|
+
- **Market Information**: Which market each reserve belongs to
|
|
53
|
+
|
|
54
|
+
### Market Overview
|
|
55
|
+
|
|
56
|
+
- **Total Markets**: Number of active Kamino markets
|
|
57
|
+
- **Total TVL**: Combined total value locked across all markets
|
|
58
|
+
- **Total Borrowed**: Total amount borrowed across all markets
|
|
59
|
+
- **Top Markets**: Markets with highest TVL
|
|
60
|
+
|
|
61
|
+
## Technical Details
|
|
62
|
+
|
|
63
|
+
### Dependencies
|
|
64
|
+
|
|
65
|
+
- `@solana/web3.js`: Solana blockchain interaction
|
|
66
|
+
- `@hubbleprotocol/kamino-sdk`: Official Kamino SDK for protocol interaction
|
|
67
|
+
|
|
68
|
+
### Environment Variables
|
|
69
|
+
|
|
70
|
+
- `SOLANA_RPC_URL`: Solana RPC endpoint (defaults to mainnet-beta)
|
|
71
|
+
|
|
72
|
+
### Service Architecture
|
|
73
|
+
|
|
74
|
+
- **KaminoService**: Handles all Kamino protocol interactions
|
|
75
|
+
- **Provider**: Formats and presents data to the agent
|
|
76
|
+
- **Account Integration**: Uses existing account system for wallet management
|
|
77
|
+
|
|
78
|
+
## Security
|
|
79
|
+
|
|
80
|
+
- **Private Messages Only**: Position data is only available in DMs
|
|
81
|
+
- **Account Verification**: Requires verified account with connected wallets
|
|
82
|
+
- **No Private Keys**: Only uses public wallet addresses for queries
|
|
83
|
+
|
|
84
|
+
## Error Handling
|
|
85
|
+
|
|
86
|
+
The plugin includes comprehensive error handling for:
|
|
87
|
+
|
|
88
|
+
- Network connectivity issues
|
|
89
|
+
- Invalid wallet addresses
|
|
90
|
+
- Missing account data
|
|
91
|
+
- API rate limits
|
|
92
|
+
- Market data unavailability
|
|
93
|
+
|
|
94
|
+
## Future Enhancements
|
|
95
|
+
|
|
96
|
+
Potential future features:
|
|
97
|
+
|
|
98
|
+
- Position management actions (deposit, withdraw, borrow, repay)
|
|
99
|
+
- Yield optimization recommendations
|
|
100
|
+
- Historical position tracking
|
|
101
|
+
- Risk assessment and alerts
|
|
102
|
+
- Integration with other DeFi protocols
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
|
|
2
|
+
import type { Plugin } from "@elizaos/core";
|
|
3
|
+
import { kaminoLiquidityProvider } from "./providers/kaminoLiquidityProvider";
|
|
4
|
+
import { kaminoPoolProvider } from "./providers/kaminoPoolProvider";
|
|
5
|
+
// Providers
|
|
6
|
+
import { kaminoProvider } from "./providers/kaminoProvider";
|
|
7
|
+
import { KaminoLiquidityService } from "./services/kaminoLiquidityService";
|
|
8
|
+
// Services
|
|
9
|
+
import { KaminoService } from "./services/kaminoService";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Kamino Protocol Plugin
|
|
13
|
+
* Provides comprehensive access to Kamino lending and liquidity protocols
|
|
14
|
+
*/
|
|
15
|
+
export const kaminoPlugin: Plugin = {
|
|
16
|
+
name: "kamino-protocol",
|
|
17
|
+
description:
|
|
18
|
+
"Comprehensive Kamino protocol integration for viewing lending positions, liquidity pools, and market analytics. Supports position tracking and yield optimization.",
|
|
19
|
+
providers: [kaminoProvider, kaminoLiquidityProvider, kaminoPoolProvider],
|
|
20
|
+
actions: [],
|
|
21
|
+
services: [KaminoService, KaminoLiquidityService],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default kaminoPlugin;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
// @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
|
|
2
|
+
import type { IAgentRuntime, Memory, Provider, State } from "@elizaos/core";
|
|
3
|
+
import { ModelType } from "@elizaos/core";
|
|
4
|
+
import type { KaminoLiquidityService } from "../services/kaminoLiquidityService";
|
|
5
|
+
|
|
6
|
+
const KAMINO_LIQUIDITY_TEXT_LIMIT = 4000;
|
|
7
|
+
|
|
8
|
+
// Import the KaminoStrategy type from the service
|
|
9
|
+
interface KaminoStrategy {
|
|
10
|
+
address: string;
|
|
11
|
+
dataSize: number;
|
|
12
|
+
lamports: number;
|
|
13
|
+
owner: string;
|
|
14
|
+
strategyType: string;
|
|
15
|
+
estimatedTvl: number;
|
|
16
|
+
volume24h: number;
|
|
17
|
+
apy: number;
|
|
18
|
+
tokenA: string;
|
|
19
|
+
tokenB: string;
|
|
20
|
+
feeTier: string;
|
|
21
|
+
rebalancing: string;
|
|
22
|
+
lastRebalance: string;
|
|
23
|
+
positions: unknown[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function asPromptRecord(value: unknown): Record<string, unknown> {
|
|
27
|
+
return value && typeof value === "object"
|
|
28
|
+
? (value as Record<string, unknown>)
|
|
29
|
+
: {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatTokenInfoForPrompt(tokenInfo: unknown): string {
|
|
33
|
+
if (!tokenInfo) {
|
|
34
|
+
return "token_status: not resolved";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const token = asPromptRecord(tokenInfo);
|
|
38
|
+
const fields = [
|
|
39
|
+
["token_status", "resolved"],
|
|
40
|
+
["name", token.name],
|
|
41
|
+
["symbol", token.symbol],
|
|
42
|
+
["address", token.address],
|
|
43
|
+
["price_usd", token.price],
|
|
44
|
+
["liquidity_usd", token.liquidity],
|
|
45
|
+
["market_cap_usd", token.marketCap],
|
|
46
|
+
["volume_24h_usd", token.volume24h],
|
|
47
|
+
["price_change_24h_percent", token.priceChange24h],
|
|
48
|
+
["decimals", token.decimals],
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
return fields
|
|
52
|
+
.filter(
|
|
53
|
+
([, value]) => value !== undefined && value !== null && value !== "",
|
|
54
|
+
)
|
|
55
|
+
.map(([key, value]) => `${key}: ${formatPromptValue(value)}`)
|
|
56
|
+
.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatMarketStatsForPrompt(marketStats: unknown): string {
|
|
60
|
+
if (!marketStats) {
|
|
61
|
+
return "market_status: not available";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stats = asPromptRecord(marketStats);
|
|
65
|
+
const stakingYields = asPromptRecord(stats.stakingYields);
|
|
66
|
+
const medianYields = asPromptRecord(stats.medianYields);
|
|
67
|
+
const limoTrades = asPromptRecord(stats.limoTrades);
|
|
68
|
+
|
|
69
|
+
return [
|
|
70
|
+
"market_status: available",
|
|
71
|
+
`timestamp: ${formatPromptValue(stats.timestamp)}`,
|
|
72
|
+
`staking_yields_total: ${formatPromptValue(stakingYields.total)}`,
|
|
73
|
+
`staking_yields_average_apy: ${formatPromptValue(stakingYields.averageApy)}`,
|
|
74
|
+
`staking_yields_max_apy: ${formatPromptValue(stakingYields.maxApy)}`,
|
|
75
|
+
`staking_yields_min_apy: ${formatPromptValue(stakingYields.minApy)}`,
|
|
76
|
+
`median_yields_total: ${formatPromptValue(medianYields.total)}`,
|
|
77
|
+
`median_yields_average_apy: ${formatPromptValue(medianYields.averageApy)}`,
|
|
78
|
+
`limo_trades_total: ${formatPromptValue(limoTrades.total)}`,
|
|
79
|
+
`limo_trades_total_volume_usd: ${formatPromptValue(limoTrades.totalVolume)}`,
|
|
80
|
+
`limo_trades_average_tip_usd: ${formatPromptValue(limoTrades.averageTip)}`,
|
|
81
|
+
`limo_trades_average_surplus_usd: ${formatPromptValue(limoTrades.averageSurplus)}`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatPromptValue(value: unknown): string {
|
|
86
|
+
if (value === null || value === undefined) {
|
|
87
|
+
return "N/A";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof value === "number") {
|
|
91
|
+
return Number.isFinite(value) ? String(value) : "N/A";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof value === "string") {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof value === "boolean") {
|
|
99
|
+
return String(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Kamino Liquidity Protocol Provider
|
|
107
|
+
* Provides information about Kamino liquidity pools and strategies
|
|
108
|
+
*/
|
|
109
|
+
export const kaminoLiquidityProvider: Provider = {
|
|
110
|
+
name: "KAMINO_LIQUIDITY",
|
|
111
|
+
description:
|
|
112
|
+
"Provides information about Kamino liquidity pools, strategies, and token-specific liquidity data",
|
|
113
|
+
descriptionCompressed:
|
|
114
|
+
"provide information Kamino liquidity pool, strategy, token-specific liquidity data",
|
|
115
|
+
dynamic: true,
|
|
116
|
+
contexts: ["finance", "crypto", "wallet"],
|
|
117
|
+
contextGate: { anyOf: ["finance", "crypto", "wallet"] },
|
|
118
|
+
cacheStable: false,
|
|
119
|
+
cacheScope: "turn",
|
|
120
|
+
roleGate: { minRole: "USER" },
|
|
121
|
+
get: async (runtime: IAgentRuntime, message: Memory, _state: State) => {
|
|
122
|
+
console.log("KAMINO_LIQUIDITY provider called");
|
|
123
|
+
|
|
124
|
+
let liquidityInfo = "";
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Extract token address from message content
|
|
128
|
+
const content = message.content.text || "";
|
|
129
|
+
const tokenMatch = content.match(/([A-Za-z0-9]{32,44})/);
|
|
130
|
+
|
|
131
|
+
let tokenIdentifier = "";
|
|
132
|
+
if (tokenMatch) {
|
|
133
|
+
tokenIdentifier = tokenMatch[1];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get Kamino liquidity service
|
|
137
|
+
const kaminoLiquidityService = runtime.getService(
|
|
138
|
+
"KAMINO_LIQUIDITY_SERVICE",
|
|
139
|
+
) as KaminoLiquidityService;
|
|
140
|
+
if (!kaminoLiquidityService) {
|
|
141
|
+
liquidityInfo += "❌ Kamino liquidity service not available.\n";
|
|
142
|
+
} else {
|
|
143
|
+
if (tokenIdentifier) {
|
|
144
|
+
console.log(`Token identifier found: ${tokenIdentifier}`);
|
|
145
|
+
|
|
146
|
+
liquidityInfo += `=== KAMINO LIQUIDITY POOL STATS ===\n\n`;
|
|
147
|
+
liquidityInfo += `Token: ${tokenIdentifier}\n\n`;
|
|
148
|
+
|
|
149
|
+
// Resolve token information using Birdeye through the service
|
|
150
|
+
const tokenInfo =
|
|
151
|
+
await kaminoLiquidityService.resolveTokenWithBirdeye(
|
|
152
|
+
tokenIdentifier,
|
|
153
|
+
);
|
|
154
|
+
if (tokenInfo) {
|
|
155
|
+
liquidityInfo += `🔍 Token Resolution via Birdeye:\n`;
|
|
156
|
+
liquidityInfo += ` 📝 Name: ${tokenInfo.name}\n`;
|
|
157
|
+
liquidityInfo += ` 🔖 Symbol: ${tokenInfo.symbol}\n`;
|
|
158
|
+
liquidityInfo += ` 🔗 Address: ${tokenInfo.address}\n`;
|
|
159
|
+
liquidityInfo += ` 💵 Price: $${tokenInfo.price?.toFixed(6) || "N/A"}\n`;
|
|
160
|
+
liquidityInfo += ` 💧 Liquidity: $${tokenInfo.liquidity?.toLocaleString() || "N/A"}\n`;
|
|
161
|
+
liquidityInfo += ` 📊 Market Cap: $${tokenInfo.marketCap?.toLocaleString() || "N/A"}\n`;
|
|
162
|
+
liquidityInfo += ` 📈 24h Change: ${tokenInfo.priceChange24h?.toFixed(2) || "N/A"}%\n\n`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Get liquidity pool stats for the specific token using optimized method
|
|
166
|
+
const poolStats = await getKaminoLiquidityStats(
|
|
167
|
+
kaminoLiquidityService,
|
|
168
|
+
tokenIdentifier,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Generate enhanced response using LLM
|
|
172
|
+
const enhancedReport = await generateEnhancedKaminoLiquidityReport(
|
|
173
|
+
runtime,
|
|
174
|
+
{
|
|
175
|
+
tokenIdentifier,
|
|
176
|
+
tokenInfo,
|
|
177
|
+
poolStats,
|
|
178
|
+
kaminoLiquidityService,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
liquidityInfo += enhancedReport;
|
|
183
|
+
} else {
|
|
184
|
+
// No specific token provided, show protocol overview without wasting RPC calls
|
|
185
|
+
liquidityInfo += `=== KAMINO LIQUIDITY PROTOCOL OVERVIEW ===\n\n`;
|
|
186
|
+
liquidityInfo += `🔍 Kamino Liquidity Protocol Information\n\n`;
|
|
187
|
+
|
|
188
|
+
// Use testConnection to get basic info without making expensive RPC calls
|
|
189
|
+
const testResults = await kaminoLiquidityService.testConnection();
|
|
190
|
+
|
|
191
|
+
liquidityInfo += `📊 Protocol Status:\n`;
|
|
192
|
+
liquidityInfo += ` ✅ Connection: ${testResults.connectionTest ? "Connected" : "Failed"}\n`;
|
|
193
|
+
liquidityInfo += ` 📋 Program ID: ${testResults.programId}\n`;
|
|
194
|
+
liquidityInfo += ` 🔗 RPC Endpoint: ${testResults.rpcEndpoint}\n`;
|
|
195
|
+
liquidityInfo += ` 📈 Available Strategies: ${testResults.strategyCount}\n\n`;
|
|
196
|
+
|
|
197
|
+
// Add general Kamino liquidity protocol info
|
|
198
|
+
liquidityInfo += await getKaminoProtocolInfo(kaminoLiquidityService);
|
|
199
|
+
|
|
200
|
+
// Add usage instructions
|
|
201
|
+
liquidityInfo += `💡 How to use:\n`;
|
|
202
|
+
liquidityInfo += ` • Provide a token address to search for specific liquidity pools\n`;
|
|
203
|
+
liquidityInfo += ` • Example: "Check Kamino liquidity for HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC"\n`;
|
|
204
|
+
liquidityInfo += ` • Visit https://app.kamino.finance/liquidity to view all pools\n\n`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error("Error in Kamino liquidity provider:", error);
|
|
209
|
+
liquidityInfo = `Error generating Kamino liquidity report: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const data = {
|
|
213
|
+
kaminoLiquidity: liquidityInfo,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const text = `${liquidityInfo}\n`.slice(0, KAMINO_LIQUIDITY_TEXT_LIMIT);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
data,
|
|
220
|
+
values: {} as Record<string, unknown>,
|
|
221
|
+
text,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get Kamino liquidity pool statistics for a specific token using optimized method
|
|
228
|
+
*/
|
|
229
|
+
async function getKaminoLiquidityStats(
|
|
230
|
+
kaminoLiquidityService: KaminoLiquidityService,
|
|
231
|
+
tokenIdentifier: string,
|
|
232
|
+
): Promise<string> {
|
|
233
|
+
let statsInfo = "";
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
statsInfo += `🔍 SEARCHING FOR KAMINO LIQUIDITY POOLS...\n\n`;
|
|
237
|
+
|
|
238
|
+
// Use the optimized getTokenLiquidityStats method that uses filters and getStrategyByAddress
|
|
239
|
+
const tokenStats =
|
|
240
|
+
await kaminoLiquidityService.getTokenLiquidityStats(tokenIdentifier);
|
|
241
|
+
|
|
242
|
+
if (tokenStats.strategies.length > 0) {
|
|
243
|
+
statsInfo += `📊 FOUND ${tokenStats.strategies.length} RELEVANT STRATEGIES:\n\n`;
|
|
244
|
+
statsInfo += `Token: ${tokenStats.tokenName}\n`;
|
|
245
|
+
statsInfo += `Total TVL: $${tokenStats.totalTvl.toLocaleString()}\n`;
|
|
246
|
+
statsInfo += `24h Volume: $${tokenStats.totalVolume.toLocaleString()}\n`;
|
|
247
|
+
statsInfo += `APY Range: ${tokenStats.apyRange.min.toFixed(2)}% - ${tokenStats.apyRange.max.toFixed(2)}%\n\n`;
|
|
248
|
+
|
|
249
|
+
// Group strategies by type for better organization
|
|
250
|
+
const strategyTypes = new Map<string, KaminoStrategy[]>();
|
|
251
|
+
tokenStats.strategies.forEach((strategy) => {
|
|
252
|
+
const type = strategy.strategyType;
|
|
253
|
+
if (!strategyTypes.has(type)) {
|
|
254
|
+
strategyTypes.set(type, []);
|
|
255
|
+
}
|
|
256
|
+
strategyTypes.get(type)?.push(strategy);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
for (const [type, strategies] of strategyTypes) {
|
|
260
|
+
statsInfo += `🏊♂️ ${type.toUpperCase()} (${strategies.length} strategies):\n`;
|
|
261
|
+
const totalTvl = strategies.reduce(
|
|
262
|
+
(sum, s) => sum + (s.estimatedTvl || 0),
|
|
263
|
+
0,
|
|
264
|
+
);
|
|
265
|
+
const avgApy =
|
|
266
|
+
strategies.reduce((sum, s) => sum + (s.apy || 0), 0) /
|
|
267
|
+
strategies.length;
|
|
268
|
+
statsInfo += ` 💰 Total TVL: $${totalTvl.toLocaleString()}\n`;
|
|
269
|
+
statsInfo += ` 🎯 Average APY: ${avgApy.toFixed(2)}%\n\n`;
|
|
270
|
+
|
|
271
|
+
// Show details for each strategy in this type
|
|
272
|
+
for (const strategy of strategies) {
|
|
273
|
+
statsInfo += await getStrategyDetails(strategy);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Add direct link to Kamino app for found strategies
|
|
278
|
+
statsInfo += `🔗 **View on Kamino:** https://app.kamino.finance/liquidity\n\n`;
|
|
279
|
+
} else {
|
|
280
|
+
statsInfo += `❌ No Kamino liquidity strategies found for ${tokenIdentifier}\n\n`;
|
|
281
|
+
statsInfo += `🔍 Analysis Results:\n`;
|
|
282
|
+
statsInfo += ` • Token: ${tokenStats.tokenName}\n`;
|
|
283
|
+
statsInfo += ` • Searched through Kamino liquidity program with optimized filters\n`;
|
|
284
|
+
statsInfo += ` • No strategies containing this token were found\n\n`;
|
|
285
|
+
statsInfo += `💡 Possible reasons:\n`;
|
|
286
|
+
statsInfo += ` • Token may not be listed on Kamino liquidity pools\n`;
|
|
287
|
+
statsInfo += ` • Token might be too new or have low liquidity\n`;
|
|
288
|
+
statsInfo += ` • Token may be listed under a different address\n`;
|
|
289
|
+
statsInfo += ` • Token might be in a different strategy type\n\n`;
|
|
290
|
+
statsInfo += `🔗 Check available strategies at: https://app.kamino.finance/liquidity\n`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Add general Kamino liquidity protocol info
|
|
294
|
+
statsInfo += await getKaminoProtocolInfo(kaminoLiquidityService);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error("Error getting Kamino liquidity stats:", error);
|
|
297
|
+
statsInfo += `❌ Error fetching liquidity data: ${error instanceof Error ? error.message : "Unknown error"}\n`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return statsInfo;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get detailed information about a specific strategy
|
|
305
|
+
*/
|
|
306
|
+
async function getStrategyDetails(strategy: KaminoStrategy): Promise<string> {
|
|
307
|
+
let details = ` 🏊♂️ STRATEGY: ${strategy.address}\n`;
|
|
308
|
+
details += ` 📈 Type: ${strategy.strategyType}\n`;
|
|
309
|
+
details += ` 💰 TVL: $${strategy.estimatedTvl.toLocaleString()}\n`;
|
|
310
|
+
details += ` 📊 24h Volume: $${strategy.volume24h.toLocaleString()}\n`;
|
|
311
|
+
details += ` 🎯 APY: ${strategy.apy.toFixed(2)}%\n`;
|
|
312
|
+
details += ` 🔄 Rebalancing: ${strategy.rebalancing}\n`;
|
|
313
|
+
details += ` 💸 Fee Tier: ${strategy.feeTier}\n`;
|
|
314
|
+
details += ` 🕒 Last Rebalance: ${new Date(strategy.lastRebalance).toLocaleDateString()}\n`;
|
|
315
|
+
|
|
316
|
+
if (strategy.positions && strategy.positions.length > 0) {
|
|
317
|
+
details += ` 📍 Positions:\n`;
|
|
318
|
+
for (const position of strategy.positions) {
|
|
319
|
+
details += ` • ${position.type}: ${position.range} ($${position.liquidity.toLocaleString()})\n`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
details += `\n`;
|
|
324
|
+
|
|
325
|
+
return details;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Get general Kamino protocol information
|
|
330
|
+
*/
|
|
331
|
+
async function getKaminoProtocolInfo(
|
|
332
|
+
kaminoLiquidityService: KaminoLiquidityService,
|
|
333
|
+
): Promise<string> {
|
|
334
|
+
let info = `🌊 KAMINO LIQUIDITY PROTOCOL INFO:\n\n`;
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const testResults = await kaminoLiquidityService.testConnection();
|
|
338
|
+
|
|
339
|
+
info += `📋 Program ID: ${testResults.programId}\n`;
|
|
340
|
+
info += `🔗 RPC Endpoint: ${testResults.rpcEndpoint}\n`;
|
|
341
|
+
info += `✅ Connection Status: ${testResults.connectionTest ? "Connected" : "Failed"}\n`;
|
|
342
|
+
info += `📊 Strategy Count: ${testResults.strategyCount}\n\n`;
|
|
343
|
+
|
|
344
|
+
info += `🔗 Useful Links:\n`;
|
|
345
|
+
info += ` • Kamino App: https://app.kamino.finance/liquidity\n`;
|
|
346
|
+
info += ` • Documentation: https://docs.kamino.finance\n`;
|
|
347
|
+
info += ` • GitHub: https://github.com/Kamino-Finance\n\n`;
|
|
348
|
+
|
|
349
|
+
info += `💡 How to use:\n`;
|
|
350
|
+
info += ` • Visit the Kamino app to view all available liquidity pools\n`;
|
|
351
|
+
info += ` • Deposit tokens to earn yield from automated market making\n`;
|
|
352
|
+
info += ` • Strategies automatically rebalance to maintain optimal positions\n`;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error("Error getting protocol info:", error);
|
|
355
|
+
info += `❌ Error fetching protocol information\n`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return info;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Generate enhanced Kamino liquidity report using LLM
|
|
363
|
+
*/
|
|
364
|
+
async function generateEnhancedKaminoLiquidityReport(
|
|
365
|
+
runtime: IAgentRuntime,
|
|
366
|
+
data: {
|
|
367
|
+
tokenIdentifier: string;
|
|
368
|
+
tokenInfo: unknown;
|
|
369
|
+
poolStats: string;
|
|
370
|
+
kaminoLiquidityService: KaminoLiquidityService;
|
|
371
|
+
},
|
|
372
|
+
): Promise<string> {
|
|
373
|
+
try {
|
|
374
|
+
// Get additional market context
|
|
375
|
+
const marketStats = await data.kaminoLiquidityService.getMarketStatistics();
|
|
376
|
+
|
|
377
|
+
// Create a focused prompt for the LLM
|
|
378
|
+
const liquidityPrompt = `You are a professional DeFi analyst specializing in Kamino Finance liquidity protocols. Generate a comprehensive, well-crafted analysis report for the token ${data.tokenIdentifier}.
|
|
379
|
+
|
|
380
|
+
TOKEN INFORMATION:
|
|
381
|
+
${formatTokenInfoForPrompt(data.tokenInfo)}
|
|
382
|
+
|
|
383
|
+
POOL STATISTICS:
|
|
384
|
+
${data.poolStats}
|
|
385
|
+
|
|
386
|
+
MARKET CONTEXT:
|
|
387
|
+
${formatMarketStatsForPrompt(marketStats)}
|
|
388
|
+
|
|
389
|
+
Please generate a professional, engaging report that includes:
|
|
390
|
+
|
|
391
|
+
1. **Token Overview** - Brief introduction to the token and its market position
|
|
392
|
+
2. **Liquidity Analysis** - Detailed breakdown of Kamino liquidity pools and strategies
|
|
393
|
+
3. **Performance Metrics** - TVL, APY ranges, volume analysis, and key performance indicators
|
|
394
|
+
4. **Strategy Assessment** - Analysis of different strategy types (staking, Limo trading) and their effectiveness
|
|
395
|
+
5. **Market Insights** - How this token's liquidity compares to market trends
|
|
396
|
+
6. **Risk & Opportunity Analysis** - Key risks and opportunities for liquidity providers
|
|
397
|
+
7. **Investment Recommendations** - Clear, actionable insights for potential investors
|
|
398
|
+
|
|
399
|
+
Format the report with:
|
|
400
|
+
- Clear sections with descriptive headers
|
|
401
|
+
- Use emojis for visual appeal and quick scanning
|
|
402
|
+
- Include specific numbers and percentages
|
|
403
|
+
- Provide professional but engaging tone
|
|
404
|
+
- Focus on actionable insights
|
|
405
|
+
- Include relevant comparisons to market standards
|
|
406
|
+
- End with a concise summary
|
|
407
|
+
|
|
408
|
+
Make it comprehensive yet easy to read. Be specific about the data and provide clear insights about this particular token's liquidity situation on Kamino Finance.
|
|
409
|
+
|
|
410
|
+
Generate a professional Kamino liquidity analysis report:`;
|
|
411
|
+
|
|
412
|
+
// Use LLM to generate the enhanced report
|
|
413
|
+
const enhancedReport = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
414
|
+
prompt: liquidityPrompt,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return enhancedReport || data.poolStats;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error("Error generating enhanced Kamino liquidity report:", error);
|
|
420
|
+
return data.poolStats; // Fallback to original stats
|
|
421
|
+
}
|
|
422
|
+
}
|