@baseline-markets/cli 0.2.1 → 0.3.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 +86 -2
- package/dist/index.js +1059 -41
- package/package.json +2 -2
- package/skills/baseline-cli/SKILL.md +112 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Cli } from "incur";
|
|
4
|
+
import { Cli as Cli2 } from "incur";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
|
|
6
7
|
// src/commands/info.ts
|
|
7
8
|
import { BaselineSDK } from "@baseline-markets/sdk";
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
arbitrum as arbitrumMainnet,
|
|
13
14
|
base as baseMainnet,
|
|
14
15
|
baseSepolia as baseTestnet,
|
|
16
|
+
hyperEvm,
|
|
17
|
+
hyperliquidEvmTestnet,
|
|
15
18
|
mainnet as ethereumMainnet
|
|
16
19
|
} from "viem/chains";
|
|
17
20
|
var chains = {
|
|
@@ -74,6 +77,32 @@ var chains = {
|
|
|
74
77
|
]
|
|
75
78
|
}
|
|
76
79
|
}
|
|
80
|
+
}),
|
|
81
|
+
hyperevm: defineChain({
|
|
82
|
+
...hyperEvm,
|
|
83
|
+
rpcUrls: {
|
|
84
|
+
default: {
|
|
85
|
+
http: ["https://wispy-hidden-knowledge.hype-mainnet.quiknode.pro/744cca7e0d6ab60a5bff9c19aee2599dbff70471/nanoreth"]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
contracts: {
|
|
89
|
+
multicall3: {
|
|
90
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
hyperevmTestnet: defineChain({
|
|
95
|
+
...hyperliquidEvmTestnet,
|
|
96
|
+
rpcUrls: {
|
|
97
|
+
default: {
|
|
98
|
+
http: ["https://wispy-hidden-knowledge.hype-testnet.quiknode.pro/744cca7e0d6ab60a5bff9c19aee2599dbff70471/nanoreth"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
contracts: {
|
|
102
|
+
multicall3: {
|
|
103
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
77
106
|
})
|
|
78
107
|
};
|
|
79
108
|
var chains_default = chains;
|
|
@@ -133,7 +162,8 @@ var infoCommand = {
|
|
|
133
162
|
transport: http(c.options.rpcUrl)
|
|
134
163
|
}));
|
|
135
164
|
const info = await sdk.getBTokenInfo(token);
|
|
136
|
-
|
|
165
|
+
const status = await sdk.getBTokenStatus(token, info);
|
|
166
|
+
return serializeBTokenInfo({ ...info, ...status }, buildTokenLinks(chain, info.bToken));
|
|
137
167
|
}
|
|
138
168
|
};
|
|
139
169
|
function serializeBTokenInfo(info, links) {
|
|
@@ -207,6 +237,7 @@ import {
|
|
|
207
237
|
createWalletClient,
|
|
208
238
|
http as http2
|
|
209
239
|
} from "viem";
|
|
240
|
+
import { sendCalls, waitForCallsStatus } from "viem/actions";
|
|
210
241
|
import { privateKeyToAccount } from "viem/accounts";
|
|
211
242
|
function parsePrivateKey(value) {
|
|
212
243
|
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
@@ -214,18 +245,6 @@ function parsePrivateKey(value) {
|
|
|
214
245
|
}
|
|
215
246
|
return value;
|
|
216
247
|
}
|
|
217
|
-
async function estimateCallGas(estimate, retryDelayMs = 1000) {
|
|
218
|
-
let lastError;
|
|
219
|
-
for (let attempt = 0;attempt < 5; attempt++) {
|
|
220
|
-
try {
|
|
221
|
-
return await estimate();
|
|
222
|
-
} catch (error) {
|
|
223
|
-
lastError = error;
|
|
224
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
throw lastError;
|
|
228
|
-
}
|
|
229
248
|
async function executeCalls(input) {
|
|
230
249
|
const account = privateKeyToAccount(input.privateKey);
|
|
231
250
|
if (input.expectedAccount && account.address.toLowerCase() !== input.expectedAccount.toLowerCase()) {
|
|
@@ -238,35 +257,52 @@ async function executeCalls(input) {
|
|
|
238
257
|
chain: input.chain,
|
|
239
258
|
transport
|
|
240
259
|
});
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
260
|
+
const { id } = await sendCalls(walletClient, {
|
|
261
|
+
account,
|
|
262
|
+
chain: input.chain,
|
|
263
|
+
calls: input.calls,
|
|
264
|
+
experimental_fallback: true
|
|
265
|
+
});
|
|
266
|
+
const status = await waitForCallsStatus(walletClient, {
|
|
267
|
+
id,
|
|
268
|
+
throwOnFailure: false
|
|
269
|
+
});
|
|
270
|
+
const bundles = [{ id, atomic: status.atomic }];
|
|
271
|
+
const receipts = status.receipts ?? [];
|
|
272
|
+
if (status.status === "failure") {
|
|
273
|
+
throw new Error("Calls bundle failed.");
|
|
274
|
+
}
|
|
275
|
+
if (receipts.length === 0) {
|
|
276
|
+
throw new Error("Calls bundle did not return any transaction receipts.");
|
|
277
|
+
}
|
|
278
|
+
const transactions = receipts.map((receipt, index) => {
|
|
279
|
+
if (!receipt.transactionHash) {
|
|
280
|
+
throw new Error(`Receipt ${index} did not return a transaction hash.`);
|
|
281
|
+
}
|
|
258
282
|
if (receipt.status !== "success") {
|
|
259
|
-
throw new Error(`Transaction ${index} reverted: ${
|
|
283
|
+
throw new Error(`Transaction ${index} reverted: ${receipt.transactionHash}`);
|
|
260
284
|
}
|
|
261
|
-
|
|
285
|
+
return {
|
|
262
286
|
index,
|
|
263
|
-
hash,
|
|
287
|
+
hash: receipt.transactionHash,
|
|
264
288
|
status: receipt.status,
|
|
265
289
|
blockNumber: receipt.blockNumber.toString(),
|
|
266
290
|
gasUsed: receipt.gasUsed.toString()
|
|
267
|
-
}
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
if (input.confirmations) {
|
|
294
|
+
const uniqueHashes = [...new Set(transactions.map((tx) => tx.hash))];
|
|
295
|
+
for (const [index, hash] of uniqueHashes.entries()) {
|
|
296
|
+
const confirmed = await publicClient.waitForTransactionReceipt({
|
|
297
|
+
hash,
|
|
298
|
+
confirmations: input.confirmations
|
|
299
|
+
});
|
|
300
|
+
if (confirmed.status !== "success") {
|
|
301
|
+
throw new Error(`Transaction ${index} reverted: ${hash}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
268
304
|
}
|
|
269
|
-
return { transactions };
|
|
305
|
+
return { bundles, transactions };
|
|
270
306
|
}
|
|
271
307
|
|
|
272
308
|
// src/commands/launch.ts
|
|
@@ -346,12 +382,18 @@ async function buildLaunchArtifact(options) {
|
|
|
346
382
|
salt,
|
|
347
383
|
deployer: account
|
|
348
384
|
});
|
|
349
|
-
const calls = await sdk.calls.launch(launchInput, {
|
|
385
|
+
const calls = await sdk.calls.launch(launchInput, {
|
|
386
|
+
account,
|
|
387
|
+
precomputedBToken: bToken
|
|
388
|
+
});
|
|
389
|
+
const links = buildTokenLinks(chain, bToken);
|
|
350
390
|
return {
|
|
351
391
|
chainId: options.chainId,
|
|
352
392
|
chain: chain.name,
|
|
353
393
|
account,
|
|
354
394
|
bToken,
|
|
395
|
+
appUrl: links.appUrl,
|
|
396
|
+
explorerUrl: links.explorerUrl,
|
|
355
397
|
calls
|
|
356
398
|
};
|
|
357
399
|
}
|
|
@@ -407,6 +449,955 @@ async function executeLaunchArtifact(artifact, options) {
|
|
|
407
449
|
});
|
|
408
450
|
}
|
|
409
451
|
|
|
452
|
+
// src/commands/quote.ts
|
|
453
|
+
import { Cli } from "incur";
|
|
454
|
+
|
|
455
|
+
// src/commands/swap.ts
|
|
456
|
+
import {
|
|
457
|
+
maxInWithSlippage,
|
|
458
|
+
minOutWithSlippage,
|
|
459
|
+
serializeCalls as serializeCalls2,
|
|
460
|
+
simulateCalls
|
|
461
|
+
} from "@baseline-markets/sdk";
|
|
462
|
+
import { z as z5 } from "incur";
|
|
463
|
+
|
|
464
|
+
// src/commands/client.ts
|
|
465
|
+
import { BaselineSDK as BaselineSDK3 } from "@baseline-markets/sdk";
|
|
466
|
+
import { z as z4 } from "incur";
|
|
467
|
+
import {
|
|
468
|
+
createPublicClient as createPublicClient4,
|
|
469
|
+
createWalletClient as createWalletClient2,
|
|
470
|
+
formatUnits as formatUnits2,
|
|
471
|
+
http as http4,
|
|
472
|
+
parseUnits as parseUnits2
|
|
473
|
+
} from "viem";
|
|
474
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
475
|
+
var chainOptions = {
|
|
476
|
+
chainId: z4.coerce.number().default(chains_default.baseSepolia.id).describe("Chain ID."),
|
|
477
|
+
rpcUrl: z4.string().optional().describe("RPC URL.")
|
|
478
|
+
};
|
|
479
|
+
var signerOptions = {
|
|
480
|
+
privateKey: z4.string().optional().describe("Private key. Falls back to BASELINE_PRIVATE_KEY."),
|
|
481
|
+
confirmations: z4.coerce.number().int().nonnegative().default(1).describe("Confirmations to wait for after each transaction.")
|
|
482
|
+
};
|
|
483
|
+
function createReadSdk(options) {
|
|
484
|
+
const chain = getChain(options.chainId);
|
|
485
|
+
const publicClient = createPublicClient4({
|
|
486
|
+
chain,
|
|
487
|
+
transport: http4(options.rpcUrl)
|
|
488
|
+
});
|
|
489
|
+
return { chain, publicClient, sdk: new BaselineSDK3(publicClient) };
|
|
490
|
+
}
|
|
491
|
+
function createWalletSdk(options) {
|
|
492
|
+
const privateKey = options.privateKey ?? process.env.BASELINE_PRIVATE_KEY;
|
|
493
|
+
if (!privateKey)
|
|
494
|
+
throw missingPrivateKeyError();
|
|
495
|
+
const chain = getChain(options.chainId);
|
|
496
|
+
const account = privateKeyToAccount3(parsePrivateKey(privateKey));
|
|
497
|
+
const transport = http4(options.rpcUrl);
|
|
498
|
+
const publicClient = createPublicClient4({ chain, transport });
|
|
499
|
+
const walletClient = createWalletClient2({
|
|
500
|
+
account,
|
|
501
|
+
chain,
|
|
502
|
+
transport
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
account,
|
|
506
|
+
chain,
|
|
507
|
+
publicClient,
|
|
508
|
+
walletClient,
|
|
509
|
+
sdk: new BaselineSDK3(publicClient, walletClient)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function resolvePrivateKey(value) {
|
|
513
|
+
const privateKey = value ?? process.env.BASELINE_PRIVATE_KEY;
|
|
514
|
+
if (!privateKey)
|
|
515
|
+
throw missingPrivateKeyError();
|
|
516
|
+
return parsePrivateKey(privateKey);
|
|
517
|
+
}
|
|
518
|
+
function missingPrivateKeyError() {
|
|
519
|
+
return new Error("--private-key or BASELINE_PRIVATE_KEY is required.");
|
|
520
|
+
}
|
|
521
|
+
function parseTokenUnits(amount, decimals) {
|
|
522
|
+
return parseUnits2(amount, decimals);
|
|
523
|
+
}
|
|
524
|
+
function formatTokenUnits(amount, decimals) {
|
|
525
|
+
const formatted = formatUnits2(amount, decimals);
|
|
526
|
+
return formatted.includes(".") ? formatted.replace(/\.?0+$/, "") : formatted;
|
|
527
|
+
}
|
|
528
|
+
function serializeAmount(amount, decimals) {
|
|
529
|
+
return {
|
|
530
|
+
raw: amount.toString(),
|
|
531
|
+
formatted: formatTokenUnits(amount, decimals),
|
|
532
|
+
decimals
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function serializeTransaction(hash) {
|
|
536
|
+
return { hash };
|
|
537
|
+
}
|
|
538
|
+
function chainName(chain) {
|
|
539
|
+
return chain.name;
|
|
540
|
+
}
|
|
541
|
+
function normalizeUser(user, fallback) {
|
|
542
|
+
return user ?? fallback;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/commands/swap.ts
|
|
546
|
+
var slippageBps = z5.coerce.number().int().min(0).max(1e4).default(100).describe("Slippage tolerance in basis points.");
|
|
547
|
+
var swapModeOptions = {
|
|
548
|
+
buy: z5.boolean().default(false).describe("Buy BTokens."),
|
|
549
|
+
sell: z5.boolean().default(false).describe("Sell BTokens."),
|
|
550
|
+
exactIn: z5.boolean().default(false).describe("Treat --amount as the exact input amount. This is the default."),
|
|
551
|
+
exactOut: z5.boolean().default(false).describe("Treat --amount as the exact output amount.")
|
|
552
|
+
};
|
|
553
|
+
var quoteOptions = z5.object({
|
|
554
|
+
...chainOptions,
|
|
555
|
+
token: address("token").describe("Baseline token address."),
|
|
556
|
+
...swapModeOptions,
|
|
557
|
+
amount: z5.string().describe("Amount in token units."),
|
|
558
|
+
bTokenDecimals: z5.coerce.number().int().min(0).max(36).optional().describe("BToken decimals. Defaults to token metadata."),
|
|
559
|
+
reserveDecimals: z5.coerce.number().int().min(0).max(36).optional().describe("Reserve token decimals. Defaults to token metadata.")
|
|
560
|
+
});
|
|
561
|
+
var executeOptions = quoteOptions.extend({
|
|
562
|
+
...signerOptions,
|
|
563
|
+
slippageBps,
|
|
564
|
+
simulate: z5.boolean().default(false).describe("Simulate the exact swap calls without sending them."),
|
|
565
|
+
approval: z5.enum(["infinite", "exact"]).default("infinite").describe("ERC20 approval amount for the input token.")
|
|
566
|
+
});
|
|
567
|
+
var swapCommand = {
|
|
568
|
+
description: "Quote and execute Baseline swaps.",
|
|
569
|
+
options: executeOptions,
|
|
570
|
+
run: ({ options }) => runSwap(options)
|
|
571
|
+
};
|
|
572
|
+
var swapQuoteCommand = {
|
|
573
|
+
description: "Quote a Baseline swap.",
|
|
574
|
+
options: quoteOptions,
|
|
575
|
+
run: ({ options }) => buildSwapQuote(options)
|
|
576
|
+
};
|
|
577
|
+
async function buildSwapQuote(options) {
|
|
578
|
+
const mode = normalizeSwapOptions(options);
|
|
579
|
+
const { chain, sdk } = createReadSdk(options);
|
|
580
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
581
|
+
const decimals = resolveSwapDecimals(mode, info);
|
|
582
|
+
const amount = parseSwapAmount(mode, decimals);
|
|
583
|
+
const quote = await readQuote(mode, sdk, amount);
|
|
584
|
+
return serializeSwapQuote({
|
|
585
|
+
chainId: chain.id,
|
|
586
|
+
chain: chainName(chain),
|
|
587
|
+
token: options.token,
|
|
588
|
+
reserve: info.reserve,
|
|
589
|
+
side: mode.side,
|
|
590
|
+
exact: mode.exact,
|
|
591
|
+
amount,
|
|
592
|
+
quote,
|
|
593
|
+
decimals,
|
|
594
|
+
slippageBps: 0,
|
|
595
|
+
limitAmount: undefined
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
async function runSwap(options) {
|
|
599
|
+
const mode = normalizeSwapOptions(options);
|
|
600
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
601
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
602
|
+
if (!info.reserve)
|
|
603
|
+
throw new Error("Token reserve is unavailable.");
|
|
604
|
+
const decimals = resolveSwapDecimals(mode, info);
|
|
605
|
+
const amount = parseSwapAmount(mode, decimals);
|
|
606
|
+
const quote = await readQuote(mode, sdk, amount);
|
|
607
|
+
const limitAmount = computeLimitAmount(quote, options.slippageBps);
|
|
608
|
+
const input = inputApproval(mode, info.reserve, amount, limitAmount, decimals);
|
|
609
|
+
const calls = [
|
|
610
|
+
...await sdk.calls.approval.ensure(input.token, sdk.proxy, input.amount, {
|
|
611
|
+
owner: account.address,
|
|
612
|
+
policy: options.approval
|
|
613
|
+
}),
|
|
614
|
+
writeSwapCall(mode, sdk, amount, limitAmount)
|
|
615
|
+
];
|
|
616
|
+
const serializedQuote = serializeSwapQuote({
|
|
617
|
+
chainId: chain.id,
|
|
618
|
+
chain: chainName(chain),
|
|
619
|
+
token: options.token,
|
|
620
|
+
reserve: info.reserve,
|
|
621
|
+
side: mode.side,
|
|
622
|
+
exact: mode.exact,
|
|
623
|
+
amount,
|
|
624
|
+
quote,
|
|
625
|
+
decimals,
|
|
626
|
+
slippageBps: options.slippageBps,
|
|
627
|
+
limitAmount
|
|
628
|
+
});
|
|
629
|
+
if (options.simulate) {
|
|
630
|
+
const simulation = await simulateCalls(publicClient, calls, {
|
|
631
|
+
account: account.address
|
|
632
|
+
});
|
|
633
|
+
return {
|
|
634
|
+
...serializedQuote,
|
|
635
|
+
account: account.address,
|
|
636
|
+
inputApproval: {
|
|
637
|
+
token: input.token,
|
|
638
|
+
amount: serializeAmount(input.amount, input.decimals)
|
|
639
|
+
},
|
|
640
|
+
calls: serializeCalls2(calls),
|
|
641
|
+
simulation
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const execution = await executeCalls({
|
|
645
|
+
chain,
|
|
646
|
+
calls,
|
|
647
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
648
|
+
rpcUrl: options.rpcUrl,
|
|
649
|
+
expectedAccount: account.address,
|
|
650
|
+
confirmations: options.confirmations
|
|
651
|
+
});
|
|
652
|
+
const lastTransaction = execution.transactions.at(-1);
|
|
653
|
+
if (!lastTransaction)
|
|
654
|
+
throw new Error("Swap execution produced no transaction.");
|
|
655
|
+
return {
|
|
656
|
+
...serializedQuote,
|
|
657
|
+
account: account.address,
|
|
658
|
+
inputApproval: {
|
|
659
|
+
token: input.token,
|
|
660
|
+
amount: serializeAmount(input.amount, input.decimals)
|
|
661
|
+
},
|
|
662
|
+
calls: serializeCalls2(calls),
|
|
663
|
+
execution,
|
|
664
|
+
transaction: serializeTransaction(lastTransaction.hash)
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function resolveSwapDecimals(options, info) {
|
|
668
|
+
return {
|
|
669
|
+
bToken: options.bTokenDecimals ?? info.decimals ?? 18,
|
|
670
|
+
reserve: options.reserveDecimals ?? info.reserveDecimals ?? 18
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function parseSwapAmount(options, decimals) {
|
|
674
|
+
return parseTokenUnits(options.amount, amountDenomination(options, decimals));
|
|
675
|
+
}
|
|
676
|
+
function amountDenomination(options, decimals) {
|
|
677
|
+
if (options.side === "buy") {
|
|
678
|
+
return options.exact === "in" ? decimals.reserve : decimals.bToken;
|
|
679
|
+
}
|
|
680
|
+
return options.exact === "in" ? decimals.bToken : decimals.reserve;
|
|
681
|
+
}
|
|
682
|
+
async function readQuote(options, sdk, amount) {
|
|
683
|
+
if (options.side === "buy") {
|
|
684
|
+
if (options.exact === "in") {
|
|
685
|
+
return {
|
|
686
|
+
kind: "buy-exact-in",
|
|
687
|
+
...await sdk.quoteBuyExactIn(options.token, amount)
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
kind: "buy-exact-out",
|
|
692
|
+
...await sdk.quoteBuyExactOut(options.token, amount)
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (options.exact === "in") {
|
|
696
|
+
return {
|
|
697
|
+
kind: "sell-exact-in",
|
|
698
|
+
...await sdk.quoteSellExactIn(options.token, amount)
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
kind: "sell-exact-out",
|
|
703
|
+
...await sdk.quoteSellExactOut(options.token, amount)
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function computeLimitAmount(quote, slippageBps2) {
|
|
707
|
+
switch (quote.kind) {
|
|
708
|
+
case "buy-exact-in":
|
|
709
|
+
return minOut(quote.tokensOut, slippageBps2);
|
|
710
|
+
case "buy-exact-out":
|
|
711
|
+
return maxIn(quote.amountIn, slippageBps2);
|
|
712
|
+
case "sell-exact-in":
|
|
713
|
+
return minOut(quote.amountOut, slippageBps2);
|
|
714
|
+
case "sell-exact-out":
|
|
715
|
+
return maxIn(quote.tokensIn, slippageBps2);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function inputApproval(options, reserve, amount, limitAmount, decimals) {
|
|
719
|
+
if (options.side === "buy") {
|
|
720
|
+
return {
|
|
721
|
+
token: reserve,
|
|
722
|
+
amount: options.exact === "in" ? amount : limitAmount,
|
|
723
|
+
decimals: decimals.reserve
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
token: options.token,
|
|
728
|
+
amount: options.exact === "in" ? amount : limitAmount,
|
|
729
|
+
decimals: decimals.bToken
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function writeSwapCall(options, sdk, amount, limitAmount) {
|
|
733
|
+
if (options.side === "buy") {
|
|
734
|
+
return options.exact === "in" ? sdk.calls.swap.buyTokensExactIn(options.token, amount, limitAmount) : sdk.calls.swap.buyTokensExactOut(options.token, amount, limitAmount);
|
|
735
|
+
}
|
|
736
|
+
return options.exact === "in" ? sdk.calls.swap.sellTokensExactIn(options.token, amount, limitAmount) : sdk.calls.swap.sellTokensExactOut(options.token, amount, limitAmount);
|
|
737
|
+
}
|
|
738
|
+
function serializeSwapQuote(input) {
|
|
739
|
+
return {
|
|
740
|
+
chainId: input.chainId,
|
|
741
|
+
chain: input.chain,
|
|
742
|
+
token: input.token,
|
|
743
|
+
reserve: input.reserve,
|
|
744
|
+
side: input.side,
|
|
745
|
+
exact: input.exact,
|
|
746
|
+
amount: serializeAmount(input.amount, amountDenomination(input, input.decimals)),
|
|
747
|
+
quote: serializeQuote(input),
|
|
748
|
+
slippageBps: input.slippageBps,
|
|
749
|
+
limitAmount: input.limitAmount !== undefined ? serializeAmount(input.limitAmount, limitDenomination(input, input.decimals)) : undefined
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function serializeQuote(input) {
|
|
753
|
+
const fee = serializeAmount(input.quote.fee, input.decimals.reserve);
|
|
754
|
+
const slippage = input.quote.slippage.toString();
|
|
755
|
+
switch (input.quote.kind) {
|
|
756
|
+
case "buy-exact-in":
|
|
757
|
+
return {
|
|
758
|
+
tokensOut: serializeAmount(input.quote.tokensOut, input.decimals.bToken),
|
|
759
|
+
fee,
|
|
760
|
+
slippage
|
|
761
|
+
};
|
|
762
|
+
case "buy-exact-out":
|
|
763
|
+
return {
|
|
764
|
+
amountIn: serializeAmount(input.quote.amountIn, input.decimals.reserve),
|
|
765
|
+
fee,
|
|
766
|
+
slippage
|
|
767
|
+
};
|
|
768
|
+
case "sell-exact-in":
|
|
769
|
+
return {
|
|
770
|
+
amountOut: serializeAmount(input.quote.amountOut, input.decimals.reserve),
|
|
771
|
+
fee,
|
|
772
|
+
slippage
|
|
773
|
+
};
|
|
774
|
+
case "sell-exact-out":
|
|
775
|
+
return {
|
|
776
|
+
tokensIn: serializeAmount(input.quote.tokensIn, input.decimals.bToken),
|
|
777
|
+
fee,
|
|
778
|
+
slippage
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function limitDenomination(options, decimals) {
|
|
783
|
+
if (options.side === "buy") {
|
|
784
|
+
return options.exact === "in" ? decimals.bToken : decimals.reserve;
|
|
785
|
+
}
|
|
786
|
+
return options.exact === "in" ? decimals.reserve : decimals.bToken;
|
|
787
|
+
}
|
|
788
|
+
function minOut(amount, bps) {
|
|
789
|
+
return minOutWithSlippage(amount, bps);
|
|
790
|
+
}
|
|
791
|
+
function maxIn(amount, bps) {
|
|
792
|
+
return maxInWithSlippage(amount, bps);
|
|
793
|
+
}
|
|
794
|
+
function normalizeSwapOptions(options) {
|
|
795
|
+
if (options.buy === options.sell) {
|
|
796
|
+
throw new Error("Specify exactly one of --buy or --sell.");
|
|
797
|
+
}
|
|
798
|
+
if (options.exactIn && options.exactOut) {
|
|
799
|
+
throw new Error("Specify only one of --exact-in or --exact-out.");
|
|
800
|
+
}
|
|
801
|
+
const { buy, sell, exactIn, exactOut, ...rest } = options;
|
|
802
|
+
return {
|
|
803
|
+
...rest,
|
|
804
|
+
side: options.buy ? "buy" : "sell",
|
|
805
|
+
exact: exactOut ? "out" : "in"
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/commands/quote.ts
|
|
810
|
+
var quoteCommand = Cli.create("quote", {
|
|
811
|
+
description: "Quote Baseline actions."
|
|
812
|
+
}).command("swap", swapQuoteCommand);
|
|
813
|
+
|
|
814
|
+
// src/commands/position/options.ts
|
|
815
|
+
import { z as z6 } from "incur";
|
|
816
|
+
var tokenOptions = {
|
|
817
|
+
token: address("token").describe("Baseline token address."),
|
|
818
|
+
bTokenDecimals: z6.coerce.number().int().min(0).max(36).optional().describe("BToken decimals. Defaults to token metadata."),
|
|
819
|
+
reserveDecimals: z6.coerce.number().int().min(0).max(36).optional().describe("Reserve decimals. Defaults to token metadata.")
|
|
820
|
+
};
|
|
821
|
+
var accountOptions = z6.object({
|
|
822
|
+
...chainOptions,
|
|
823
|
+
...tokenOptions,
|
|
824
|
+
user: address("user").describe("User address.")
|
|
825
|
+
});
|
|
826
|
+
var stakeOptions = z6.object({
|
|
827
|
+
...chainOptions,
|
|
828
|
+
...signerOptions,
|
|
829
|
+
...tokenOptions,
|
|
830
|
+
amount: z6.string().describe("BToken amount to stake."),
|
|
831
|
+
user: address("user").optional().describe("Stake recipient. Defaults to signer."),
|
|
832
|
+
approval: z6.enum(["infinite", "exact"]).default("infinite").describe("ERC20 approval amount for BToken staking."),
|
|
833
|
+
simulate: z6.boolean().default(false).describe("Simulate the exact stake calls without sending them.")
|
|
834
|
+
});
|
|
835
|
+
var unstakeOptions = z6.object({
|
|
836
|
+
...chainOptions,
|
|
837
|
+
...signerOptions,
|
|
838
|
+
...tokenOptions,
|
|
839
|
+
amount: z6.string().describe("Staked BToken amount to withdraw."),
|
|
840
|
+
simulate: z6.boolean().default(false).describe("Simulate the exact unstake calls without sending them.")
|
|
841
|
+
});
|
|
842
|
+
var claimOptions = z6.object({
|
|
843
|
+
...chainOptions,
|
|
844
|
+
...signerOptions,
|
|
845
|
+
...tokenOptions,
|
|
846
|
+
user: address("user").optional().describe("Reward recipient. Defaults to signer."),
|
|
847
|
+
asNative: z6.boolean().default(false).describe("Claim wrapped-native rewards as native ETH when supported."),
|
|
848
|
+
simulate: z6.boolean().default(false).describe("Simulate the exact claim calls without sending them.")
|
|
849
|
+
});
|
|
850
|
+
var borrowOptions = z6.object({
|
|
851
|
+
...chainOptions,
|
|
852
|
+
...signerOptions,
|
|
853
|
+
...tokenOptions,
|
|
854
|
+
amount: z6.string().describe("Reserve amount to borrow."),
|
|
855
|
+
recipient: address("recipient").optional().describe("Reserve recipient. Defaults to signer."),
|
|
856
|
+
outputNative: z6.boolean().default(false).describe("Receive wrapped-native reserves as native ETH when supported."),
|
|
857
|
+
simulate: z6.boolean().default(false).describe("Simulate the exact borrow calls without sending them.")
|
|
858
|
+
});
|
|
859
|
+
var repayOptions = z6.object({
|
|
860
|
+
...chainOptions,
|
|
861
|
+
...signerOptions,
|
|
862
|
+
...tokenOptions,
|
|
863
|
+
amount: z6.string().describe("Reserve amount to repay."),
|
|
864
|
+
recipient: address("recipient").optional().describe("Credit account to repay. Defaults to signer."),
|
|
865
|
+
useNative: z6.boolean().default(false).describe("Repay wrapped-native reserves with native ETH when supported."),
|
|
866
|
+
approval: z6.enum(["infinite", "exact"]).default("infinite").describe("ERC20 approval amount for reserve repayment."),
|
|
867
|
+
simulate: z6.boolean().default(false).describe("Simulate the exact repay calls without sending them.")
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/commands/position/read.ts
|
|
871
|
+
import { abis } from "@baseline-markets/sdk";
|
|
872
|
+
import { BaseError, ContractFunctionRevertedError, erc20Abi } from "viem";
|
|
873
|
+
async function readPosition(options) {
|
|
874
|
+
const { chain, publicClient, sdk } = createReadSdk(options);
|
|
875
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
876
|
+
const snapshot = await readPositionSnapshot({
|
|
877
|
+
publicClient,
|
|
878
|
+
proxy: sdk.proxy,
|
|
879
|
+
token: options.token,
|
|
880
|
+
user: options.user,
|
|
881
|
+
reserve: info.reserve
|
|
882
|
+
});
|
|
883
|
+
return serializePosition({
|
|
884
|
+
chainId: chain.id,
|
|
885
|
+
chain: chainName(chain),
|
|
886
|
+
token: options.token,
|
|
887
|
+
name: info.name,
|
|
888
|
+
symbol: info.symbol,
|
|
889
|
+
reserve: info.reserve,
|
|
890
|
+
reserveSymbol: snapshot.reserveSymbol,
|
|
891
|
+
user: options.user,
|
|
892
|
+
account: snapshot.account,
|
|
893
|
+
creditAccount: snapshot.creditAccount,
|
|
894
|
+
maxBorrow: snapshot.maxBorrow,
|
|
895
|
+
earned: snapshot.earned,
|
|
896
|
+
bTokenDecimals: options.bTokenDecimals ?? info.decimals ?? 18,
|
|
897
|
+
reserveDecimals: options.reserveDecimals ?? info.reserveDecimals ?? 18
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
async function readPositionSnapshot(input) {
|
|
901
|
+
const contracts = [
|
|
902
|
+
{
|
|
903
|
+
address: input.proxy,
|
|
904
|
+
abi: abis.bLens,
|
|
905
|
+
functionName: "stakedPosition",
|
|
906
|
+
args: [input.token, input.user]
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
address: input.proxy,
|
|
910
|
+
abi: abis.bLens,
|
|
911
|
+
functionName: "creditAccount",
|
|
912
|
+
args: [input.token, input.user]
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
address: input.proxy,
|
|
916
|
+
abi: abis.bCredit,
|
|
917
|
+
functionName: "getMaxBorrow",
|
|
918
|
+
args: [input.token, input.user]
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
address: input.proxy,
|
|
922
|
+
abi: abis.bStaking,
|
|
923
|
+
functionName: "getEarned",
|
|
924
|
+
args: [input.token, input.user]
|
|
925
|
+
},
|
|
926
|
+
...input.reserve ? [
|
|
927
|
+
{
|
|
928
|
+
address: input.reserve,
|
|
929
|
+
abi: erc20Abi,
|
|
930
|
+
functionName: "symbol"
|
|
931
|
+
}
|
|
932
|
+
] : []
|
|
933
|
+
];
|
|
934
|
+
const results = await input.publicClient.multicall({
|
|
935
|
+
allowFailure: true,
|
|
936
|
+
contracts
|
|
937
|
+
});
|
|
938
|
+
const staked = requiredResult(results[0], "staked position");
|
|
939
|
+
const credit = requiredResult(results[1], "credit account");
|
|
940
|
+
const maxBorrow = results[2]?.status === "success" ? results[2].result : 0n;
|
|
941
|
+
const earned = requiredResult(results[3], "earned rewards");
|
|
942
|
+
const reserveSymbol = results[4]?.status === "success" ? results[4].result : undefined;
|
|
943
|
+
return {
|
|
944
|
+
account: {
|
|
945
|
+
amount: staked[0],
|
|
946
|
+
locked: staked[1],
|
|
947
|
+
earned: staked[2],
|
|
948
|
+
userAccumulator: staked[3]
|
|
949
|
+
},
|
|
950
|
+
creditAccount: {
|
|
951
|
+
collateral: credit[0],
|
|
952
|
+
debt: credit[1]
|
|
953
|
+
},
|
|
954
|
+
maxBorrow,
|
|
955
|
+
earned,
|
|
956
|
+
reserveSymbol
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function requiredResult(result, label) {
|
|
960
|
+
if (result?.status === "success")
|
|
961
|
+
return result.result;
|
|
962
|
+
throw new Error(`Failed to read ${label}.`);
|
|
963
|
+
}
|
|
964
|
+
function formatPosition(position) {
|
|
965
|
+
const symbol = position.symbol ?? "BTokens";
|
|
966
|
+
const reserveUnit = position.reserveSymbol ?? position.reserve ?? "reserve";
|
|
967
|
+
const title = position.name ? position.symbol ? `${position.name} (${position.symbol})` : position.name : position.symbol ? position.symbol : "Position";
|
|
968
|
+
return [
|
|
969
|
+
title,
|
|
970
|
+
`Chain: ${position.chain} (${position.chainId})`,
|
|
971
|
+
`Token: ${position.token}`,
|
|
972
|
+
`User: ${position.user}`,
|
|
973
|
+
"",
|
|
974
|
+
table([
|
|
975
|
+
["Total staked", position.amount.formatted, symbol],
|
|
976
|
+
["Claimable", position.earned.formatted, reserveUnit],
|
|
977
|
+
["Collateral", position.collateral.formatted, symbol],
|
|
978
|
+
["Debt", position.debt.formatted, reserveUnit],
|
|
979
|
+
["Max borrow", position.maxBorrow.formatted, reserveUnit]
|
|
980
|
+
])
|
|
981
|
+
].join(`
|
|
982
|
+
`);
|
|
983
|
+
}
|
|
984
|
+
async function readMaxBorrow(sdk, token, user) {
|
|
985
|
+
try {
|
|
986
|
+
return await sdk.getMaxBorrow(token, user);
|
|
987
|
+
} catch (error) {
|
|
988
|
+
if (error instanceof BaseError && error.walk((cause) => cause instanceof ContractFunctionRevertedError)) {
|
|
989
|
+
return 0n;
|
|
990
|
+
}
|
|
991
|
+
throw error;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function serializePosition(input) {
|
|
995
|
+
return {
|
|
996
|
+
chainId: input.chainId,
|
|
997
|
+
chain: input.chain,
|
|
998
|
+
token: input.token,
|
|
999
|
+
name: input.name,
|
|
1000
|
+
symbol: input.symbol,
|
|
1001
|
+
reserve: input.reserve,
|
|
1002
|
+
reserveSymbol: input.reserveSymbol,
|
|
1003
|
+
user: input.user,
|
|
1004
|
+
amount: serializeAmount(input.account.amount, input.bTokenDecimals),
|
|
1005
|
+
locked: serializeAmount(input.account.locked, input.bTokenDecimals),
|
|
1006
|
+
unlocked: serializeAmount(input.account.amount - input.account.locked, input.bTokenDecimals),
|
|
1007
|
+
earned: serializeAmount(input.earned ?? input.account.earned, input.reserveDecimals),
|
|
1008
|
+
collateral: serializeAmount(input.creditAccount.collateral, input.bTokenDecimals),
|
|
1009
|
+
debt: serializeAmount(input.creditAccount.debt, input.reserveDecimals),
|
|
1010
|
+
maxBorrow: serializeAmount(input.maxBorrow, input.reserveDecimals),
|
|
1011
|
+
storedEarned: serializeAmount(input.account.earned, input.reserveDecimals),
|
|
1012
|
+
userAccumulator: input.account.userAccumulator.toString()
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function table(rows) {
|
|
1016
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length), 5);
|
|
1017
|
+
const amountWidth = Math.max(...rows.map(([, amount]) => amount.length), 6);
|
|
1018
|
+
const unitWidth = Math.max(...rows.map(([, , unit]) => unit.length), 4);
|
|
1019
|
+
const line = `| ${"-".repeat(labelWidth)} | ${"-".repeat(amountWidth)} | ${"-".repeat(unitWidth)} |`;
|
|
1020
|
+
return [
|
|
1021
|
+
`| ${"Metric".padEnd(labelWidth)} | ${"Amount".padStart(amountWidth)} | ${"Unit".padEnd(unitWidth)} |`,
|
|
1022
|
+
line,
|
|
1023
|
+
...rows.map(([label, amount, unit]) => `| ${label.padEnd(labelWidth)} | ${amount.padStart(amountWidth)} | ${unit.padEnd(unitWidth)} |`)
|
|
1024
|
+
].join(`
|
|
1025
|
+
`);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/commands/position/tx.ts
|
|
1029
|
+
import { serializeCalls as serializeCalls3, simulateCalls as simulateCalls2 } from "@baseline-markets/sdk";
|
|
1030
|
+
async function simulatePositionCalls(publicClient, calls, account) {
|
|
1031
|
+
return {
|
|
1032
|
+
calls: serializeCalls3(calls),
|
|
1033
|
+
simulation: await simulateCalls2(publicClient, calls, {
|
|
1034
|
+
account
|
|
1035
|
+
})
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
async function executePositionCalls(input) {
|
|
1039
|
+
const execution = await executeCalls({
|
|
1040
|
+
chain: input.chain,
|
|
1041
|
+
calls: input.calls,
|
|
1042
|
+
privateKey: input.privateKey,
|
|
1043
|
+
rpcUrl: input.rpcUrl,
|
|
1044
|
+
expectedAccount: input.account,
|
|
1045
|
+
confirmations: input.confirmations
|
|
1046
|
+
});
|
|
1047
|
+
const lastTransaction = execution.transactions.at(-1);
|
|
1048
|
+
if (!lastTransaction) {
|
|
1049
|
+
throw new Error(`Position ${input.action} produced no transaction.`);
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
calls: serializeCalls3(input.calls),
|
|
1053
|
+
execution,
|
|
1054
|
+
transaction: serializeTransaction(lastTransaction.hash)
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/commands/position/actions.ts
|
|
1059
|
+
async function stakePosition(options) {
|
|
1060
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
1061
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
1062
|
+
const bTokenDecimals = options.bTokenDecimals ?? info.decimals ?? 18;
|
|
1063
|
+
const reserveDecimals = options.reserveDecimals ?? info.reserveDecimals ?? 18;
|
|
1064
|
+
const amount = parseTokenUnits(options.amount, bTokenDecimals);
|
|
1065
|
+
const user = normalizeUser(options.user, account.address);
|
|
1066
|
+
const calls = [
|
|
1067
|
+
...await sdk.calls.approval.ensure(options.token, sdk.proxy, amount, {
|
|
1068
|
+
owner: account.address,
|
|
1069
|
+
policy: options.approval
|
|
1070
|
+
}),
|
|
1071
|
+
sdk.calls.staking.deposit(options.token, user, amount)
|
|
1072
|
+
];
|
|
1073
|
+
if (options.simulate) {
|
|
1074
|
+
return {
|
|
1075
|
+
chainId: chain.id,
|
|
1076
|
+
chain: chainName(chain),
|
|
1077
|
+
token: options.token,
|
|
1078
|
+
user,
|
|
1079
|
+
signer: account.address,
|
|
1080
|
+
deposited: serializeAmount(amount, bTokenDecimals),
|
|
1081
|
+
...await simulatePositionCalls(publicClient, calls, account.address)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
const execution = await executePositionCalls({
|
|
1085
|
+
action: "stake",
|
|
1086
|
+
account: account.address,
|
|
1087
|
+
chain,
|
|
1088
|
+
calls,
|
|
1089
|
+
confirmations: options.confirmations,
|
|
1090
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
1091
|
+
rpcUrl: options.rpcUrl
|
|
1092
|
+
});
|
|
1093
|
+
const [stakeAccount, creditAccount, maxBorrow, earned] = await Promise.all([
|
|
1094
|
+
sdk.getStakedAccount(options.token, user),
|
|
1095
|
+
sdk.getCreditAccount(options.token, user),
|
|
1096
|
+
readMaxBorrow(sdk, options.token, user),
|
|
1097
|
+
sdk.getEarned(options.token, user)
|
|
1098
|
+
]);
|
|
1099
|
+
return {
|
|
1100
|
+
...serializePosition({
|
|
1101
|
+
chainId: chain.id,
|
|
1102
|
+
chain: chainName(chain),
|
|
1103
|
+
token: options.token,
|
|
1104
|
+
name: info.name,
|
|
1105
|
+
symbol: info.symbol,
|
|
1106
|
+
reserve: info.reserve,
|
|
1107
|
+
user,
|
|
1108
|
+
account: stakeAccount,
|
|
1109
|
+
creditAccount,
|
|
1110
|
+
maxBorrow,
|
|
1111
|
+
earned,
|
|
1112
|
+
bTokenDecimals,
|
|
1113
|
+
reserveDecimals
|
|
1114
|
+
}),
|
|
1115
|
+
signer: account.address,
|
|
1116
|
+
deposited: serializeAmount(amount, bTokenDecimals),
|
|
1117
|
+
...execution
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
async function unstakePosition(options) {
|
|
1121
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
1122
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
1123
|
+
const bTokenDecimals = options.bTokenDecimals ?? info.decimals ?? 18;
|
|
1124
|
+
const reserveDecimals = options.reserveDecimals ?? info.reserveDecimals ?? 18;
|
|
1125
|
+
const amount = parseTokenUnits(options.amount, bTokenDecimals);
|
|
1126
|
+
const calls = [sdk.calls.staking.withdraw(options.token, amount)];
|
|
1127
|
+
if (options.simulate) {
|
|
1128
|
+
return {
|
|
1129
|
+
chainId: chain.id,
|
|
1130
|
+
chain: chainName(chain),
|
|
1131
|
+
token: options.token,
|
|
1132
|
+
user: account.address,
|
|
1133
|
+
signer: account.address,
|
|
1134
|
+
withdrawn: serializeAmount(amount, bTokenDecimals),
|
|
1135
|
+
...await simulatePositionCalls(publicClient, calls, account.address)
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const execution = await executePositionCalls({
|
|
1139
|
+
action: "unstake",
|
|
1140
|
+
account: account.address,
|
|
1141
|
+
chain,
|
|
1142
|
+
calls,
|
|
1143
|
+
confirmations: options.confirmations,
|
|
1144
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
1145
|
+
rpcUrl: options.rpcUrl
|
|
1146
|
+
});
|
|
1147
|
+
const [stakeAccount, creditAccount, maxBorrow, earned] = await Promise.all([
|
|
1148
|
+
sdk.getStakedAccount(options.token, account.address),
|
|
1149
|
+
sdk.getCreditAccount(options.token, account.address),
|
|
1150
|
+
readMaxBorrow(sdk, options.token, account.address),
|
|
1151
|
+
sdk.getEarned(options.token, account.address)
|
|
1152
|
+
]);
|
|
1153
|
+
return {
|
|
1154
|
+
...serializePosition({
|
|
1155
|
+
chainId: chain.id,
|
|
1156
|
+
chain: chainName(chain),
|
|
1157
|
+
token: options.token,
|
|
1158
|
+
name: info.name,
|
|
1159
|
+
symbol: info.symbol,
|
|
1160
|
+
reserve: info.reserve,
|
|
1161
|
+
user: account.address,
|
|
1162
|
+
account: stakeAccount,
|
|
1163
|
+
creditAccount,
|
|
1164
|
+
maxBorrow,
|
|
1165
|
+
earned,
|
|
1166
|
+
bTokenDecimals,
|
|
1167
|
+
reserveDecimals
|
|
1168
|
+
}),
|
|
1169
|
+
signer: account.address,
|
|
1170
|
+
withdrawn: serializeAmount(amount, bTokenDecimals),
|
|
1171
|
+
...execution
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
async function claimPosition(options) {
|
|
1175
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
1176
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
1177
|
+
const bTokenDecimals = options.bTokenDecimals ?? info.decimals ?? 18;
|
|
1178
|
+
const reserveDecimals = options.reserveDecimals ?? info.reserveDecimals ?? 18;
|
|
1179
|
+
const user = normalizeUser(options.user, account.address);
|
|
1180
|
+
const amount = await sdk.getEarned(options.token, user);
|
|
1181
|
+
const calls = [
|
|
1182
|
+
sdk.calls.staking.claim(options.token, user, options.asNative)
|
|
1183
|
+
];
|
|
1184
|
+
if (options.simulate) {
|
|
1185
|
+
return {
|
|
1186
|
+
chainId: chain.id,
|
|
1187
|
+
chain: chainName(chain),
|
|
1188
|
+
token: options.token,
|
|
1189
|
+
user,
|
|
1190
|
+
signer: account.address,
|
|
1191
|
+
claimed: serializeAmount(amount, reserveDecimals),
|
|
1192
|
+
asNative: options.asNative,
|
|
1193
|
+
...await simulatePositionCalls(publicClient, calls, account.address)
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
const execution = await executePositionCalls({
|
|
1197
|
+
action: "claim",
|
|
1198
|
+
account: account.address,
|
|
1199
|
+
chain,
|
|
1200
|
+
calls,
|
|
1201
|
+
confirmations: options.confirmations,
|
|
1202
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
1203
|
+
rpcUrl: options.rpcUrl
|
|
1204
|
+
});
|
|
1205
|
+
const [stakeAccount, creditAccount, maxBorrow, earned] = await Promise.all([
|
|
1206
|
+
sdk.getStakedAccount(options.token, user),
|
|
1207
|
+
sdk.getCreditAccount(options.token, user),
|
|
1208
|
+
readMaxBorrow(sdk, options.token, user),
|
|
1209
|
+
sdk.getEarned(options.token, user)
|
|
1210
|
+
]);
|
|
1211
|
+
return {
|
|
1212
|
+
...serializePosition({
|
|
1213
|
+
chainId: chain.id,
|
|
1214
|
+
chain: chainName(chain),
|
|
1215
|
+
token: options.token,
|
|
1216
|
+
name: info.name,
|
|
1217
|
+
symbol: info.symbol,
|
|
1218
|
+
reserve: info.reserve,
|
|
1219
|
+
user,
|
|
1220
|
+
account: stakeAccount,
|
|
1221
|
+
creditAccount,
|
|
1222
|
+
maxBorrow,
|
|
1223
|
+
earned,
|
|
1224
|
+
bTokenDecimals,
|
|
1225
|
+
reserveDecimals
|
|
1226
|
+
}),
|
|
1227
|
+
signer: account.address,
|
|
1228
|
+
claimed: serializeAmount(amount, reserveDecimals),
|
|
1229
|
+
asNative: options.asNative,
|
|
1230
|
+
...execution
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
async function borrowPosition(options) {
|
|
1234
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
1235
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
1236
|
+
const reserveDecimals = options.reserveDecimals ?? info.reserveDecimals ?? 18;
|
|
1237
|
+
const amount = parseTokenUnits(options.amount, reserveDecimals);
|
|
1238
|
+
const recipient = normalizeUser(options.recipient, account.address);
|
|
1239
|
+
const calls = [
|
|
1240
|
+
sdk.calls.credit.borrow(options.token, amount, recipient, {
|
|
1241
|
+
outputNative: options.outputNative
|
|
1242
|
+
})
|
|
1243
|
+
];
|
|
1244
|
+
if (options.simulate) {
|
|
1245
|
+
return {
|
|
1246
|
+
chainId: chain.id,
|
|
1247
|
+
chain: chainName(chain),
|
|
1248
|
+
token: options.token,
|
|
1249
|
+
signer: account.address,
|
|
1250
|
+
recipient,
|
|
1251
|
+
borrowed: serializeAmount(amount, reserveDecimals),
|
|
1252
|
+
outputNative: options.outputNative,
|
|
1253
|
+
...await simulatePositionCalls(publicClient, calls, account.address)
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
const execution = await executePositionCalls({
|
|
1257
|
+
action: "borrow",
|
|
1258
|
+
account: account.address,
|
|
1259
|
+
chain,
|
|
1260
|
+
calls,
|
|
1261
|
+
confirmations: options.confirmations,
|
|
1262
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
1263
|
+
rpcUrl: options.rpcUrl
|
|
1264
|
+
});
|
|
1265
|
+
const [stakeAccount, creditAccount, maxBorrow, earned] = await Promise.all([
|
|
1266
|
+
sdk.getStakedAccount(options.token, account.address),
|
|
1267
|
+
sdk.getCreditAccount(options.token, account.address),
|
|
1268
|
+
readMaxBorrow(sdk, options.token, account.address),
|
|
1269
|
+
sdk.getEarned(options.token, account.address)
|
|
1270
|
+
]);
|
|
1271
|
+
return {
|
|
1272
|
+
...serializePosition({
|
|
1273
|
+
chainId: chain.id,
|
|
1274
|
+
chain: chainName(chain),
|
|
1275
|
+
token: options.token,
|
|
1276
|
+
name: info.name,
|
|
1277
|
+
symbol: info.symbol,
|
|
1278
|
+
reserve: info.reserve,
|
|
1279
|
+
user: account.address,
|
|
1280
|
+
account: stakeAccount,
|
|
1281
|
+
creditAccount,
|
|
1282
|
+
maxBorrow,
|
|
1283
|
+
earned,
|
|
1284
|
+
bTokenDecimals: options.bTokenDecimals ?? info.decimals ?? 18,
|
|
1285
|
+
reserveDecimals
|
|
1286
|
+
}),
|
|
1287
|
+
signer: account.address,
|
|
1288
|
+
recipient,
|
|
1289
|
+
borrowed: serializeAmount(amount, reserveDecimals),
|
|
1290
|
+
outputNative: options.outputNative,
|
|
1291
|
+
...execution
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
async function repayPosition(options) {
|
|
1295
|
+
const { chain, publicClient, sdk, account } = createWalletSdk(options);
|
|
1296
|
+
const info = await sdk.getBTokenInfo(options.token);
|
|
1297
|
+
if (!options.useNative && !info.reserve) {
|
|
1298
|
+
throw new Error("Token reserve is unavailable.");
|
|
1299
|
+
}
|
|
1300
|
+
const reserveDecimals = options.reserveDecimals ?? info.reserveDecimals ?? 18;
|
|
1301
|
+
const amount = parseTokenUnits(options.amount, reserveDecimals);
|
|
1302
|
+
const recipient = normalizeUser(options.recipient, account.address);
|
|
1303
|
+
const calls = [
|
|
1304
|
+
...options.useNative ? [] : await sdk.calls.approval.ensure(info.reserve, sdk.proxy, amount, {
|
|
1305
|
+
owner: account.address,
|
|
1306
|
+
policy: options.approval
|
|
1307
|
+
}),
|
|
1308
|
+
sdk.calls.credit.repay(options.token, amount, recipient, {
|
|
1309
|
+
useNative: options.useNative
|
|
1310
|
+
})
|
|
1311
|
+
];
|
|
1312
|
+
if (options.simulate) {
|
|
1313
|
+
return {
|
|
1314
|
+
chainId: chain.id,
|
|
1315
|
+
chain: chainName(chain),
|
|
1316
|
+
token: options.token,
|
|
1317
|
+
signer: account.address,
|
|
1318
|
+
recipient,
|
|
1319
|
+
repaid: serializeAmount(amount, reserveDecimals),
|
|
1320
|
+
useNative: options.useNative,
|
|
1321
|
+
...await simulatePositionCalls(publicClient, calls, account.address)
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
const execution = await executePositionCalls({
|
|
1325
|
+
action: "repay",
|
|
1326
|
+
account: account.address,
|
|
1327
|
+
chain,
|
|
1328
|
+
calls,
|
|
1329
|
+
confirmations: options.confirmations,
|
|
1330
|
+
privateKey: resolvePrivateKey(options.privateKey),
|
|
1331
|
+
rpcUrl: options.rpcUrl
|
|
1332
|
+
});
|
|
1333
|
+
const [stakeAccount, creditAccount, maxBorrow, earned] = await Promise.all([
|
|
1334
|
+
sdk.getStakedAccount(options.token, recipient),
|
|
1335
|
+
sdk.getCreditAccount(options.token, recipient),
|
|
1336
|
+
readMaxBorrow(sdk, options.token, recipient),
|
|
1337
|
+
sdk.getEarned(options.token, recipient)
|
|
1338
|
+
]);
|
|
1339
|
+
return {
|
|
1340
|
+
...serializePosition({
|
|
1341
|
+
chainId: chain.id,
|
|
1342
|
+
chain: chainName(chain),
|
|
1343
|
+
token: options.token,
|
|
1344
|
+
name: info.name,
|
|
1345
|
+
symbol: info.symbol,
|
|
1346
|
+
reserve: info.reserve,
|
|
1347
|
+
user: recipient,
|
|
1348
|
+
account: stakeAccount,
|
|
1349
|
+
creditAccount,
|
|
1350
|
+
maxBorrow,
|
|
1351
|
+
earned,
|
|
1352
|
+
bTokenDecimals: options.bTokenDecimals ?? info.decimals ?? 18,
|
|
1353
|
+
reserveDecimals
|
|
1354
|
+
}),
|
|
1355
|
+
signer: account.address,
|
|
1356
|
+
recipient,
|
|
1357
|
+
repaid: serializeAmount(amount, reserveDecimals),
|
|
1358
|
+
useNative: options.useNative,
|
|
1359
|
+
...execution
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/commands/position/index.ts
|
|
1364
|
+
var positionCommand = {
|
|
1365
|
+
description: "Inspect and manage a Baseline position.",
|
|
1366
|
+
options: accountOptions,
|
|
1367
|
+
async run({
|
|
1368
|
+
options,
|
|
1369
|
+
formatExplicit
|
|
1370
|
+
}) {
|
|
1371
|
+
const position = await readPosition(options);
|
|
1372
|
+
return formatExplicit ? position : formatPosition(position);
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
var positionStakeCommand = {
|
|
1376
|
+
description: "Stake BTokens into a Baseline position.",
|
|
1377
|
+
options: stakeOptions,
|
|
1378
|
+
run: ({ options }) => stakePosition(options)
|
|
1379
|
+
};
|
|
1380
|
+
var positionUnstakeCommand = {
|
|
1381
|
+
description: "Withdraw staked BTokens from a Baseline position.",
|
|
1382
|
+
options: unstakeOptions,
|
|
1383
|
+
run: ({ options }) => unstakePosition(options)
|
|
1384
|
+
};
|
|
1385
|
+
var positionClaimCommand = {
|
|
1386
|
+
description: "Claim position rewards.",
|
|
1387
|
+
options: claimOptions,
|
|
1388
|
+
run: ({ options }) => claimPosition(options)
|
|
1389
|
+
};
|
|
1390
|
+
var positionBorrowCommand = {
|
|
1391
|
+
description: "Borrow reserves against a Baseline position.",
|
|
1392
|
+
options: borrowOptions,
|
|
1393
|
+
run: ({ options }) => borrowPosition(options)
|
|
1394
|
+
};
|
|
1395
|
+
var positionRepayCommand = {
|
|
1396
|
+
description: "Repay reserves for a Baseline position.",
|
|
1397
|
+
options: repayOptions,
|
|
1398
|
+
run: ({ options }) => repayPosition(options)
|
|
1399
|
+
};
|
|
1400
|
+
|
|
410
1401
|
// src/env.ts
|
|
411
1402
|
import { existsSync, readFileSync } from "node:fs";
|
|
412
1403
|
import { dirname as dirname2, resolve } from "node:path";
|
|
@@ -447,18 +1438,45 @@ function loadEnv(path) {
|
|
|
447
1438
|
|
|
448
1439
|
// src/index.ts
|
|
449
1440
|
loadCliEnv();
|
|
450
|
-
var cli =
|
|
1441
|
+
var cli = Cli2.create("baseline", {
|
|
451
1442
|
version: "0.2.0",
|
|
452
1443
|
description: "Baseline Markets CLI",
|
|
453
1444
|
sync: {
|
|
454
1445
|
include: [skillsGlob],
|
|
455
1446
|
suggestions: [
|
|
456
1447
|
"build launch calls for a new Baseline token",
|
|
457
|
-
"output launch calls as JSON for wallet_sendCalls"
|
|
1448
|
+
"output launch calls as JSON for wallet_sendCalls",
|
|
1449
|
+
"quote a Baseline token swap",
|
|
1450
|
+
"manage a Baseline position"
|
|
458
1451
|
]
|
|
459
1452
|
}
|
|
460
|
-
}).command("launch", launchCommand).command("info", infoCommand);
|
|
461
|
-
|
|
1453
|
+
}).command("launch", launchCommand).command("info", infoCommand).command(quoteCommand).command("swap", swapCommand).command("position", positionCommand).command("position stake", positionStakeCommand).command("position unstake", positionUnstakeCommand).command("position claim", positionClaimCommand).command("position borrow", positionBorrowCommand).command("position repay", positionRepayCommand);
|
|
1454
|
+
function normalizeArgv(argv) {
|
|
1455
|
+
const [command, subcommand, ...rest] = argv;
|
|
1456
|
+
switch (command) {
|
|
1457
|
+
case "position":
|
|
1458
|
+
switch (subcommand) {
|
|
1459
|
+
case "stake":
|
|
1460
|
+
case "unstake":
|
|
1461
|
+
case "claim":
|
|
1462
|
+
case "borrow":
|
|
1463
|
+
case "repay":
|
|
1464
|
+
if (rest[0] && !rest[0].startsWith("-")) {
|
|
1465
|
+
return [`position ${subcommand} ${rest[0]}`, ...rest.slice(1)];
|
|
1466
|
+
}
|
|
1467
|
+
return [`position ${subcommand}`, ...rest];
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return argv;
|
|
1471
|
+
}
|
|
1472
|
+
function serveCli(argv = process.argv.slice(2), options) {
|
|
1473
|
+
return cli.serve(normalizeArgv(argv), options);
|
|
1474
|
+
}
|
|
1475
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
1476
|
+
await serveCli();
|
|
1477
|
+
}
|
|
462
1478
|
export {
|
|
1479
|
+
serveCli,
|
|
1480
|
+
normalizeArgv,
|
|
463
1481
|
cli
|
|
464
1482
|
};
|