Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-pans-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aave/cli": patch
---

**feat:** add position market swap commands for supply, borrow and repay
110 changes: 110 additions & 0 deletions packages/cli/src/commands/action/swap/borrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
type BorrowSwapQuoteRequest,
bigDecimal,
invariant,
reserveId,
userBorrowItemId,
} from '@aave/client';
import { borrowSwapQuote } from '@aave/client/actions';
import { Flags } from '@oclif/core';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

import * as common from '../../../common.js';
import {
executePositionMarketSwap,
quoteRows,
} from '../../../helpers/swaps.js';

export default class ActionSwapBorrow extends common.V4Command {
static override description =
'Swap borrowed position into another reserve using market mode';

static override flags = {
'debt-position-id': Flags.string({
required: true,
description: 'Debt position ID to swap',
}),
'buy-reserve-id': Flags.string({
required: true,
description: 'Reserve ID to receive debt on',
}),
amount: Flags.string({
required: true,
description: 'Amount to swap',
}),
'quote-only': Flags.boolean({
required: false,
default: false,
description: 'Only fetch and display quote',
}),
address: common.address({
required: false,
description: 'User address for quote generation',
}),
'private-key': common.privateKey({
required: false,
}),
};

protected override headers = [{ value: 'Field' }, { value: 'Value' }];

async run(): Promise<unknown> {
const { flags } = await this.parse(ActionSwapBorrow);

const amount = flags.amount.trim();
invariant(amount.length > 0, 'Amount cannot be empty');

const user = common.userAddressFromFlagOrEnv(flags.address);

const request: BorrowSwapQuoteRequest = {
market: {
debtPosition: userBorrowItemId(flags['debt-position-id']),
buyReserve: reserveId(flags['buy-reserve-id']),
amount: bigDecimal(amount),
user,
},
};

const quote = await borrowSwapQuote(this.client, request);
if (quote.isErr()) {
this.error(quote.error);
}

this.display(quoteRows(quote.value.quote));

if (flags['quote-only']) {
return this.toSuccessJson(quote.value);
}

const privateKey = (flags['private-key'] ??
process.env.PRIVATE_KEY) as `0x${string}`;
invariant(
privateKey,
'Provide --private-key or PRIVATE_KEY environment variable',
);

const wallet = createWalletClient({
account: privateKeyToAccount(privateKey),
transport: http(),
});

const result = await executePositionMarketSwap(
this.client,
wallet,
quote.value,
);
if (result.isErr()) {
this.error(String(result.error));
}

const receipt = result.value;

this.display([
['Swap ID', receipt.id],
['Created At', receipt.createdAt.toISOString()],
]);

return this.toSuccessJson(receipt);
}
}
110 changes: 110 additions & 0 deletions packages/cli/src/commands/action/swap/repay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
bigDecimal,
invariant,
type RepayWithSupplyQuoteRequest,
reserveId,
userBorrowItemId,
} from '@aave/client';
import { repayWithSupplyQuote } from '@aave/client/actions';
import { Flags } from '@oclif/core';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

import * as common from '../../../common.js';
import {
executePositionMarketSwap,
quoteRows,
} from '../../../helpers/swaps.js';

export default class ActionSwapRepay extends common.V4Command {
static override description =
'Repay debt using another supply position through market swap';

static override flags = {
'debt-position-id': Flags.string({
required: true,
description: 'Debt position ID to repay',
}),
'repay-with-reserve-id': Flags.string({
required: true,
description: 'Reserve ID used as repayment source',
}),
amount: Flags.string({
required: true,
description: 'Amount to repay',
}),
'quote-only': Flags.boolean({
required: false,
default: false,
description: 'Only fetch and display quote',
}),
address: common.address({
required: false,
description: 'User address for quote generation',
}),
'private-key': common.privateKey({
required: false,
}),
};

protected override headers = [{ value: 'Field' }, { value: 'Value' }];

async run(): Promise<unknown> {
const { flags } = await this.parse(ActionSwapRepay);

const amount = flags.amount.trim();
invariant(amount.length > 0, 'Amount cannot be empty');

const user = common.userAddressFromFlagOrEnv(flags.address);

const request: RepayWithSupplyQuoteRequest = {
market: {
debtPosition: userBorrowItemId(flags['debt-position-id']),
repayWithReserve: reserveId(flags['repay-with-reserve-id']),
amount: bigDecimal(amount),
user,
},
};

const quote = await repayWithSupplyQuote(this.client, request);
if (quote.isErr()) {
this.error(quote.error);
}

this.display(quoteRows(quote.value.quote));

if (flags['quote-only']) {
return this.toSuccessJson(quote.value);
}

const privateKey = (flags['private-key'] ??
process.env.PRIVATE_KEY) as `0x${string}`;
invariant(
privateKey,
'Provide --private-key or PRIVATE_KEY environment variable',
);

const wallet = createWalletClient({
account: privateKeyToAccount(privateKey),
transport: http(),
});

const result = await executePositionMarketSwap(
this.client,
wallet,
quote.value,
);
if (result.isErr()) {
this.error(String(result.error));
}

const receipt = result.value;

this.display([
['Swap ID', receipt.id],
['Created At', receipt.createdAt.toISOString()],
]);

return this.toSuccessJson(receipt);
}
}
116 changes: 116 additions & 0 deletions packages/cli/src/commands/action/swap/supply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {
bigDecimal,
invariant,
reserveId,
type SupplySwapQuoteRequest,
userSupplyItemId,
} from '@aave/client';
import { supplySwapQuote } from '@aave/client/actions';
import { Flags } from '@oclif/core';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

import * as common from '../../../common.js';
import {
executePositionMarketSwap,
quoteRows,
} from '../../../helpers/swaps.js';

export default class ActionSwapSupply extends common.V4Command {
static override description =
'Swap supplied position into another reserve using market mode';

static override flags = {
'sell-position-id': Flags.string({
required: true,
description: 'Supply position ID to sell from',
}),
'buy-reserve-id': Flags.string({
required: true,
description: 'Reserve ID to buy into',
}),
amount: Flags.string({
required: true,
description: 'Amount to swap',
}),
'enable-collateral': Flags.boolean({
required: false,
default: false,
description: 'Enable collateral on the resulting supply position',
}),
'quote-only': Flags.boolean({
required: false,
default: false,
description: 'Only fetch and display quote',
}),
address: common.address({
required: false,
description: 'User address for quote generation',
}),
'private-key': common.privateKey({
required: false,
}),
};

protected override headers = [{ value: 'Field' }, { value: 'Value' }];

async run(): Promise<unknown> {
const { flags } = await this.parse(ActionSwapSupply);

const amount = flags.amount.trim();
invariant(amount.length > 0, 'Amount cannot be empty');

const user = common.userAddressFromFlagOrEnv(flags.address);

const request: SupplySwapQuoteRequest = {
market: {
sellPosition: userSupplyItemId(flags['sell-position-id']),
buyReserve: reserveId(flags['buy-reserve-id']),
amount: bigDecimal(amount),
user,
enableCollateral: flags['enable-collateral'],
},
};

const quote = await supplySwapQuote(this.client, request);
if (quote.isErr()) {
this.error(quote.error);
}

this.display(quoteRows(quote.value.quote));

if (flags['quote-only']) {
return this.toSuccessJson(quote.value);
}

const privateKey = (flags['private-key'] ??
process.env.PRIVATE_KEY) as `0x${string}`;
invariant(
privateKey,
'Provide --private-key or PRIVATE_KEY environment variable',
);

const wallet = createWalletClient({
account: privateKeyToAccount(privateKey),
transport: http(),
});

const result = await executePositionMarketSwap(
this.client,
wallet,
quote.value,
);
if (result.isErr()) {
this.error(String(result.error));
}

const receipt = result.value;

this.display([
['Swap ID', receipt.id],
['Created At', receipt.createdAt.toISOString()],
]);

return this.toSuccessJson(receipt);
}
}
Loading