Skip to content

Commit

Permalink
feat(ar.io cli): init leave-network, delegate-stake PE-5854
Browse files Browse the repository at this point in the history
  • Loading branch information
fedellen committed Nov 26, 2024
1 parent 7a6aa4b commit 40ebe06
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 20 deletions.
25 changes: 22 additions & 3 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import { program } from 'commander';
import { intentsUsingYears, isValidIntent, validIntents } from '../types/io.js';
import { mIOToken } from '../types/token.js';
import { version } from '../version.js';
import { delegateStake } from './commands/delegateStake.js';
import { joinNetwork } from './commands/joinNetwork.js';
import { transfer } from './commands/transfer.js';
import { updateGatewaySettings } from './commands/updateGatewaySettings.js';
import {
addressOptions,
arNSAuctionPricesOptions,
delegateStakeOptions,
epochOptions,
getVaultOptions,
globalOptions,
Expand All @@ -38,6 +40,8 @@ import {
paginationOptions,
tokenCostOptions,
transferOptions,
updateGatewaySettingsOptions,
walletOptions,
} from './options.js';
import {
AddressAndNameOptions,
Expand All @@ -50,6 +54,7 @@ import {
NameOptions,
PaginationAddressOptions,
PaginationOptions,
WalletOptions,
} from './types.js';
import {
addressFromOptions,
Expand All @@ -63,6 +68,7 @@ import {
requiredNameFromOptions,
requiredVaultIdFromOptions,
runCommand,
writeIOFromOptions,
} from './utils.js';

makeCommand({
Expand Down Expand Up @@ -163,7 +169,7 @@ makeCommand<PaginationAddressOptions>({
});

makeCommand<PaginationAddressOptions>({
name: 'get-gateway-delegate-allow-list',
name: 'get-allowed-delegates',
description: 'Get the allow list of a gateway delegate',
options: paginationAddressOptions,
action: async (o) => {
Expand Down Expand Up @@ -548,14 +554,27 @@ makeCommand({
action: joinNetwork,
});

makeCommand<WalletOptions>({
name: 'leave-network',
description: 'Leave a gateway from the AR.IO network',
options: walletOptions,
// TODO: Add a confirmation prompt? Could get settings, display, then confirm prompt
action: (options) => writeIOFromOptions(options).leaveNetwork(),
});

makeCommand({
name: 'update-gateway-settings',
description: 'Update AR.IO gateway settings',
options: joinNetworkOptions,
options: updateGatewaySettingsOptions,
action: updateGatewaySettings,
});

// delegate-stake
makeCommand({
name: 'delegate-stake',
description: 'Delegate stake to a gateway',
options: delegateStakeOptions,
action: delegateStake,
});

// increase-operator-stake

Expand Down
84 changes: 84 additions & 0 deletions src/cli/commands/delegateStake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import prompts from 'prompts';

import { mIOToken } from '../../types/token.js';
import { TransferOptions } from '../types.js';
import {
formatIOWithCommas,
jwkToAddress,
requiredJwkFromOptions,
requiredTargetAndQuantityFromOptions,
writeIOFromOptions,
writeOptionsFromOptions,
} from '../utils.js';

export async function delegateStake(options: TransferOptions) {
const jwk = requiredJwkFromOptions(options);
const address = jwkToAddress(jwk);
const io = writeIOFromOptions(options);

const { target, ioQuantity } = requiredTargetAndQuantityFromOptions(options);
const mIOQuantity = ioQuantity.toMIO();

if (!options.skipConfirmation) {
const balance = await io.getBalance({ address });

if (balance < mIOQuantity.valueOf()) {
throw new Error(
`Insufficient IO balance for delegating stake. Balance available: ${new mIOToken(balance).toIO()} IO`,
);
}

const targetGateway = await io.getGateway({ address: target });
if (targetGateway === undefined) {
throw new Error(`Gateway not found for address: ${target}`);
}
if (targetGateway.settings.allowDelegatedStaking === false) {
throw new Error(`Gateway does not allow delegated staking: ${target}`);
}

// TODO: could get allow list and assert doesn't exist or user is on it

// TODO: could read from contract to get current delegated stake if there is none, get contract minimum delegated stake. Then see if the new stake value will satisfy minimum delegated stake for both the target gateway settings min delegate stake and contract min delegated amounts

const { confirm } = await prompts({
type: 'confirm',
name: 'confirm',
message: `Target Gateway:\n${JSON.stringify(targetGateway, null, 2)}\n\nAre you sure you want to delegate ${formatIOWithCommas(ioQuantity)} IO to ${target}?`,
});

if (!confirm) {
return { message: 'Delegate stake aborted by user' };
}
}

const result = await io.delegateStake(
{
target,
stakeQty: ioQuantity.toMIO(),
},
writeOptionsFromOptions(options),
);

const output = {
senderAddress: address,
transferResult: result,
message: `Successfully delegated ${formatIOWithCommas(ioQuantity)} IO to ${target}`,
};

return output;
}
15 changes: 4 additions & 11 deletions src/cli/commands/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
*/
import prompts from 'prompts';

import { IOToken, mIOToken } from '../../types/token.js';
import { mIOToken } from '../../types/token.js';
import { TransferOptions } from '../types.js';
import {
formatIOWithCommas,
jwkToAddress,
requiredJwkFromOptions,
requiredTargetAndQuantityFromOptions,
writeIOFromOptions,
writeOptionsFromOptions,
} from '../utils.js';
Expand All @@ -30,20 +31,12 @@ export async function transfer(options: TransferOptions) {
const address = jwkToAddress(jwk);
const io = writeIOFromOptions(options);

const { target, quantity } = options;
if (target === undefined) {
throw new Error('Target address is required');
}
if (quantity === undefined) {
throw new Error('Quantity is required');
}

const ioQuantity = new IOToken(+quantity);
const { target, ioQuantity } = requiredTargetAndQuantityFromOptions(options);

if (!options.skipConfirmation) {
const balance = await io.getBalance({ address });

if (balance < quantity) {
if (balance < +ioQuantity.toMIO()) {
throw new Error(
`Insufficient IO balance for transfer. Balance available: ${new mIOToken(balance).toIO()} IO`,
);
Expand Down
2 changes: 2 additions & 0 deletions src/cli/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ export const transferOptions = [
optionMap.target,
];

export const delegateStakeOptions = transferOptions;

export const updateGatewaySettingsOptions = [
...writeActionOptions,
optionMap.disableAutoStake,
Expand Down
4 changes: 3 additions & 1 deletion src/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ export type WriteActionOptions = WalletOptions & {
};

export type TransferOptions = WriteActionOptions & {
quantity: number | undefined;
quantity: string | undefined;
target: string | undefined;
};

export type DelegateStakeOptions = TransferOptions;

export type JoinNetworkOptions = WriteActionOptions &
CLIOptionsFromAoParams<
Omit<AoJoinNetworkParams, 'allowDelegatedStaking' | 'autoStake'>
Expand Down
16 changes: 16 additions & 0 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
JsonSerializable,
NameOptions,
PaginationOptions,
TransferOptions,
UpdateGatewaySettingsOptions,
VaultIdOptions,
WalletOptions,
Expand Down Expand Up @@ -327,3 +328,18 @@ export function gatewaySettingsFromOptions({
properties,
};
}

export function requiredTargetAndQuantityFromOptions(
options: TransferOptions,
): { target: string; ioQuantity: IOToken } {
if (options.target === undefined) {
throw new Error('No target provided. Use --target');
}
if (options.quantity === undefined) {
throw new Error('No quantity provided. Use --quantity');
}
return {
target: options.target,
ioQuantity: new IOToken(+options.quantity),
};
}
11 changes: 6 additions & 5 deletions src/types/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,13 @@ export type AoAddressParams = {
address: WalletAddress;
};

export type AoBalanceParams = {
address: WalletAddress;
};
export type AoBalanceParams = AoAddressParams;

export type AoPaginatedAddressParams = PaginationParams & {
address: WalletAddress;
export type AoPaginatedAddressParams = PaginationParams & AoAddressParams;

export type AoDelegateStakeParams = {
target: WalletAddress;
stakeQty: number | mIOToken;
};

export type AoGetArNSRecordsParams = PaginationParams<AoArNSNameDataWithName>;
Expand Down

0 comments on commit 40ebe06

Please sign in to comment.