lyra-plugin-onchain 0.1.8 → 0.1.9
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/package.json +1 -1
- package/src/client.ts +12 -3
- package/src/index.ts +0 -4
- package/src/protocols.ts +2 -9
- package/src/tools/scallop.ts +43 -41
- package/src/tools/suilend.ts +85 -32
- package/src/tools/tools.test.ts +6 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lyra-plugin-onchain",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Sui on-chain tools for Lyra: SUI transfers, DeepBook market data, Walrus receipts/memory — every write policy-checked, simulated, and recorded on-chain via lyra::policy",
|
|
6
6
|
"license": "MIT",
|
package/src/client.ts
CHANGED
|
@@ -11,14 +11,23 @@ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
|
11
11
|
|
|
12
12
|
export type SuiNetwork = 'testnet' | 'mainnet'
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Fullnode JSON-RPC URL for a network. `LYRA_RPC_URL` overrides the default
|
|
16
|
+
* public endpoint — recommended in production: the public `fullnode.mainnet.
|
|
17
|
+
* sui.io` is load-balanced across replicas at different checkpoint heights, so
|
|
18
|
+
* a freshly-created object (e.g. a just-opened lending obligation) can flicker
|
|
19
|
+
* in and out between calls. A single dedicated node gives read-your-writes
|
|
20
|
+
* consistency. The override applies to mainnet only (testnet keeps its default).
|
|
21
|
+
*/
|
|
15
22
|
export function suiRpcUrl(network: SuiNetwork): string {
|
|
23
|
+
const override = process.env.LYRA_RPC_URL?.trim()
|
|
24
|
+
if (network === 'mainnet' && override) return override
|
|
16
25
|
return getFullnodeUrl(network)
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
/** A Sui JSON-RPC client for the given network. */
|
|
28
|
+
/** A Sui JSON-RPC client for the given network (honours `LYRA_RPC_URL`). */
|
|
20
29
|
export function makeSuiClient(network: SuiNetwork): SuiClient {
|
|
21
|
-
return new SuiClient({ url:
|
|
30
|
+
return new SuiClient({ url: suiRpcUrl(network) })
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
/**
|
package/src/index.ts
CHANGED
|
@@ -37,10 +37,8 @@ import {
|
|
|
37
37
|
import { makePolicyCreate, makePolicyShow } from './tools/policy'
|
|
38
38
|
import { makeProtocolsList } from './tools/protocols'
|
|
39
39
|
import {
|
|
40
|
-
makeScallopBorrow,
|
|
41
40
|
makeScallopMarkets,
|
|
42
41
|
makeScallopPosition,
|
|
43
|
-
makeScallopRepay,
|
|
44
42
|
makeScallopSupply,
|
|
45
43
|
makeScallopWithdraw,
|
|
46
44
|
} from './tools/scallop'
|
|
@@ -105,8 +103,6 @@ const plugin: NativePlugin = {
|
|
|
105
103
|
ctx.registerTool(makeScallopPosition(onchain) as ToolDef)
|
|
106
104
|
ctx.registerTool(makeScallopSupply(onchain) as ToolDef)
|
|
107
105
|
ctx.registerTool(makeScallopWithdraw(onchain) as ToolDef)
|
|
108
|
-
ctx.registerTool(makeScallopBorrow(onchain) as ToolDef)
|
|
109
|
-
ctx.registerTool(makeScallopRepay(onchain) as ToolDef)
|
|
110
106
|
ctx.registerTool(makeNaviMarkets(onchain) as ToolDef)
|
|
111
107
|
ctx.registerTool(makeNaviPosition(onchain) as ToolDef)
|
|
112
108
|
ctx.registerTool(makeNaviSupply(onchain) as ToolDef)
|
package/src/protocols.ts
CHANGED
|
@@ -51,16 +51,9 @@ export const PROTOCOLS: ProtocolCapability[] = [
|
|
|
51
51
|
category: 'lending',
|
|
52
52
|
read: true,
|
|
53
53
|
execute: true,
|
|
54
|
-
tools: [
|
|
55
|
-
'scallop.markets',
|
|
56
|
-
'scallop.position',
|
|
57
|
-
'scallop.supply',
|
|
58
|
-
'scallop.withdraw',
|
|
59
|
-
'scallop.borrow',
|
|
60
|
-
'scallop.repay',
|
|
61
|
-
],
|
|
54
|
+
tools: ['scallop.markets', 'scallop.position', 'scallop.supply', 'scallop.withdraw'],
|
|
62
55
|
llamaProjects: ['scallop-lending', 'scallop'],
|
|
63
|
-
note: '
|
|
56
|
+
note: 'supply / withdraw lending yield (borrow/repay via NAVI or Suilend)',
|
|
64
57
|
},
|
|
65
58
|
{
|
|
66
59
|
id: 'navi',
|
package/src/tools/scallop.ts
CHANGED
|
@@ -129,32 +129,30 @@ const AmountSchema = z.object({
|
|
|
129
129
|
})
|
|
130
130
|
type AmountArgs = z.infer<typeof AmountSchema>
|
|
131
131
|
|
|
132
|
+
// Scallop redeems a MARKET-COIN amount on withdraw (not underlying SUI). We
|
|
133
|
+
// find the agent's SUI market coin by type pattern (robust to package upgrades).
|
|
134
|
+
const MARKET_COIN_SUI_RE = /::reserve::MarketCoin<0x0*2::sui::SUI>$/
|
|
135
|
+
|
|
132
136
|
async function runScallopWrite(
|
|
133
137
|
ctx: OnchainRuntimeContext,
|
|
134
138
|
amount: string,
|
|
135
|
-
kind: 'supply' | 'withdraw'
|
|
139
|
+
kind: 'supply' | 'withdraw',
|
|
136
140
|
): Promise<{ ok: true; data: unknown } | { ok: false; error: string }> {
|
|
137
141
|
const err = ensureMainnet(ctx)
|
|
138
142
|
if (err) return { ok: false, error: err }
|
|
139
143
|
const amountMist = suiToMist(amount)
|
|
140
144
|
if (amountMist === undefined || amountMist <= 0n)
|
|
141
145
|
return { ok: false, error: `invalid amount "${amount}"` }
|
|
142
|
-
// Minimum guard
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const tooSmall = checkMinimum(minAction, amountMist)
|
|
146
|
+
// Minimum guard on supply (withdraw pulls the agent's own funds back).
|
|
147
|
+
if (kind === 'supply') {
|
|
148
|
+
const tooSmall = checkMinimum('supply', amountMist)
|
|
146
149
|
if (tooSmall) return { ok: false, error: tooSmall }
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
// Policy gate on value-moving
|
|
150
|
-
if (ctx.policy && kind
|
|
152
|
+
// Policy gate on the value-moving supply.
|
|
153
|
+
if (ctx.policy && kind === 'supply') {
|
|
151
154
|
const verdict = evaluatePolicy(
|
|
152
|
-
{
|
|
153
|
-
kind: 'transfer',
|
|
154
|
-
coinType: SUI_TYPE,
|
|
155
|
-
amountMist,
|
|
156
|
-
protocol: kind === 'borrow' ? 'borrow' : 'scallop',
|
|
157
|
-
},
|
|
155
|
+
{ kind: 'transfer', coinType: SUI_TYPE, amountMist, protocol: 'scallop' },
|
|
158
156
|
ctx.policy,
|
|
159
157
|
)
|
|
160
158
|
if (!verdict.allowed)
|
|
@@ -166,15 +164,34 @@ async function runScallopWrite(
|
|
|
166
164
|
const builder = await sdk.createScallopBuilder()
|
|
167
165
|
const tx = builder.createTxBlock()
|
|
168
166
|
tx.setSender(ctx.agentAddress)
|
|
169
|
-
//
|
|
170
|
-
// borrow return a coin to hand back; repay consumes coins. depositQuick's 3rd
|
|
171
|
-
// arg returnSCoin=false keeps the old market-coin standard (withdrawQuick
|
|
172
|
-
// redeems it; the new sCoin standard → "No valid coins").
|
|
167
|
+
// depositQuick's 3rd arg returnSCoin=false keeps the old market-coin standard.
|
|
173
168
|
let out: unknown
|
|
174
|
-
if (kind === 'supply')
|
|
175
|
-
|
|
176
|
-
else
|
|
177
|
-
|
|
169
|
+
if (kind === 'supply') {
|
|
170
|
+
out = await tx.depositQuick(Number(amountMist), 'sui', false)
|
|
171
|
+
} else {
|
|
172
|
+
// withdrawQuick redeems a MARKET-COIN amount, not underlying SUI. Convert
|
|
173
|
+
// the requested SUI to market coins at the position's rate and clamp to the
|
|
174
|
+
// held balance, so a full/over-withdraw cleanly redeems everything.
|
|
175
|
+
const [balances, q] = await Promise.all([
|
|
176
|
+
ctx.client.getAllBalances({ owner: ctx.agentAddress }),
|
|
177
|
+
sdk.createScallopQuery().then(async query => {
|
|
178
|
+
await query.init()
|
|
179
|
+
return query
|
|
180
|
+
}),
|
|
181
|
+
])
|
|
182
|
+
const mc = balances.find(b => MARKET_COIN_SUI_RE.test(b.coinType))
|
|
183
|
+
const heldMc = BigInt(mc?.totalBalance ?? '0')
|
|
184
|
+
if (heldMc <= 0n) return { ok: false, error: 'no Scallop SUI position to withdraw' }
|
|
185
|
+
const port = (await q.getUserPortfolio({ walletAddress: ctx.agentAddress })) as {
|
|
186
|
+
lendings?: Array<{ coinName?: string; suppliedCoin?: number }>
|
|
187
|
+
}
|
|
188
|
+
const supplied = (port.lendings ?? []).find(l => l.coinName === 'sui')?.suppliedCoin ?? 0
|
|
189
|
+
const suppliedMist = BigInt(Math.floor(supplied * 1e9))
|
|
190
|
+
// redeem = requested/rate, where rate = supplied/heldMc; clamp to heldMc.
|
|
191
|
+
let redeemMc = suppliedMist > 0n ? (amountMist * heldMc) / suppliedMist : heldMc
|
|
192
|
+
if (redeemMc > heldMc || redeemMc <= 0n) redeemMc = heldMc
|
|
193
|
+
out = await tx.withdrawQuick(Number(redeemMc), 'sui')
|
|
194
|
+
}
|
|
178
195
|
if (out) tx.transferObjects([out as never], ctx.agentAddress)
|
|
179
196
|
const transaction = tx.txBlock
|
|
180
197
|
|
|
@@ -229,23 +246,8 @@ export function makeScallopWithdraw(ctx: OnchainRuntimeContext): ToolDef<AmountA
|
|
|
229
246
|
}
|
|
230
247
|
}
|
|
231
248
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
searchHint: 'scallop borrow loan leverage debt against collateral sui',
|
|
238
|
-
schema: AmountSchema,
|
|
239
|
-
handler: async args => runScallopWrite(ctx, args.amount, 'borrow'),
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function makeScallopRepay(ctx: OnchainRuntimeContext): ToolDef<AmountArgs> {
|
|
244
|
-
return {
|
|
245
|
-
name: 'scallop.repay',
|
|
246
|
-
description: 'Repay borrowed SUI debt on Scallop. Simulated, then executed.',
|
|
247
|
-
searchHint: 'scallop repay pay back debt loan close sui',
|
|
248
|
-
schema: AmountSchema,
|
|
249
|
-
handler: async args => runScallopWrite(ctx, args.amount, 'repay'),
|
|
250
|
-
}
|
|
251
|
-
}
|
|
249
|
+
// NOTE: Scallop borrow/repay require opening a Scallop obligation + posting
|
|
250
|
+
// collateral (a separate flow from lending deposits — borrowQuick alone aborts
|
|
251
|
+
// "No obligation found"). Until that flow is wired, borrowing routes through the
|
|
252
|
+
// verified NAVI/Suilend adapters instead, so we don't ship a borrow tool here
|
|
253
|
+
// that can't execute.
|
package/src/tools/suilend.ts
CHANGED
|
@@ -57,19 +57,31 @@ function isSuiType(t: string): boolean {
|
|
|
57
57
|
return t === SUI_TYPE || t === SUI_LONG
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/** The agent's obligation cap + id, or null if it has no Suilend position yet.
|
|
60
|
+
/** The agent's obligation cap + id, or null if it has no Suilend position yet.
|
|
61
|
+
* Retries: getObligationOwnerCaps reads owned objects + their BCS, which some
|
|
62
|
+
* fullnode replicas serve inconsistently right after a write ("invalid data
|
|
63
|
+
* type" / stale owned-object index) — a short retry rides out that lag. */
|
|
61
64
|
async function findObligation(
|
|
62
65
|
ctx: OnchainRuntimeContext,
|
|
63
66
|
): Promise<{ capId: string; obligationId: string } | null> {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
let lastErr: unknown
|
|
68
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
const caps = await SuilendClient.getObligationOwnerCaps(
|
|
71
|
+
ctx.agentAddress,
|
|
72
|
+
[LENDING_MARKET_TYPE],
|
|
73
|
+
ctx.client as never,
|
|
74
|
+
)
|
|
75
|
+
if (!caps.length) return null
|
|
76
|
+
const c = caps[0] as { id: unknown; obligationId: string | { id: string } }
|
|
77
|
+
const obligationId = typeof c.obligationId === 'string' ? c.obligationId : c.obligationId.id
|
|
78
|
+
return { capId: capObjectId(c), obligationId }
|
|
79
|
+
} catch (e) {
|
|
80
|
+
lastErr = e
|
|
81
|
+
await new Promise(r => setTimeout(r, 1200))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw lastErr
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
// --- suilend.supply --------------------------------------------------------
|
|
@@ -79,6 +91,43 @@ const AmountSchema = z.object({
|
|
|
79
91
|
})
|
|
80
92
|
type AmountArgs = z.infer<typeof AmountSchema>
|
|
81
93
|
|
|
94
|
+
// Borrowable/repayable assets. Suilend (like most money markets) forbids
|
|
95
|
+
// borrowing the SAME asset you post as collateral (obligation::borrow abort 8),
|
|
96
|
+
// so the canonical flow is "supply SUI → borrow a stablecoin". USDC is the
|
|
97
|
+
// default; SUI is available for the reverse (supply a stable, borrow SUI).
|
|
98
|
+
const USDC_TYPE = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC'
|
|
99
|
+
type CoinInfo = { type: string; decimals: number; minBase: bigint; label: string }
|
|
100
|
+
const BORROW_COINS: Record<'usdc' | 'sui', CoinInfo> = {
|
|
101
|
+
usdc: { type: USDC_TYPE, decimals: 6, minBase: 100_000n, label: 'USDC' }, // 0.1 USDC
|
|
102
|
+
sui: { type: SUI_TYPE, decimals: 9, minBase: 10_000_000n, label: 'SUI' }, // 0.01 SUI
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Parse a decimal amount into base units for a given decimals count. */
|
|
106
|
+
function toBaseUnits(amount: string, decimals: number): bigint | undefined {
|
|
107
|
+
const a = amount.trim()
|
|
108
|
+
if (!/^\d+(\.\d+)?$/.test(a)) return undefined
|
|
109
|
+
const dot = a.indexOf('.')
|
|
110
|
+
const whole = dot === -1 ? a : a.slice(0, dot)
|
|
111
|
+
const frac = dot === -1 ? '' : a.slice(dot + 1)
|
|
112
|
+
const fracPadded = (frac + '0'.repeat(decimals)).slice(0, decimals)
|
|
113
|
+
try {
|
|
114
|
+
return BigInt(whole || '0') * 10n ** BigInt(decimals) + BigInt(fracPadded || '0')
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const BorrowSchema = z.object({
|
|
121
|
+
amount: z.string().min(1).describe('Amount to borrow/repay, e.g. "5".'),
|
|
122
|
+
coin: z
|
|
123
|
+
.enum(['usdc', 'sui'])
|
|
124
|
+
.optional()
|
|
125
|
+
.describe(
|
|
126
|
+
'Asset to borrow/repay (default "usdc"). Suilend disallows borrowing the same asset you supply as collateral, so borrowing USDC against SUI collateral is the canonical flow.',
|
|
127
|
+
),
|
|
128
|
+
})
|
|
129
|
+
type BorrowArgs = z.infer<typeof BorrowSchema>
|
|
130
|
+
|
|
82
131
|
export function makeSuilendSupply(ctx: OnchainRuntimeContext): ToolDef<AmountArgs> {
|
|
83
132
|
return {
|
|
84
133
|
name: 'suilend.supply',
|
|
@@ -217,24 +266,25 @@ export function makeSuilendWithdraw(ctx: OnchainRuntimeContext): ToolDef<AmountA
|
|
|
217
266
|
|
|
218
267
|
// --- suilend.borrow --------------------------------------------------------
|
|
219
268
|
|
|
220
|
-
export function makeSuilendBorrow(ctx: OnchainRuntimeContext): ToolDef<
|
|
269
|
+
export function makeSuilendBorrow(ctx: OnchainRuntimeContext): ToolDef<BorrowArgs> {
|
|
221
270
|
return {
|
|
222
271
|
name: 'suilend.borrow',
|
|
223
272
|
description:
|
|
224
|
-
'Borrow
|
|
225
|
-
searchHint: 'suilend borrow loan leverage debt against collateral
|
|
226
|
-
schema:
|
|
273
|
+
'Borrow an asset from Suilend against supplied collateral. Default borrows USDC against SUI collateral (Suilend disallows same-asset borrows). Requires an existing obligation with enough health; the pre-flight simulation fails cleanly if under-collateralized. Policy-checked, simulated, then executed.',
|
|
274
|
+
searchHint: 'suilend borrow loan leverage debt against collateral usdc stablecoin money market',
|
|
275
|
+
schema: BorrowSchema,
|
|
227
276
|
handler: async args => {
|
|
228
277
|
const err = ensureMainnet(ctx)
|
|
229
278
|
if (err) return { ok: false, error: err }
|
|
230
|
-
const
|
|
231
|
-
|
|
279
|
+
const coin = BORROW_COINS[args.coin ?? 'usdc']
|
|
280
|
+
const amountBase = toBaseUnits(args.amount, coin.decimals)
|
|
281
|
+
if (amountBase === undefined || amountBase <= 0n)
|
|
232
282
|
return { ok: false, error: `invalid amount "${args.amount}"` }
|
|
233
|
-
|
|
234
|
-
|
|
283
|
+
if (amountBase < coin.minBase)
|
|
284
|
+
return { ok: false, error: `amount too small: below the minimum ${coin.label} borrow` }
|
|
235
285
|
if (ctx.policy) {
|
|
236
286
|
const verdict = evaluatePolicy(
|
|
237
|
-
{ kind: 'transfer', coinType:
|
|
287
|
+
{ kind: 'transfer', coinType: coin.type, amountMist: amountBase, protocol: 'borrow' },
|
|
238
288
|
ctx.policy,
|
|
239
289
|
)
|
|
240
290
|
if (!verdict.allowed)
|
|
@@ -252,8 +302,8 @@ export function makeSuilendBorrow(ctx: OnchainRuntimeContext): ToolDef<AmountArg
|
|
|
252
302
|
ctx.agentAddress,
|
|
253
303
|
obligation.capId,
|
|
254
304
|
obligation.obligationId,
|
|
255
|
-
|
|
256
|
-
|
|
305
|
+
coin.type,
|
|
306
|
+
amountBase.toString(),
|
|
257
307
|
tx as never,
|
|
258
308
|
)
|
|
259
309
|
const sim = await simulate(ctx.client, tx, ctx.agentAddress)
|
|
@@ -274,7 +324,8 @@ export function makeSuilendBorrow(ctx: OnchainRuntimeContext): ToolDef<AmountArg
|
|
|
274
324
|
data: {
|
|
275
325
|
protocol: 'suilend',
|
|
276
326
|
action: 'borrow',
|
|
277
|
-
|
|
327
|
+
amount: args.amount,
|
|
328
|
+
coin: coin.label,
|
|
278
329
|
digest: res.digest,
|
|
279
330
|
policyEnforced: ctx.policy != null,
|
|
280
331
|
},
|
|
@@ -288,22 +339,23 @@ export function makeSuilendBorrow(ctx: OnchainRuntimeContext): ToolDef<AmountArg
|
|
|
288
339
|
|
|
289
340
|
// --- suilend.repay ---------------------------------------------------------
|
|
290
341
|
|
|
291
|
-
export function makeSuilendRepay(ctx: OnchainRuntimeContext): ToolDef<
|
|
342
|
+
export function makeSuilendRepay(ctx: OnchainRuntimeContext): ToolDef<BorrowArgs> {
|
|
292
343
|
return {
|
|
293
344
|
name: 'suilend.repay',
|
|
294
345
|
description:
|
|
295
|
-
'Repay borrowed
|
|
296
|
-
searchHint: 'suilend repay pay back debt loan close sui money market',
|
|
297
|
-
schema:
|
|
346
|
+
'Repay borrowed debt on Suilend (default USDC; pass coin "sui" to repay a SUI loan). Simulated, then executed.',
|
|
347
|
+
searchHint: 'suilend repay pay back debt loan close usdc stablecoin sui money market',
|
|
348
|
+
schema: BorrowSchema,
|
|
298
349
|
handler: async args => {
|
|
299
350
|
const err = ensureMainnet(ctx)
|
|
300
351
|
if (err) return { ok: false, error: err }
|
|
301
|
-
const
|
|
302
|
-
|
|
352
|
+
const coin = BORROW_COINS[args.coin ?? 'usdc']
|
|
353
|
+
const amountBase = toBaseUnits(args.amount, coin.decimals)
|
|
354
|
+
if (amountBase === undefined || amountBase <= 0n)
|
|
303
355
|
return { ok: false, error: `invalid amount "${args.amount}"` }
|
|
304
356
|
if (ctx.policy) {
|
|
305
357
|
const verdict = evaluatePolicy(
|
|
306
|
-
{ kind: 'transfer', coinType:
|
|
358
|
+
{ kind: 'transfer', coinType: coin.type, amountMist: amountBase, protocol: 'suilend' },
|
|
307
359
|
ctx.policy,
|
|
308
360
|
)
|
|
309
361
|
if (!verdict.allowed)
|
|
@@ -318,8 +370,8 @@ export function makeSuilendRepay(ctx: OnchainRuntimeContext): ToolDef<AmountArgs
|
|
|
318
370
|
await suilend.repayIntoObligation(
|
|
319
371
|
ctx.agentAddress,
|
|
320
372
|
obligation.obligationId,
|
|
321
|
-
|
|
322
|
-
|
|
373
|
+
coin.type,
|
|
374
|
+
amountBase.toString(),
|
|
323
375
|
tx as never,
|
|
324
376
|
)
|
|
325
377
|
const sim = await simulate(ctx.client, tx, ctx.agentAddress)
|
|
@@ -340,7 +392,8 @@ export function makeSuilendRepay(ctx: OnchainRuntimeContext): ToolDef<AmountArgs
|
|
|
340
392
|
data: {
|
|
341
393
|
protocol: 'suilend',
|
|
342
394
|
action: 'repay',
|
|
343
|
-
|
|
395
|
+
amount: args.amount,
|
|
396
|
+
coin: coin.label,
|
|
344
397
|
digest: res.digest,
|
|
345
398
|
},
|
|
346
399
|
}
|
package/src/tools/tools.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { PluginContext, ToolDef } from 'lyra-core'
|
|
|
15
15
|
import plugin from '../index'
|
|
16
16
|
import type { OnchainRuntimeContext } from '../types'
|
|
17
17
|
import { makeVoloStake } from './liquid-stake'
|
|
18
|
-
import {
|
|
18
|
+
import { makeNaviBorrow, makeNaviRepay } from './navi'
|
|
19
19
|
import { makeSuilendBorrow, makeSuilendSupply } from './suilend'
|
|
20
20
|
|
|
21
21
|
// A context whose network/amount guards resolve before any client access.
|
|
@@ -56,8 +56,6 @@ describe('onchain plugin registration', () => {
|
|
|
56
56
|
// lending: the three biggest Sui money markets, full CRUD
|
|
57
57
|
'scallop.supply',
|
|
58
58
|
'scallop.withdraw',
|
|
59
|
-
'scallop.borrow',
|
|
60
|
-
'scallop.repay',
|
|
61
59
|
'navi.supply',
|
|
62
60
|
'navi.borrow',
|
|
63
61
|
'navi.repay',
|
|
@@ -112,8 +110,8 @@ describe('write-tool guards (fail before network)', () => {
|
|
|
112
110
|
if (!res.ok) expect(res.error).toContain('amount too small')
|
|
113
111
|
})
|
|
114
112
|
|
|
115
|
-
test('
|
|
116
|
-
const res = await
|
|
113
|
+
test('navi.borrow rejects a dust amount', async () => {
|
|
114
|
+
const res = await makeNaviBorrow(stubCtx()).handler({ amount: '0.001' })
|
|
117
115
|
expect(res.ok).toBe(false)
|
|
118
116
|
if (!res.ok) expect(res.error).toContain('amount too small')
|
|
119
117
|
})
|
|
@@ -134,9 +132,9 @@ describe('write-tool guards (fail before network)', () => {
|
|
|
134
132
|
if (!stake.ok) expect(stake.error).toContain('mainnet only')
|
|
135
133
|
})
|
|
136
134
|
|
|
137
|
-
test('
|
|
138
|
-
const tool =
|
|
139
|
-
expect(tool.name).toBe('
|
|
135
|
+
test('navi.repay is a registered, well-formed tool', () => {
|
|
136
|
+
const tool = makeNaviRepay(stubCtx())
|
|
137
|
+
expect(tool.name).toBe('navi.repay')
|
|
140
138
|
expect(typeof tool.handler).toBe('function')
|
|
141
139
|
})
|
|
142
140
|
})
|