amped-defi 1.0.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/README.md +757 -0
- package/dist/__mocks__/@sodax/sdk.d.ts +24 -0
- package/dist/__mocks__/@sodax/sdk.d.ts.map +1 -0
- package/dist/__mocks__/@sodax/sdk.js +24 -0
- package/dist/__mocks__/@sodax/sdk.js.map +1 -0
- package/dist/__tests__/setup.d.ts +4 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +32 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/policyEngine.d.ts +119 -0
- package/dist/policy/policyEngine.d.ts.map +1 -0
- package/dist/policy/policyEngine.js +322 -0
- package/dist/policy/policyEngine.js.map +1 -0
- package/dist/providers/spokeProviderFactory.d.ts +38 -0
- package/dist/providers/spokeProviderFactory.d.ts.map +1 -0
- package/dist/providers/spokeProviderFactory.js +212 -0
- package/dist/providers/spokeProviderFactory.js.map +1 -0
- package/dist/sodax/client.d.ts +34 -0
- package/dist/sodax/client.d.ts.map +1 -0
- package/dist/sodax/client.js +99 -0
- package/dist/sodax/client.js.map +1 -0
- package/dist/tools/bridge.d.ts +105 -0
- package/dist/tools/bridge.d.ts.map +1 -0
- package/dist/tools/bridge.js +334 -0
- package/dist/tools/bridge.js.map +1 -0
- package/dist/tools/discovery.d.ts +141 -0
- package/dist/tools/discovery.d.ts.map +1 -0
- package/dist/tools/discovery.js +777 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/moneyMarket.d.ts +227 -0
- package/dist/tools/moneyMarket.d.ts.map +1 -0
- package/dist/tools/moneyMarket.js +867 -0
- package/dist/tools/moneyMarket.js.map +1 -0
- package/dist/tools/portfolio.d.ts +43 -0
- package/dist/tools/portfolio.d.ts.map +1 -0
- package/dist/tools/portfolio.js +538 -0
- package/dist/tools/portfolio.js.map +1 -0
- package/dist/tools/swap.d.ts +71 -0
- package/dist/tools/swap.d.ts.map +1 -0
- package/dist/tools/swap.js +762 -0
- package/dist/tools/swap.js.map +1 -0
- package/dist/tools/walletManagement.d.ts +80 -0
- package/dist/tools/walletManagement.d.ts.map +1 -0
- package/dist/tools/walletManagement.js +289 -0
- package/dist/tools/walletManagement.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/errorUtils.d.ts +2 -0
- package/dist/utils/errorUtils.d.ts.map +1 -0
- package/dist/utils/errorUtils.js +19 -0
- package/dist/utils/errorUtils.js.map +1 -0
- package/dist/utils/errors.d.ts +144 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +310 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/positionAggregator.d.ts +122 -0
- package/dist/utils/positionAggregator.d.ts.map +1 -0
- package/dist/utils/positionAggregator.js +377 -0
- package/dist/utils/positionAggregator.js.map +1 -0
- package/dist/utils/priceService.d.ts +45 -0
- package/dist/utils/priceService.d.ts.map +1 -0
- package/dist/utils/priceService.js +108 -0
- package/dist/utils/priceService.js.map +1 -0
- package/dist/utils/sodaxApi.d.ts +92 -0
- package/dist/utils/sodaxApi.d.ts.map +1 -0
- package/dist/utils/sodaxApi.js +143 -0
- package/dist/utils/sodaxApi.js.map +1 -0
- package/dist/utils/tokenResolver.d.ts +54 -0
- package/dist/utils/tokenResolver.d.ts.map +1 -0
- package/dist/utils/tokenResolver.js +252 -0
- package/dist/utils/tokenResolver.js.map +1 -0
- package/dist/wallet/backendConfig.d.ts +37 -0
- package/dist/wallet/backendConfig.d.ts.map +1 -0
- package/dist/wallet/backendConfig.js +125 -0
- package/dist/wallet/backendConfig.js.map +1 -0
- package/dist/wallet/backends/BankrBackend.d.ts +73 -0
- package/dist/wallet/backends/BankrBackend.d.ts.map +1 -0
- package/dist/wallet/backends/BankrBackend.js +315 -0
- package/dist/wallet/backends/BankrBackend.js.map +1 -0
- package/dist/wallet/backends/BankrWalletProvider.d.ts +75 -0
- package/dist/wallet/backends/BankrWalletProvider.d.ts.map +1 -0
- package/dist/wallet/backends/BankrWalletProvider.js +243 -0
- package/dist/wallet/backends/BankrWalletProvider.js.map +1 -0
- package/dist/wallet/backends/EnvBackend.d.ts +50 -0
- package/dist/wallet/backends/EnvBackend.d.ts.map +1 -0
- package/dist/wallet/backends/EnvBackend.js +114 -0
- package/dist/wallet/backends/EnvBackend.js.map +1 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.d.ts +40 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.d.ts.map +1 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.js +81 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.js.map +1 -0
- package/dist/wallet/backends/index.d.ts +10 -0
- package/dist/wallet/backends/index.d.ts.map +1 -0
- package/dist/wallet/backends/index.js +10 -0
- package/dist/wallet/backends/index.js.map +1 -0
- package/dist/wallet/index.d.ts +9 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +12 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/providers/AmpedWalletProvider.d.ts +107 -0
- package/dist/wallet/providers/AmpedWalletProvider.d.ts.map +1 -0
- package/dist/wallet/providers/AmpedWalletProvider.js +208 -0
- package/dist/wallet/providers/AmpedWalletProvider.js.map +1 -0
- package/dist/wallet/providers/BankrBackend.d.ts +105 -0
- package/dist/wallet/providers/BankrBackend.d.ts.map +1 -0
- package/dist/wallet/providers/BankrBackend.js +327 -0
- package/dist/wallet/providers/BankrBackend.js.map +1 -0
- package/dist/wallet/providers/LocalKeyBackend.d.ts +62 -0
- package/dist/wallet/providers/LocalKeyBackend.d.ts.map +1 -0
- package/dist/wallet/providers/LocalKeyBackend.js +152 -0
- package/dist/wallet/providers/LocalKeyBackend.js.map +1 -0
- package/dist/wallet/providers/chainConfig.d.ts +209 -0
- package/dist/wallet/providers/chainConfig.d.ts.map +1 -0
- package/dist/wallet/providers/chainConfig.js +175 -0
- package/dist/wallet/providers/chainConfig.js.map +1 -0
- package/dist/wallet/providers/index.d.ts +30 -0
- package/dist/wallet/providers/index.d.ts.map +1 -0
- package/dist/wallet/providers/index.js +32 -0
- package/dist/wallet/providers/index.js.map +1 -0
- package/dist/wallet/providers/types.d.ts +156 -0
- package/dist/wallet/providers/types.d.ts.map +1 -0
- package/dist/wallet/providers/types.js +11 -0
- package/dist/wallet/providers/types.js.map +1 -0
- package/dist/wallet/skillWalletAdapter.d.ts +96 -0
- package/dist/wallet/skillWalletAdapter.d.ts.map +1 -0
- package/dist/wallet/skillWalletAdapter.js +280 -0
- package/dist/wallet/skillWalletAdapter.js.map +1 -0
- package/dist/wallet/types.d.ts +134 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +138 -0
- package/dist/wallet/types.js.map +1 -0
- package/dist/wallet/walletManager.d.ts +111 -0
- package/dist/wallet/walletManager.d.ts.map +1 -0
- package/dist/wallet/walletManager.js +476 -0
- package/dist/wallet/walletManager.js.map +1 -0
- package/dist/wallet/walletRegistry.d.ts +95 -0
- package/dist/wallet/walletRegistry.d.ts.map +1 -0
- package/dist/wallet/walletRegistry.js +184 -0
- package/dist/wallet/walletRegistry.js.map +1 -0
- package/index.js +2 -0
- package/openclaw.plugin.json +37 -0
- package/package.json +69 -0
- package/src/__mocks__/@sodax/sdk.ts +28 -0
- package/src/__tests__/errors.test.ts +238 -0
- package/src/__tests__/policyEngine.test.ts +354 -0
- package/src/__tests__/positionAggregator.test.ts +271 -0
- package/src/__tests__/setup.ts +35 -0
- package/src/__tests__/sodaxApi.test.ts +203 -0
- package/src/__tests__/walletRegistry.test.ts +155 -0
- package/src/index.ts +376 -0
- package/src/policy/policyEngine.ts +389 -0
- package/src/providers/spokeProviderFactory.ts +283 -0
- package/src/sodax/client.ts +113 -0
- package/src/tools/bridge.ts +425 -0
- package/src/tools/discovery.ts +989 -0
- package/src/tools/moneyMarket.ts +1265 -0
- package/src/tools/portfolio.ts +697 -0
- package/src/tools/swap.ts +926 -0
- package/src/tools/walletManagement.ts +359 -0
- package/src/types.ts +228 -0
- package/src/utils/errorUtils.ts +16 -0
- package/src/utils/errors.ts +396 -0
- package/src/utils/positionAggregator.ts +559 -0
- package/src/utils/priceService.ts +153 -0
- package/src/utils/sodaxApi.ts +261 -0
- package/src/utils/tokenResolver.ts +286 -0
- package/src/wallet/backendConfig.ts +151 -0
- package/src/wallet/backends/BankrBackend.ts +399 -0
- package/src/wallet/backends/BankrWalletProvider.ts +329 -0
- package/src/wallet/backends/EnvBackend.ts +149 -0
- package/src/wallet/backends/EvmWalletSkillBackend.ts +110 -0
- package/src/wallet/backends/index.ts +10 -0
- package/src/wallet/index.ts +14 -0
- package/src/wallet/providers/AmpedWalletProvider.ts +267 -0
- package/src/wallet/providers/BankrBackend.ts +407 -0
- package/src/wallet/providers/LocalKeyBackend.ts +184 -0
- package/src/wallet/providers/chainConfig.ts +194 -0
- package/src/wallet/providers/index.ts +62 -0
- package/src/wallet/providers/types.ts +186 -0
- package/src/wallet/skillWalletAdapter.ts +335 -0
- package/src/wallet/types.ts +248 -0
- package/src/wallet/walletManager.ts +561 -0
- package/src/wallet/walletRegistry.ts +216 -0
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Money Market Tools for Amped DeFi Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.
|
|
5
|
+
* Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).
|
|
6
|
+
*
|
|
7
|
+
* Key capabilities:
|
|
8
|
+
* - Supply: Deposit tokens as collateral on any supported chain
|
|
9
|
+
* - Borrow: Borrow tokens to any chain (cross-chain capable)
|
|
10
|
+
* - Withdraw: Withdraw supplied tokens from any chain
|
|
11
|
+
* - Repay: Repay borrowed tokens from any chain
|
|
12
|
+
* - Intent-based operations: Create intents for custom flows
|
|
13
|
+
*
|
|
14
|
+
* Cross-chain flows:
|
|
15
|
+
* 1. Supply on Chain A → Borrow to Chain B (different destination)
|
|
16
|
+
* 2. Supply on Chain A → Borrow on Chain A (same chain)
|
|
17
|
+
* 3. Cross-chain repay: Repay debt from any chain
|
|
18
|
+
* 4. Cross-chain withdraw: Withdraw collateral to any chain
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Type, Static } from "@sinclair/typebox";
|
|
22
|
+
import { getSodaxClient } from "../sodax/client";
|
|
23
|
+
import { getSpokeProvider } from "../providers/spokeProviderFactory";
|
|
24
|
+
import { PolicyEngine } from "../policy/policyEngine";
|
|
25
|
+
import { getWalletManager } from '../wallet/walletManager';
|
|
26
|
+
import { AgentTools } from "../types";
|
|
27
|
+
import { serializeError } from '../utils/errorUtils';
|
|
28
|
+
import { resolveToken, getTokenInfo } from '../utils/tokenResolver';
|
|
29
|
+
import { toSodaxChainId } from '../wallet/types';
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// TypeBox Schemas
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base schema for money market operations
|
|
37
|
+
*/
|
|
38
|
+
const MoneyMarketBaseSchema = Type.Object({
|
|
39
|
+
walletId: Type.String({
|
|
40
|
+
description: "Unique identifier for the wallet"
|
|
41
|
+
}),
|
|
42
|
+
chainId: Type.String({
|
|
43
|
+
description: "Source SODAX spoke chain ID where the operation originates (e.g., 'ethereum', 'arbitrum', 'sonic')"
|
|
44
|
+
}),
|
|
45
|
+
token: Type.String({
|
|
46
|
+
description: "Token address or symbol to supply/borrow/withdraw/repay",
|
|
47
|
+
}),
|
|
48
|
+
amount: Type.String({
|
|
49
|
+
description: "Amount in human-readable units (e.g., '100.5' for 100.5 USDC). Use '-1' for max repay (repay full debt).",
|
|
50
|
+
}),
|
|
51
|
+
timeoutMs: Type.Optional(
|
|
52
|
+
Type.Number({
|
|
53
|
+
description: "Operation timeout in milliseconds",
|
|
54
|
+
default: 180000,
|
|
55
|
+
})
|
|
56
|
+
),
|
|
57
|
+
policyId: Type.Optional(
|
|
58
|
+
Type.String({ description: "Optional policy profile identifier for custom limits" })
|
|
59
|
+
),
|
|
60
|
+
skipSimulation: Type.Optional(
|
|
61
|
+
Type.Boolean({
|
|
62
|
+
description: "Skip transaction simulation (not recommended)",
|
|
63
|
+
default: false,
|
|
64
|
+
})
|
|
65
|
+
),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Supply operation schema
|
|
70
|
+
* Supply tokens as collateral to the money market on the specified chain
|
|
71
|
+
*/
|
|
72
|
+
const MoneyMarketSupplySchema = Type.Composite([
|
|
73
|
+
MoneyMarketBaseSchema,
|
|
74
|
+
Type.Object({
|
|
75
|
+
useAsCollateral: Type.Optional(
|
|
76
|
+
Type.Boolean({
|
|
77
|
+
description: "Whether to use the supplied tokens as collateral for borrowing (default: true)",
|
|
78
|
+
default: true,
|
|
79
|
+
})
|
|
80
|
+
),
|
|
81
|
+
// Cross-chain supply options
|
|
82
|
+
dstChainId: Type.Optional(
|
|
83
|
+
Type.String({
|
|
84
|
+
description: "Optional destination chain for the supply operation. If different from chainId, performs cross-chain supply.",
|
|
85
|
+
})
|
|
86
|
+
),
|
|
87
|
+
recipient: Type.Optional(
|
|
88
|
+
Type.String({
|
|
89
|
+
description: "Optional recipient address for the supplied position (defaults to wallet address)",
|
|
90
|
+
})
|
|
91
|
+
),
|
|
92
|
+
}),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Withdraw operation schema
|
|
97
|
+
* Withdraw supplied tokens from the money market
|
|
98
|
+
*/
|
|
99
|
+
const MoneyMarketWithdrawSchema = Type.Composite([
|
|
100
|
+
MoneyMarketBaseSchema,
|
|
101
|
+
Type.Object({
|
|
102
|
+
withdrawType: Type.Optional(
|
|
103
|
+
Type.Union([
|
|
104
|
+
Type.Literal('default'),
|
|
105
|
+
Type.Literal('collateral'),
|
|
106
|
+
Type.Literal('all'),
|
|
107
|
+
], {
|
|
108
|
+
description: "Withdraw type: 'default' (standard), 'collateral' (withdraw collateral only), 'all' (withdraw maximum)",
|
|
109
|
+
default: 'default',
|
|
110
|
+
})
|
|
111
|
+
),
|
|
112
|
+
// Cross-chain withdraw options
|
|
113
|
+
dstChainId: Type.Optional(
|
|
114
|
+
Type.String({
|
|
115
|
+
description: "Optional destination chain to receive withdrawn tokens. If different from chainId, performs cross-chain withdraw.",
|
|
116
|
+
})
|
|
117
|
+
),
|
|
118
|
+
recipient: Type.Optional(
|
|
119
|
+
Type.String({
|
|
120
|
+
description: "Optional recipient address to receive withdrawn tokens (defaults to wallet address)",
|
|
121
|
+
})
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Borrow operation schema
|
|
128
|
+
* Borrow tokens from the money market
|
|
129
|
+
*
|
|
130
|
+
* Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!
|
|
131
|
+
* Example: Supply USDC on Ethereum, borrow USDT to Arbitrum
|
|
132
|
+
*/
|
|
133
|
+
const MoneyMarketBorrowSchema = Type.Composite([
|
|
134
|
+
MoneyMarketBaseSchema,
|
|
135
|
+
Type.Object({
|
|
136
|
+
interestRateMode: Type.Optional(
|
|
137
|
+
Type.Union([
|
|
138
|
+
Type.Literal(1, { description: "Stable interest rate" }),
|
|
139
|
+
Type.Literal(2, { description: "Variable interest rate (recommended)" }),
|
|
140
|
+
], {
|
|
141
|
+
description: "Interest rate mode: 1 = Stable, 2 = Variable",
|
|
142
|
+
default: 2,
|
|
143
|
+
})
|
|
144
|
+
),
|
|
145
|
+
referralCode: Type.Optional(
|
|
146
|
+
Type.String({
|
|
147
|
+
description: "Optional referral code for the borrow operation",
|
|
148
|
+
})
|
|
149
|
+
),
|
|
150
|
+
// Cross-chain borrow options (key feature!)
|
|
151
|
+
dstChainId: Type.Optional(
|
|
152
|
+
Type.String({
|
|
153
|
+
description: "Destination chain to receive borrowed tokens. If different from chainId, performs cross-chain borrow (supply on chainId, receive borrowed tokens on dstChainId).",
|
|
154
|
+
})
|
|
155
|
+
),
|
|
156
|
+
recipient: Type.Optional(
|
|
157
|
+
Type.String({
|
|
158
|
+
description: "Optional recipient address to receive borrowed tokens (defaults to wallet address on dstChainId or chainId)",
|
|
159
|
+
})
|
|
160
|
+
),
|
|
161
|
+
}),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Repay operation schema
|
|
166
|
+
* Repay borrowed tokens to the money market
|
|
167
|
+
*/
|
|
168
|
+
const MoneyMarketRepaySchema = Type.Composite([
|
|
169
|
+
MoneyMarketBaseSchema,
|
|
170
|
+
Type.Object({
|
|
171
|
+
interestRateMode: Type.Optional(
|
|
172
|
+
Type.Union([
|
|
173
|
+
Type.Literal(1, { description: "Stable interest rate" }),
|
|
174
|
+
Type.Literal(2, { description: "Variable interest rate" }),
|
|
175
|
+
], {
|
|
176
|
+
description: "Interest rate mode of the debt to repay: 1 = Stable, 2 = Variable",
|
|
177
|
+
default: 2,
|
|
178
|
+
})
|
|
179
|
+
),
|
|
180
|
+
repayAll: Type.Optional(
|
|
181
|
+
Type.Boolean({
|
|
182
|
+
description: "If true, repays the full debt amount (useful for closing position)",
|
|
183
|
+
default: false,
|
|
184
|
+
})
|
|
185
|
+
),
|
|
186
|
+
// Cross-chain repay options
|
|
187
|
+
collateralChainId: Type.Optional(
|
|
188
|
+
Type.String({
|
|
189
|
+
description: "Optional chain ID where collateral is held (for cross-chain repay scenarios)",
|
|
190
|
+
})
|
|
191
|
+
),
|
|
192
|
+
}),
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create Intent schemas for advanced users
|
|
197
|
+
* These allow building custom multi-step flows
|
|
198
|
+
*/
|
|
199
|
+
const CreateSupplyIntentSchema = Type.Composite([
|
|
200
|
+
MoneyMarketSupplySchema,
|
|
201
|
+
Type.Object({
|
|
202
|
+
raw: Type.Optional(
|
|
203
|
+
Type.Boolean({
|
|
204
|
+
description: "If true, returns raw transaction data instead of executing (for custom signing flows)",
|
|
205
|
+
default: false,
|
|
206
|
+
})
|
|
207
|
+
),
|
|
208
|
+
}),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
const CreateBorrowIntentSchema = Type.Composite([
|
|
212
|
+
MoneyMarketBorrowSchema,
|
|
213
|
+
Type.Object({
|
|
214
|
+
raw: Type.Optional(
|
|
215
|
+
Type.Boolean({
|
|
216
|
+
description: "If true, returns raw transaction data instead of executing (for custom signing flows)",
|
|
217
|
+
default: false,
|
|
218
|
+
})
|
|
219
|
+
),
|
|
220
|
+
}),
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const CreateWithdrawIntentSchema = Type.Composite([
|
|
224
|
+
MoneyMarketWithdrawSchema,
|
|
225
|
+
Type.Object({
|
|
226
|
+
raw: Type.Optional(
|
|
227
|
+
Type.Boolean({
|
|
228
|
+
description: "If true, returns raw transaction data instead of executing (for custom signing flows)",
|
|
229
|
+
default: false,
|
|
230
|
+
})
|
|
231
|
+
),
|
|
232
|
+
}),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
const CreateRepayIntentSchema = Type.Composite([
|
|
236
|
+
MoneyMarketRepaySchema,
|
|
237
|
+
Type.Object({
|
|
238
|
+
raw: Type.Optional(
|
|
239
|
+
Type.Boolean({
|
|
240
|
+
description: "If true, returns raw transaction data instead of executing (for custom signing flows)",
|
|
241
|
+
default: false,
|
|
242
|
+
})
|
|
243
|
+
),
|
|
244
|
+
}),
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Output Types
|
|
249
|
+
// ============================================================================
|
|
250
|
+
|
|
251
|
+
interface MoneyMarketOperationResult {
|
|
252
|
+
success: boolean;
|
|
253
|
+
txHash?: string;
|
|
254
|
+
status: "success" | "pending" | "failed";
|
|
255
|
+
spokeTxHash?: string;
|
|
256
|
+
hubTxHash?: string;
|
|
257
|
+
intentHash?: string;
|
|
258
|
+
operation: string;
|
|
259
|
+
chainId: string;
|
|
260
|
+
dstChainId?: string;
|
|
261
|
+
token: string;
|
|
262
|
+
amount: string;
|
|
263
|
+
message?: string;
|
|
264
|
+
warnings?: string[];
|
|
265
|
+
// Cross-chain specific
|
|
266
|
+
isCrossChain?: boolean;
|
|
267
|
+
srcSpokeTxHash?: string;
|
|
268
|
+
dstSpokeTxHash?: string;
|
|
269
|
+
// Raw intent data (for createIntent operations)
|
|
270
|
+
rawIntent?: unknown;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface IntentResult extends MoneyMarketOperationResult {
|
|
274
|
+
intentData: unknown;
|
|
275
|
+
requiresSubmission: boolean;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Helper Functions
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Converts human-readable amount to token units (wei)
|
|
284
|
+
*/
|
|
285
|
+
function parseTokenAmount(amount: string, decimals: number = 18): bigint {
|
|
286
|
+
// Handle special case for max repay
|
|
287
|
+
if (amount === '-1') {
|
|
288
|
+
return BigInt(-1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const parsed = parseFloat(amount);
|
|
292
|
+
if (isNaN(parsed)) {
|
|
293
|
+
throw new Error(`Invalid amount: ${amount}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const multiplier = Math.pow(10, decimals);
|
|
297
|
+
return BigInt(Math.floor(parsed * multiplier));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Resolves wallet and creates spoke provider for the operation
|
|
302
|
+
*/
|
|
303
|
+
async function resolveWalletAndProvider(
|
|
304
|
+
walletId: string,
|
|
305
|
+
chainId: string
|
|
306
|
+
): Promise<{
|
|
307
|
+
walletAddress: string;
|
|
308
|
+
spokeProvider: any;
|
|
309
|
+
}> {
|
|
310
|
+
const walletManager = getWalletManager();
|
|
311
|
+
const wallet = await walletManager.resolve(walletId);
|
|
312
|
+
const walletAddress = await wallet.getAddress();
|
|
313
|
+
|
|
314
|
+
const spokeProvider = await getSpokeProvider(walletId, chainId);
|
|
315
|
+
|
|
316
|
+
return { walletAddress, spokeProvider };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Common pre-operation checks and allowance handling
|
|
321
|
+
*/
|
|
322
|
+
async function prepareMoneyMarketOperation(
|
|
323
|
+
walletId: string,
|
|
324
|
+
chainId: string,
|
|
325
|
+
token: string,
|
|
326
|
+
amount: string,
|
|
327
|
+
operation: "supply" | "withdraw" | "borrow" | "repay",
|
|
328
|
+
policyId?: string
|
|
329
|
+
): Promise<{ walletAddress: string; spokeProvider: any; policyResult: any; tokenAddr: string }> {
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Hub Chain Validation
|
|
332
|
+
// ============================================================================
|
|
333
|
+
// SODAX architecture: Money market operations must be initiated from spoke chains,
|
|
334
|
+
// not the hub chain (Sonic). The hub chain is the settlement layer where contracts
|
|
335
|
+
// live, but users interact via spoke chains that relay operations to the hub.
|
|
336
|
+
// Reference: sodax-tests/tests/crossChainSdk.test.ts explicitly omits SONIC_MAINNET_CHAIN_ID
|
|
337
|
+
const isHubChainSource = chainId.toLowerCase() === 'sonic' || chainId === '146';
|
|
338
|
+
if (isHubChainSource) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Money market operations cannot be initiated from the hub chain (Sonic). ` +
|
|
341
|
+
`Please use a spoke chain (base, arbitrum, ethereum, optimism, avalanche, bsc, polygon) as the source chain.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
// Ensure sodax client is initialized
|
|
345
|
+
const _sodaxClient = getSodaxClient(); // Just verify it's ready
|
|
346
|
+
void _sodaxClient;
|
|
347
|
+
|
|
348
|
+
// Normalize chain ID to SDK format for token resolution
|
|
349
|
+
const sdkChainId = toSodaxChainId(chainId);
|
|
350
|
+
|
|
351
|
+
// Resolve token symbol to address
|
|
352
|
+
const tokenAddr = await resolveToken(sdkChainId, token);
|
|
353
|
+
|
|
354
|
+
// Resolve wallet and create spoke provider
|
|
355
|
+
const { walletAddress, spokeProvider } = await resolveWalletAndProvider(walletId, chainId);
|
|
356
|
+
|
|
357
|
+
// Policy check
|
|
358
|
+
const policyEngine = new PolicyEngine();
|
|
359
|
+
const policyResult = await policyEngine.checkMoneyMarket({
|
|
360
|
+
walletId,
|
|
361
|
+
chainId,
|
|
362
|
+
token,
|
|
363
|
+
amount, // Add required amount parameter
|
|
364
|
+
amountUsd: parseFloat(amount), // Simplified - would need actual price lookup
|
|
365
|
+
operation,
|
|
366
|
+
policyId,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (!policyResult.allowed) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Policy check failed: ${policyResult.reason || "Operation not permitted"}.`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { walletAddress, spokeProvider, policyResult, tokenAddr };
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Resolves token and returns its decimals
|
|
379
|
+
* Falls back to 18 decimals if token info not found
|
|
380
|
+
*/
|
|
381
|
+
async function getTokenDecimals(
|
|
382
|
+
chainId: string,
|
|
383
|
+
token: string
|
|
384
|
+
): Promise<number> {
|
|
385
|
+
try {
|
|
386
|
+
const sdkChainId = toSodaxChainId(chainId);
|
|
387
|
+
const tokenInfo = await getTokenInfo(sdkChainId, token);
|
|
388
|
+
return tokenInfo?.decimals ?? 18;
|
|
389
|
+
} catch {
|
|
390
|
+
// If token info lookup fails, fall back to 18 decimals
|
|
391
|
+
return 18;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Checks and handles token approval if needed
|
|
398
|
+
*/
|
|
399
|
+
async function ensureAllowance(
|
|
400
|
+
params: {
|
|
401
|
+
token: string;
|
|
402
|
+
amount: bigint;
|
|
403
|
+
action: 'supply' | 'repay';
|
|
404
|
+
},
|
|
405
|
+
spokeProvider: any,
|
|
406
|
+
raw: boolean = false
|
|
407
|
+
): Promise<{ approvalTxHash?: string; rawApproval?: unknown }> {
|
|
408
|
+
const sodaxClient = await getSodaxClient();
|
|
409
|
+
|
|
410
|
+
// Check if allowance is sufficient
|
|
411
|
+
const isAllowanceValid = await sodaxClient.moneyMarket.isAllowanceValid(
|
|
412
|
+
params,
|
|
413
|
+
spokeProvider
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (!isAllowanceValid.ok || !isAllowanceValid.value) {
|
|
417
|
+
if (raw) {
|
|
418
|
+
// Return raw approval transaction
|
|
419
|
+
const rawApproval = await sodaxClient.moneyMarket.approve(
|
|
420
|
+
params,
|
|
421
|
+
spokeProvider,
|
|
422
|
+
true // raw mode
|
|
423
|
+
);
|
|
424
|
+
return { rawApproval };
|
|
425
|
+
} else {
|
|
426
|
+
// Execute approval transaction
|
|
427
|
+
const approvalResult = await sodaxClient.moneyMarket.approve(
|
|
428
|
+
params,
|
|
429
|
+
spokeProvider,
|
|
430
|
+
false
|
|
431
|
+
);
|
|
432
|
+
// Handle Result type from SDK
|
|
433
|
+
const txHash = (approvalResult as any).ok
|
|
434
|
+
? (approvalResult as any).value
|
|
435
|
+
: (approvalResult as any).txHash || approvalResult;
|
|
436
|
+
return { approvalTxHash: String(txHash) };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Determine if operation is cross-chain
|
|
445
|
+
*/
|
|
446
|
+
function isCrossChainOperation(srcChainId: string, dstChainId?: string): boolean {
|
|
447
|
+
return !!dstChainId && dstChainId !== srcChainId;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// Tool Handlers
|
|
452
|
+
// ============================================================================
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Supply tokens to the money market
|
|
456
|
+
*
|
|
457
|
+
* Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)
|
|
458
|
+
*/
|
|
459
|
+
async function handleSupply(
|
|
460
|
+
params: Static<typeof MoneyMarketSupplySchema>
|
|
461
|
+
): Promise<MoneyMarketOperationResult> {
|
|
462
|
+
const {
|
|
463
|
+
walletId,
|
|
464
|
+
chainId,
|
|
465
|
+
token,
|
|
466
|
+
amount,
|
|
467
|
+
timeoutMs = 180000,
|
|
468
|
+
policyId,
|
|
469
|
+
useAsCollateral = true,
|
|
470
|
+
dstChainId,
|
|
471
|
+
recipient,
|
|
472
|
+
skipSimulation = false
|
|
473
|
+
} = params;
|
|
474
|
+
|
|
475
|
+
const crossChain = isCrossChainOperation(chainId, dstChainId);
|
|
476
|
+
const warnings: string[] = [];
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
// Pre-operation checks
|
|
480
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
481
|
+
walletId,
|
|
482
|
+
chainId,
|
|
483
|
+
token,
|
|
484
|
+
amount,
|
|
485
|
+
"supply",
|
|
486
|
+
policyId
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// Parse amount with actual token decimals
|
|
490
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
491
|
+
const amountBigInt = parseTokenAmount(amount, decimals);
|
|
492
|
+
|
|
493
|
+
// Check allowance for supply
|
|
494
|
+
const { approvalTxHash } = await ensureAllowance(
|
|
495
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'supply' },
|
|
496
|
+
spokeProvider
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
if (approvalTxHash) {
|
|
500
|
+
warnings.push(`Approval transaction executed: ${approvalTxHash}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const sodaxClient = await getSodaxClient();
|
|
504
|
+
|
|
505
|
+
// Build supply parameters
|
|
506
|
+
const supplyParams: any = {
|
|
507
|
+
action: 'supply',
|
|
508
|
+
token: tokenAddr,
|
|
509
|
+
amount: amountBigInt,
|
|
510
|
+
|
|
511
|
+
toAddress: recipient || walletAddress,
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// Add cross-chain parameters if applicable
|
|
515
|
+
if (crossChain && dstChainId) {
|
|
516
|
+
supplyParams.toChainId = toSodaxChainId(dstChainId);
|
|
517
|
+
warnings.push(`Cross-chain supply: tokens supplied on ${chainId}, collateral recorded on ${dstChainId}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Check and handle allowance (required for ALL supply operations)
|
|
521
|
+
// Reference: sodax-frontend moneymarket-ops.ts - supply ALWAYS checks allowance
|
|
522
|
+
try {
|
|
523
|
+
const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(
|
|
524
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'supply' },
|
|
525
|
+
spokeProvider
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (allowanceResult.ok && !allowanceResult.value) {
|
|
529
|
+
console.log('[mm:supply] Approval needed, approving...');
|
|
530
|
+
const approveResult = await (sodaxClient as any).moneyMarket.approve(
|
|
531
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'supply' },
|
|
532
|
+
spokeProvider
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (!approveResult.ok) {
|
|
536
|
+
throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Wait for approval confirmation
|
|
540
|
+
const approvalTxHash = approveResult.value;
|
|
541
|
+
if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {
|
|
542
|
+
await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);
|
|
543
|
+
console.log('[mm:supply] Approval confirmed');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch (allowanceError) {
|
|
547
|
+
console.warn('[mm:supply] Allowance check failed, proceeding anyway:', allowanceError);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Execute supply
|
|
551
|
+
const supplyResult = await (sodaxClient as any).moneyMarket.supply(
|
|
552
|
+
supplyParams,
|
|
553
|
+
spokeProvider,
|
|
554
|
+
timeoutMs
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
// Handle Result type from SDK
|
|
558
|
+
if (supplyResult.ok === false) {
|
|
559
|
+
throw new Error(`Supply failed: ${serializeError(supplyResult.error)}`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const value = supplyResult.ok ? supplyResult.value : supplyResult;
|
|
563
|
+
// SDK may return [spokeTxHash, hubTxHash] tuple
|
|
564
|
+
const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];
|
|
565
|
+
|
|
566
|
+
// Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)
|
|
567
|
+
const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;
|
|
568
|
+
const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;
|
|
569
|
+
const dstTxHash = deliveryInfo?.dstTxHash;
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
success: true,
|
|
573
|
+
txHash: String(spokeTxHash),
|
|
574
|
+
status: "success",
|
|
575
|
+
spokeTxHash: String(spokeTxHash),
|
|
576
|
+
hubTxHash: hubTxHash ? String(hubTxHash) : undefined,
|
|
577
|
+
intentHash: undefined,
|
|
578
|
+
operation: "supply",
|
|
579
|
+
chainId,
|
|
580
|
+
dstChainId: dstChainId || chainId,
|
|
581
|
+
token,
|
|
582
|
+
amount,
|
|
583
|
+
isCrossChain: crossChain,
|
|
584
|
+
message: crossChain
|
|
585
|
+
? `Successfully supplied ${amount} ${token} on ${chainId}. Collateral available on ${dstChainId || chainId}.`
|
|
586
|
+
: `Successfully supplied ${amount} ${token} to money market on ${chainId}`,
|
|
587
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
588
|
+
};
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error during supply";
|
|
591
|
+
return {
|
|
592
|
+
success: false,
|
|
593
|
+
status: "failed",
|
|
594
|
+
operation: "supply",
|
|
595
|
+
chainId,
|
|
596
|
+
dstChainId: dstChainId || chainId,
|
|
597
|
+
token,
|
|
598
|
+
amount,
|
|
599
|
+
isCrossChain: crossChain,
|
|
600
|
+
message: `Money market supply failed: ${errorMessage}`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Withdraw tokens from the money market
|
|
607
|
+
*
|
|
608
|
+
* Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId
|
|
609
|
+
*/
|
|
610
|
+
async function handleWithdraw(
|
|
611
|
+
params: Static<typeof MoneyMarketWithdrawSchema>
|
|
612
|
+
): Promise<MoneyMarketOperationResult> {
|
|
613
|
+
const {
|
|
614
|
+
walletId,
|
|
615
|
+
chainId,
|
|
616
|
+
token,
|
|
617
|
+
amount,
|
|
618
|
+
timeoutMs = 180000,
|
|
619
|
+
policyId,
|
|
620
|
+
withdrawType = 'default',
|
|
621
|
+
dstChainId,
|
|
622
|
+
recipient,
|
|
623
|
+
skipSimulation = false
|
|
624
|
+
} = params;
|
|
625
|
+
|
|
626
|
+
const crossChain = isCrossChainOperation(chainId, dstChainId);
|
|
627
|
+
const warnings: string[] = [];
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
// Pre-operation checks
|
|
631
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
632
|
+
walletId,
|
|
633
|
+
chainId,
|
|
634
|
+
token,
|
|
635
|
+
amount,
|
|
636
|
+
"withdraw",
|
|
637
|
+
policyId
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
// Parse amount with actual token decimals
|
|
641
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
642
|
+
const amountBigInt = parseTokenAmount(amount, decimals);
|
|
643
|
+
|
|
644
|
+
const sodaxClient = await getSodaxClient();
|
|
645
|
+
|
|
646
|
+
// Build withdraw parameters
|
|
647
|
+
const withdrawParams: any = {
|
|
648
|
+
action: 'withdraw',
|
|
649
|
+
token: tokenAddr,
|
|
650
|
+
amount: amountBigInt,
|
|
651
|
+
|
|
652
|
+
toAddress: recipient || walletAddress,
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Add cross-chain parameters if applicable
|
|
656
|
+
if (crossChain && dstChainId) {
|
|
657
|
+
withdrawParams.toChainId = toSodaxChainId(dstChainId);
|
|
658
|
+
warnings.push(`Cross-chain withdraw: withdrawing from ${chainId}, receiving tokens on ${dstChainId}`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Check and handle allowance (required for hub chain operations)
|
|
662
|
+
// Reference: sodax-frontend moneymarket-ops.ts
|
|
663
|
+
const isHubChain = chainId === 'sonic' || chainId === '146';
|
|
664
|
+
if (isHubChain) {
|
|
665
|
+
try {
|
|
666
|
+
const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(
|
|
667
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'withdraw' },
|
|
668
|
+
spokeProvider
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
if (allowanceResult.ok && !allowanceResult.value) {
|
|
672
|
+
console.log('[mm:withdraw] Approval needed, approving...');
|
|
673
|
+
const approveResult = await (sodaxClient as any).moneyMarket.approve(
|
|
674
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'withdraw' },
|
|
675
|
+
spokeProvider
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
if (!approveResult.ok) {
|
|
679
|
+
throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Wait for approval confirmation
|
|
683
|
+
const approvalTxHash = approveResult.value;
|
|
684
|
+
if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {
|
|
685
|
+
await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);
|
|
686
|
+
console.log('[mm:withdraw] Approval confirmed');
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} catch (allowanceError) {
|
|
690
|
+
console.warn('[mm:withdraw] Allowance check failed, proceeding anyway:', allowanceError);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Execute withdraw
|
|
695
|
+
const withdrawResult = await (sodaxClient as any).moneyMarket.withdraw(
|
|
696
|
+
withdrawParams,
|
|
697
|
+
spokeProvider,
|
|
698
|
+
timeoutMs
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
// Handle Result type from SDK
|
|
702
|
+
if (withdrawResult.ok === false) {
|
|
703
|
+
throw new Error(`Withdraw failed: ${serializeError(withdrawResult.error)}`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const value = withdrawResult.ok ? withdrawResult.value : withdrawResult;
|
|
707
|
+
const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];
|
|
708
|
+
|
|
709
|
+
// Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)
|
|
710
|
+
const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;
|
|
711
|
+
const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;
|
|
712
|
+
const dstTxHash = deliveryInfo?.dstTxHash;
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
success: true,
|
|
716
|
+
txHash: String(spokeTxHash),
|
|
717
|
+
status: "success",
|
|
718
|
+
spokeTxHash: String(spokeTxHash),
|
|
719
|
+
hubTxHash: hubTxHash ? String(hubTxHash) : undefined,
|
|
720
|
+
intentHash: undefined,
|
|
721
|
+
operation: "withdraw",
|
|
722
|
+
chainId,
|
|
723
|
+
dstChainId: dstChainId || chainId,
|
|
724
|
+
token,
|
|
725
|
+
amount,
|
|
726
|
+
isCrossChain: crossChain,
|
|
727
|
+
message: crossChain
|
|
728
|
+
? `Successfully withdrew ${amount} ${token} from ${chainId} to ${dstChainId}`
|
|
729
|
+
: `Successfully withdrew ${amount} ${token} from money market on ${chainId}`,
|
|
730
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
731
|
+
};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error during withdraw";
|
|
734
|
+
return {
|
|
735
|
+
success: false,
|
|
736
|
+
status: "failed",
|
|
737
|
+
operation: "withdraw",
|
|
738
|
+
chainId,
|
|
739
|
+
dstChainId: dstChainId || chainId,
|
|
740
|
+
token,
|
|
741
|
+
amount,
|
|
742
|
+
isCrossChain: crossChain,
|
|
743
|
+
message: `Money market withdraw failed: ${errorMessage}`,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Borrow tokens from the money market
|
|
750
|
+
*
|
|
751
|
+
* KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!
|
|
752
|
+
* Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)
|
|
753
|
+
*
|
|
754
|
+
* This is a powerful cross-chain DeFi primitive that allows:
|
|
755
|
+
* 1. Accessing liquidity without moving collateral
|
|
756
|
+
* 2. Arbitraging interest rates across chains
|
|
757
|
+
* 3. Efficient capital utilization across the entire SODAX network
|
|
758
|
+
*/
|
|
759
|
+
async function handleBorrow(
|
|
760
|
+
params: Static<typeof MoneyMarketBorrowSchema>
|
|
761
|
+
): Promise<MoneyMarketOperationResult> {
|
|
762
|
+
const {
|
|
763
|
+
walletId,
|
|
764
|
+
chainId,
|
|
765
|
+
token,
|
|
766
|
+
amount,
|
|
767
|
+
timeoutMs = 180000,
|
|
768
|
+
policyId,
|
|
769
|
+
interestRateMode = 2,
|
|
770
|
+
referralCode,
|
|
771
|
+
dstChainId, // KEY: This can be different from chainId for cross-chain borrow!
|
|
772
|
+
recipient,
|
|
773
|
+
skipSimulation = false
|
|
774
|
+
} = params;
|
|
775
|
+
|
|
776
|
+
const crossChain = isCrossChainOperation(chainId, dstChainId);
|
|
777
|
+
const warnings: string[] = [];
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
// Pre-operation checks
|
|
781
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
782
|
+
walletId,
|
|
783
|
+
chainId,
|
|
784
|
+
token,
|
|
785
|
+
amount,
|
|
786
|
+
"borrow",
|
|
787
|
+
policyId
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
// Parse amount with actual token decimals
|
|
791
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
792
|
+
const amountBigInt = parseTokenAmount(amount, decimals);
|
|
793
|
+
|
|
794
|
+
// Get user's positions to check health factor (best practice)
|
|
795
|
+
const sodaxClient = await getSodaxClient();
|
|
796
|
+
|
|
797
|
+
// For cross-chain borrow, resolve token on DESTINATION chain
|
|
798
|
+
// SDK expects: getMoneyMarketToken(toChainId, params.token)
|
|
799
|
+
// So params.token must be the destination chain's token address
|
|
800
|
+
let borrowTokenAddr = tokenAddr;
|
|
801
|
+
if (crossChain && dstChainId) {
|
|
802
|
+
borrowTokenAddr = await resolveToken(dstChainId, token);
|
|
803
|
+
console.log('[mm:borrow] Cross-chain: resolved token on destination chain', {
|
|
804
|
+
srcChain: chainId,
|
|
805
|
+
dstChain: dstChainId,
|
|
806
|
+
srcTokenAddr: tokenAddr,
|
|
807
|
+
dstTokenAddr: borrowTokenAddr,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Build borrow parameters
|
|
812
|
+
const borrowParams: any = {
|
|
813
|
+
action: 'borrow',
|
|
814
|
+
token: borrowTokenAddr,
|
|
815
|
+
amount: amountBigInt,
|
|
816
|
+
|
|
817
|
+
toAddress: recipient || walletAddress,
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// KEY CROSS-CHAIN FEATURE:
|
|
821
|
+
// If dstChainId is provided and different from chainId, the borrowed tokens
|
|
822
|
+
// will be delivered to dstChainId instead of chainId where the borrow is initiated
|
|
823
|
+
if (crossChain && dstChainId) {
|
|
824
|
+
borrowParams.toChainId = toSodaxChainId(dstChainId);
|
|
825
|
+
warnings.push(`Cross-chain borrow: Using collateral on ${chainId}, receiving borrowed tokens on ${dstChainId}`);
|
|
826
|
+
warnings.push(`Ensure you have sufficient collateral on ${chainId} to support this borrow`);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Check and handle allowance (required for hub chain operations)
|
|
830
|
+
// Reference: sodax-frontend moneymarket-ops.ts
|
|
831
|
+
const isHubChain = chainId === 'sonic' || chainId === '146';
|
|
832
|
+
if (isHubChain) {
|
|
833
|
+
try {
|
|
834
|
+
const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(
|
|
835
|
+
{ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },
|
|
836
|
+
spokeProvider
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
if (allowanceResult.ok && !allowanceResult.value) {
|
|
840
|
+
console.log('[mm:borrow] Approval needed, approving...');
|
|
841
|
+
const approveResult = await (sodaxClient as any).moneyMarket.approve(
|
|
842
|
+
{ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },
|
|
843
|
+
spokeProvider
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
if (!approveResult.ok) {
|
|
847
|
+
throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Wait for approval confirmation
|
|
851
|
+
const approvalTxHash = approveResult.value;
|
|
852
|
+
if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {
|
|
853
|
+
await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);
|
|
854
|
+
console.log('[mm:borrow] Approval confirmed');
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
} catch (allowanceError) {
|
|
858
|
+
console.warn('[mm:borrow] Allowance check failed, proceeding anyway:', allowanceError);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Execute borrow
|
|
863
|
+
const borrowResult = await (sodaxClient as any).moneyMarket.borrow(
|
|
864
|
+
borrowParams,
|
|
865
|
+
spokeProvider,
|
|
866
|
+
timeoutMs
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
// Handle Result type from SDK
|
|
870
|
+
if (borrowResult.ok === false) {
|
|
871
|
+
throw new Error(`Borrow failed: ${serializeError(borrowResult.error)}`);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const value = borrowResult.ok ? borrowResult.value : borrowResult;
|
|
875
|
+
const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];
|
|
876
|
+
|
|
877
|
+
// Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)
|
|
878
|
+
const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;
|
|
879
|
+
const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;
|
|
880
|
+
const dstTxHash = deliveryInfo?.dstTxHash;
|
|
881
|
+
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
txHash: String(spokeTxHash),
|
|
885
|
+
status: "success",
|
|
886
|
+
spokeTxHash: String(spokeTxHash),
|
|
887
|
+
hubTxHash: hubTxHash ? String(hubTxHash) : undefined,
|
|
888
|
+
intentHash: undefined,
|
|
889
|
+
operation: "borrow",
|
|
890
|
+
chainId,
|
|
891
|
+
dstChainId: dstChainId || chainId,
|
|
892
|
+
token,
|
|
893
|
+
amount,
|
|
894
|
+
isCrossChain: crossChain,
|
|
895
|
+
message: crossChain
|
|
896
|
+
? `Successfully borrowed ${amount} ${token} on ${dstChainId} using collateral from ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`
|
|
897
|
+
: `Successfully borrowed ${amount} ${token} from money market on ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`,
|
|
898
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
899
|
+
};
|
|
900
|
+
} catch (error) {
|
|
901
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error during borrow";
|
|
902
|
+
return {
|
|
903
|
+
success: false,
|
|
904
|
+
status: "failed",
|
|
905
|
+
operation: "borrow",
|
|
906
|
+
chainId,
|
|
907
|
+
dstChainId: dstChainId || chainId,
|
|
908
|
+
token,
|
|
909
|
+
amount,
|
|
910
|
+
isCrossChain: crossChain,
|
|
911
|
+
message: `Money market borrow failed: ${errorMessage}`,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Repay borrowed tokens to the money market
|
|
918
|
+
*
|
|
919
|
+
* Supports cross-chain repay: repay debt using tokens from a different chain
|
|
920
|
+
*/
|
|
921
|
+
async function handleRepay(
|
|
922
|
+
params: Static<typeof MoneyMarketRepaySchema>
|
|
923
|
+
): Promise<MoneyMarketOperationResult> {
|
|
924
|
+
const {
|
|
925
|
+
walletId,
|
|
926
|
+
chainId,
|
|
927
|
+
token,
|
|
928
|
+
amount,
|
|
929
|
+
timeoutMs = 180000,
|
|
930
|
+
policyId,
|
|
931
|
+
interestRateMode = 2,
|
|
932
|
+
repayAll = false,
|
|
933
|
+
collateralChainId,
|
|
934
|
+
skipSimulation = false
|
|
935
|
+
} = params;
|
|
936
|
+
|
|
937
|
+
const crossChain = !!collateralChainId && collateralChainId !== chainId;
|
|
938
|
+
const warnings: string[] = [];
|
|
939
|
+
|
|
940
|
+
try {
|
|
941
|
+
// Pre-operation checks
|
|
942
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
943
|
+
walletId,
|
|
944
|
+
chainId,
|
|
945
|
+
token,
|
|
946
|
+
amount,
|
|
947
|
+
"repay",
|
|
948
|
+
policyId
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
// Parse amount with actual token decimals (use -1 for max repay if repayAll is true)
|
|
952
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
953
|
+
const amountBigInt = repayAll ? BigInt(-1) : parseTokenAmount(amount, decimals);
|
|
954
|
+
|
|
955
|
+
// Check allowance for repay
|
|
956
|
+
const { approvalTxHash } = await ensureAllowance(
|
|
957
|
+
{ token: tokenAddr, amount: amountBigInt === BigInt(-1) ? BigInt(0) : amountBigInt, action: 'repay' },
|
|
958
|
+
spokeProvider
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
if (approvalTxHash) {
|
|
962
|
+
warnings.push(`Approval transaction executed: ${approvalTxHash}`);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const sodaxClient = await getSodaxClient();
|
|
966
|
+
|
|
967
|
+
// Build repay parameters
|
|
968
|
+
const repayParams: any = {
|
|
969
|
+
action: 'repay',
|
|
970
|
+
token: tokenAddr,
|
|
971
|
+
amount: amountBigInt,
|
|
972
|
+
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// Add cross-chain parameters if applicable
|
|
976
|
+
if (crossChain && collateralChainId) {
|
|
977
|
+
repayParams.toChainId = collateralChainId;
|
|
978
|
+
warnings.push(`Cross-chain repay: Repaying debt on ${collateralChainId} using tokens from ${chainId}`);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Check and handle allowance (required for ALL repay operations)
|
|
982
|
+
// Reference: sodax-frontend moneymarket-ops.ts - repay ALWAYS checks allowance
|
|
983
|
+
try {
|
|
984
|
+
const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(
|
|
985
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'repay' },
|
|
986
|
+
spokeProvider
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
if (allowanceResult.ok && !allowanceResult.value) {
|
|
990
|
+
console.log('[mm:repay] Approval needed, approving...');
|
|
991
|
+
const approveResult = await (sodaxClient as any).moneyMarket.approve(
|
|
992
|
+
{ token: tokenAddr, amount: amountBigInt, action: 'repay' },
|
|
993
|
+
spokeProvider
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
if (!approveResult.ok) {
|
|
997
|
+
throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Wait for approval confirmation
|
|
1001
|
+
const approvalTxHash = approveResult.value;
|
|
1002
|
+
if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {
|
|
1003
|
+
await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);
|
|
1004
|
+
console.log('[mm:repay] Approval confirmed');
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
} catch (allowanceError) {
|
|
1008
|
+
console.warn('[mm:repay] Allowance check failed, proceeding anyway:', allowanceError);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Execute repay
|
|
1012
|
+
const repayResult = await (sodaxClient as any).moneyMarket.repay(
|
|
1013
|
+
repayParams,
|
|
1014
|
+
spokeProvider,
|
|
1015
|
+
timeoutMs
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
// Handle Result type from SDK
|
|
1019
|
+
if (repayResult.ok === false) {
|
|
1020
|
+
throw new Error(`Repay failed: ${serializeError(repayResult.error)}`);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const value = repayResult.ok ? repayResult.value : repayResult;
|
|
1024
|
+
const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];
|
|
1025
|
+
|
|
1026
|
+
// Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)
|
|
1027
|
+
const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;
|
|
1028
|
+
const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;
|
|
1029
|
+
const dstTxHash = deliveryInfo?.dstTxHash;
|
|
1030
|
+
|
|
1031
|
+
return {
|
|
1032
|
+
success: true,
|
|
1033
|
+
txHash: String(spokeTxHash),
|
|
1034
|
+
status: "success",
|
|
1035
|
+
spokeTxHash: String(spokeTxHash),
|
|
1036
|
+
hubTxHash: hubTxHash ? String(hubTxHash) : undefined,
|
|
1037
|
+
intentHash: undefined,
|
|
1038
|
+
operation: "repay",
|
|
1039
|
+
chainId,
|
|
1040
|
+
token,
|
|
1041
|
+
amount: repayAll ? "max (full debt)" : amount,
|
|
1042
|
+
isCrossChain: crossChain,
|
|
1043
|
+
message: repayAll
|
|
1044
|
+
? `Successfully repaid full debt for ${token} on ${chainId}`
|
|
1045
|
+
: `Successfully repaid ${amount} ${token} to money market on ${chainId}`,
|
|
1046
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1047
|
+
};
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error during repay";
|
|
1050
|
+
return {
|
|
1051
|
+
success: false,
|
|
1052
|
+
status: "failed",
|
|
1053
|
+
operation: "repay",
|
|
1054
|
+
chainId,
|
|
1055
|
+
token,
|
|
1056
|
+
amount: repayAll ? "max (full debt)" : amount,
|
|
1057
|
+
isCrossChain: crossChain,
|
|
1058
|
+
message: `Money market repay failed: ${errorMessage}`,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ============================================================================
|
|
1064
|
+
// Intent Creation Handlers (Advanced)
|
|
1065
|
+
// ============================================================================
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Create a supply intent without executing (for custom flows)
|
|
1069
|
+
*/
|
|
1070
|
+
async function handleCreateSupplyIntent(
|
|
1071
|
+
params: Static<typeof CreateSupplyIntentSchema>
|
|
1072
|
+
): Promise<IntentResult> {
|
|
1073
|
+
const { walletId, chainId, token, amount, useAsCollateral = true, dstChainId, recipient, raw = true } = params;
|
|
1074
|
+
|
|
1075
|
+
try {
|
|
1076
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
1077
|
+
walletId, chainId, token, amount, "supply"
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
1081
|
+
const amountBigInt = parseTokenAmount(amount, decimals);
|
|
1082
|
+
const sodaxClient = await getSodaxClient();
|
|
1083
|
+
|
|
1084
|
+
const supplyParams: any = {
|
|
1085
|
+
action: 'supply',
|
|
1086
|
+
token: tokenAddr,
|
|
1087
|
+
amount: amountBigInt,
|
|
1088
|
+
|
|
1089
|
+
toAddress: recipient || walletAddress,
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
if (dstChainId) {
|
|
1093
|
+
supplyParams.toChainId = toSodaxChainId(dstChainId);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const intentData = await sodaxClient.moneyMarket.createSupplyIntent(
|
|
1097
|
+
supplyParams,
|
|
1098
|
+
spokeProvider,
|
|
1099
|
+
raw
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
success: true,
|
|
1104
|
+
status: "pending",
|
|
1105
|
+
operation: "createSupplyIntent",
|
|
1106
|
+
chainId,
|
|
1107
|
+
dstChainId: dstChainId || chainId,
|
|
1108
|
+
token,
|
|
1109
|
+
amount,
|
|
1110
|
+
intentData,
|
|
1111
|
+
requiresSubmission: true,
|
|
1112
|
+
message: "Supply intent created. Submit this intent to execute the supply operation.",
|
|
1113
|
+
};
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1116
|
+
throw new Error(`Create supply intent failed: ${errorMessage}`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Create a borrow intent without executing (for custom flows)
|
|
1122
|
+
*/
|
|
1123
|
+
async function handleCreateBorrowIntent(
|
|
1124
|
+
params: Static<typeof CreateBorrowIntentSchema>
|
|
1125
|
+
): Promise<IntentResult> {
|
|
1126
|
+
const { walletId, chainId, token, amount, interestRateMode = 2, dstChainId, recipient, raw = true } = params;
|
|
1127
|
+
|
|
1128
|
+
try {
|
|
1129
|
+
const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(
|
|
1130
|
+
walletId, chainId, token, amount, "borrow"
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
const decimals = await getTokenDecimals(chainId, token);
|
|
1134
|
+
const amountBigInt = parseTokenAmount(amount, decimals);
|
|
1135
|
+
const sodaxClient = await getSodaxClient();
|
|
1136
|
+
|
|
1137
|
+
const borrowParams: any = {
|
|
1138
|
+
action: 'borrow',
|
|
1139
|
+
token: tokenAddr,
|
|
1140
|
+
amount: amountBigInt,
|
|
1141
|
+
|
|
1142
|
+
toAddress: recipient || walletAddress,
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
if (dstChainId) {
|
|
1146
|
+
borrowParams.toChainId = toSodaxChainId(dstChainId);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const intentData = await sodaxClient.moneyMarket.createBorrowIntent(
|
|
1150
|
+
borrowParams,
|
|
1151
|
+
spokeProvider,
|
|
1152
|
+
raw
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
success: true,
|
|
1157
|
+
status: "pending",
|
|
1158
|
+
operation: "createBorrowIntent",
|
|
1159
|
+
chainId,
|
|
1160
|
+
dstChainId: dstChainId || chainId,
|
|
1161
|
+
token,
|
|
1162
|
+
amount,
|
|
1163
|
+
intentData,
|
|
1164
|
+
requiresSubmission: true,
|
|
1165
|
+
message: dstChainId && dstChainId !== chainId
|
|
1166
|
+
? `Cross-chain borrow intent created. Collateral on ${chainId}, borrowed tokens to ${dstChainId}. Submit this intent to execute.`
|
|
1167
|
+
: "Borrow intent created. Submit this intent to execute the borrow operation.",
|
|
1168
|
+
};
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1171
|
+
throw new Error(`Create borrow intent failed: ${errorMessage}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// ============================================================================
|
|
1176
|
+
// Tool Registration
|
|
1177
|
+
// ============================================================================
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Registers all money market tools with the agent tools registry
|
|
1181
|
+
*/
|
|
1182
|
+
export function registerMoneyMarketTools(agentTools: AgentTools): void {
|
|
1183
|
+
// Supply
|
|
1184
|
+
agentTools.register({
|
|
1185
|
+
name: "amped_mm_supply",
|
|
1186
|
+
summary: "Supply tokens as collateral to the SODAX money market. Supports same-chain and cross-chain supply (supply on chain A, collateral available on chain B).",
|
|
1187
|
+
schema: MoneyMarketSupplySchema,
|
|
1188
|
+
handler: handleSupply,
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
// Withdraw
|
|
1192
|
+
agentTools.register({
|
|
1193
|
+
name: "amped_mm_withdraw",
|
|
1194
|
+
summary: "Withdraw supplied tokens from the SODAX money market. Supports cross-chain withdraw (withdraw from chain A, receive tokens on chain B).",
|
|
1195
|
+
schema: MoneyMarketWithdrawSchema,
|
|
1196
|
+
handler: handleWithdraw,
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// Borrow
|
|
1200
|
+
agentTools.register({
|
|
1201
|
+
name: "amped_mm_borrow",
|
|
1202
|
+
summary: "Borrow tokens from the SODAX money market. KEY FEATURE: Can borrow to a different chain than collateral! Example: Supply on Ethereum, borrow to Arbitrum using dstChainId parameter.",
|
|
1203
|
+
schema: MoneyMarketBorrowSchema,
|
|
1204
|
+
handler: handleBorrow,
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// Repay
|
|
1208
|
+
agentTools.register({
|
|
1209
|
+
name: "amped_mm_repay",
|
|
1210
|
+
summary: "Repay borrowed tokens to the SODAX money market. Use amount='-1' or repayAll=true to repay full debt. Supports cross-chain repay.",
|
|
1211
|
+
schema: MoneyMarketRepaySchema,
|
|
1212
|
+
handler: handleRepay,
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
// Advanced: Create Intent variants for custom flows
|
|
1216
|
+
agentTools.register({
|
|
1217
|
+
name: "amped_mm_create_supply_intent",
|
|
1218
|
+
summary: "[Advanced] Create a supply intent without executing. Returns raw intent data for custom signing or multi-step flows.",
|
|
1219
|
+
schema: CreateSupplyIntentSchema,
|
|
1220
|
+
handler: handleCreateSupplyIntent,
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
agentTools.register({
|
|
1224
|
+
name: "amped_mm_create_borrow_intent",
|
|
1225
|
+
summary: "[Advanced] Create a borrow intent without executing. Supports cross-chain borrow intents. Returns raw intent data for custom signing or multi-step flows.",
|
|
1226
|
+
schema: CreateBorrowIntentSchema,
|
|
1227
|
+
handler: handleCreateBorrowIntent,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// ============================================================================
|
|
1232
|
+
// Re-exports for testing and direct usage
|
|
1233
|
+
// ============================================================================
|
|
1234
|
+
|
|
1235
|
+
export {
|
|
1236
|
+
MoneyMarketBaseSchema,
|
|
1237
|
+
MoneyMarketSupplySchema,
|
|
1238
|
+
MoneyMarketWithdrawSchema,
|
|
1239
|
+
MoneyMarketBorrowSchema,
|
|
1240
|
+
MoneyMarketRepaySchema,
|
|
1241
|
+
CreateSupplyIntentSchema,
|
|
1242
|
+
CreateWithdrawIntentSchema,
|
|
1243
|
+
CreateBorrowIntentSchema,
|
|
1244
|
+
CreateRepayIntentSchema,
|
|
1245
|
+
handleSupply,
|
|
1246
|
+
handleWithdraw,
|
|
1247
|
+
handleBorrow,
|
|
1248
|
+
handleRepay,
|
|
1249
|
+
handleCreateSupplyIntent,
|
|
1250
|
+
handleCreateBorrowIntent,
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// Aliases for index.ts compatibility
|
|
1254
|
+
export {
|
|
1255
|
+
MoneyMarketSupplySchema as MmSupplySchema,
|
|
1256
|
+
MoneyMarketWithdrawSchema as MmWithdrawSchema,
|
|
1257
|
+
MoneyMarketBorrowSchema as MmBorrowSchema,
|
|
1258
|
+
MoneyMarketRepaySchema as MmRepaySchema,
|
|
1259
|
+
handleSupply as handleMmSupply,
|
|
1260
|
+
handleWithdraw as handleMmWithdraw,
|
|
1261
|
+
handleBorrow as handleMmBorrow,
|
|
1262
|
+
handleRepay as handleMmRepay,
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
export type { MoneyMarketOperationResult, IntentResult };
|