Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show auction details, improve name details printing #247

Merged
merged 2 commits into from
Apr 11, 2024
Merged
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
49 changes: 16 additions & 33 deletions src/actions/aens.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
import { isAddressValid, getDefaultPointerKey } from '@aeternity/aepp-sdk';
import { initSdkByWalletFile } from '../utils/cli.js';
import { print, printTransaction } from '../utils/print.js';
import { isAvailable, updateNameStatus, validateName } from '../utils/helpers.js';
import { getNameEntry, validateName } from '../utils/helpers.js';
import CliError from '../utils/CliError.js';

async function ensureNameStatus(name, sdk, status, operation) {
const nameEntry = await getNameEntry(name, sdk);
if (nameEntry.status !== status) {
throw new CliError(`AENS name is ${nameEntry.status} and cannot be ${operation}`);
}
}

// ## Claim `name` function
export async function preClaim(walletPath, name, options) {
const {
Expand All @@ -20,10 +27,7 @@ export async function preClaim(walletPath, name, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name' available
const nameEntry = await updateNameStatus(name, sdk);
if (!isAvailable(nameEntry)) {
throw new CliError('AENS name not available');
}
await ensureNameStatus(name, sdk, 'AVAILABLE', 'preclaimed');
// Create `pre-claim` transaction
const preClaimTx = await sdk.aensPreclaim(name, {
ttl, fee, nonce, waitMined,
Expand All @@ -49,10 +53,7 @@ export async function claim(walletPath, name, salt, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name' available
const nameEntry = await updateNameStatus(name, sdk);
if (!isAvailable(nameEntry)) {
throw new CliError('AENS name not available');
}
await ensureNameStatus(name, sdk, 'AVAILABLE', 'claimed');

// Wait for next block and create `claimName` transaction
const claimTx = await sdk.aensClaim(name, salt, {
Expand Down Expand Up @@ -82,10 +83,7 @@ export async function updateName(walletPath, name, addresses, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name` is unavailable and we can update it
const nameEntry = await updateNameStatus(name, sdk);
if (isAvailable(nameEntry)) {
throw new CliError(`AENS name is ${nameEntry.status} and cannot be updated`);
}
await ensureNameStatus(name, sdk, 'CLAIMED', 'updated');

// Create `updateName` transaction
const updateTx = await sdk.aensUpdate(
Expand Down Expand Up @@ -116,10 +114,7 @@ export async function extendName(walletPath, name, nameTtl, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name` is unavailable and we can update it
const nameEntry = await updateNameStatus(name, sdk);
if (isAvailable(nameEntry)) {
throw new CliError(`AENS name is ${nameEntry.status} and cannot be extended`);
}
await ensureNameStatus(name, sdk, 'CLAIMED', 'extended');

// Create `updateName` transaction
const updateTx = await sdk.aensUpdate(name, {}, {
Expand Down Expand Up @@ -148,10 +143,7 @@ export async function transferName(walletPath, name, address, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name` is unavailable and we can transfer it
const nameEntry = await updateNameStatus(name, sdk);
if (isAvailable(nameEntry)) {
throw new CliError('AENS name is available, nothing to transfer');
}
await ensureNameStatus(name, sdk, 'CLAIMED', 'transferred');

// Create `transferName` transaction
const transferTX = await sdk.aensTransfer(name, address, {
Expand All @@ -178,10 +170,7 @@ export async function revokeName(walletPath, name, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if `name` is unavailable and we can revoke it
const nameEntry = await updateNameStatus(name, sdk);
if (isAvailable(nameEntry)) {
throw new CliError('AENS name is available, nothing to revoke');
}
await ensureNameStatus(name, sdk, 'CLAIMED', 'revoked');

// Create `revokeName` transaction
const revokeTx = await sdk.aensRevoke(name, {
Expand All @@ -207,10 +196,7 @@ export async function nameBid(walletPath, name, nameFee, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name' available
const nameEntry = await updateNameStatus(name, sdk);
if (!isAvailable(nameEntry)) {
throw new CliError('Auction do not start or already end');
}
await ensureNameStatus(name, sdk, 'AUCTION', 'bidded');

// Wait for next block and create `claimName` transaction
const nameBidTx = await sdk.aensBid(name, nameFee, {
Expand All @@ -236,10 +222,7 @@ export async function fullClaim(walletPath, name, options) {
const sdk = await initSdkByWalletFile(walletPath, options);

// Check if that `name' available
const nameEntry = await updateNameStatus(name, sdk);
if (!isAvailable(nameEntry)) {
throw new CliError('AENS name not available');
}
await ensureNameStatus(name, sdk, 'AVAILABLE', 'claimed');

// Wait for next block and create `claimName` transaction
nonce = nonce && +nonce;
Expand Down
44 changes: 41 additions & 3 deletions src/actions/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
//
// This script initialize all `inspect` function

import BigNumber from 'bignumber.js';
import { Encoding, unpackTx as _unpackTx, Tag } from '@aeternity/aepp-sdk';
import { initSdk } from '../utils/cli.js';
import {
print,
printBlock,
printBlockTransactions,
printName, printOracle, printQueries,
printOracle, printQueries,
printTransaction,
printUnderscored,
} from '../utils/print.js';
import {
checkPref, getBlock, updateNameStatus, validateName,
checkPref, getBlock, getNameEntry, timeAgo, validateName,
} from '../utils/helpers.js';
import CliError from '../utils/CliError.js';

Expand Down Expand Up @@ -68,10 +69,47 @@ async function getBlockByHeight(height, { json, ...options }) {
printBlock(await sdk.api.getKeyBlockByHeight(+height), json);
}

const formatCoins = (coins) => `${new BigNumber(coins).shiftedBy(-18).toFixed()}ae`;

const formatTtl = (ttl, height) => {
const date = new Date();
const diff = Math.abs(ttl - height) < 3 ? 0 : ttl - height;
date.setMinutes(date.getMinutes() + diff * 3);
return `${ttl} (${timeAgo(date)})`;
};

async function getName(name, { json, ...options }) {
validateName(name);
const sdk = initSdk(options);
printName(await updateNameStatus(name, sdk), json);
const nameEntry = await getNameEntry(name, sdk);

if (json) {
print(nameEntry);
return;
}

const height = await sdk.getHeight({ cached: true });
printUnderscored('Status', nameEntry.status);
printUnderscored('Name hash', nameEntry.id);
switch (nameEntry.status) {
case 'CLAIMED':
printUnderscored('Owner', nameEntry.owner);
if (nameEntry.pointers?.length) {
nameEntry.pointers.forEach(({ key, id }) => printUnderscored(`Pointer ${key}`, id));
} else printUnderscored('Pointers', 'N/A');
printUnderscored('TTL', formatTtl(nameEntry.ttl, height));
break;
case 'AUCTION':
printUnderscored('Highest bidder', nameEntry.highestBidder);
printUnderscored('Highest bid', formatCoins(nameEntry.highestBid));
printUnderscored('Ends at height', formatTtl(nameEntry.endsAt, height));
printUnderscored('Started at height', formatTtl(nameEntry.startedAt, height));
break;
case 'AVAILABLE':
break;
default:
throw new Error(`Unknown name status: ${nameEntry.status}`);
}
}

async function getContract(contractId, { json, ...options }) {
Expand Down
54 changes: 40 additions & 14 deletions src/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// That script contains base helper function

import { resolve } from 'path';
import { Encoding, decode as _decode } from '@aeternity/aepp-sdk';
import { Encoding, decode as _decode, produceNameId } from '@aeternity/aepp-sdk';
import CliError from './CliError.js';

// ## Method which retrieve block info by hash
Expand Down Expand Up @@ -49,21 +49,23 @@ export function checkPref(hash, hashType) {

// ## AENS helpers methods

// Get `name` status
export async function updateNameStatus(name, sdk) {
try {
return { ...await sdk.getName(name), status: 'CLAIMED' };
} catch (e) {
if (e.response && e.response.status === 404) {
return { name, status: 'AVAILABLE' };
}
throw e;
}
// Get `name` entry
export async function getNameEntry(nameAsString, sdk) {
const handle404 = (error) => {
if (error.response?.status === 404) return undefined;
throw error;
};
const [name, auction] = await Promise.all([
sdk.api.getNameEntryByName(nameAsString).catch(handle404),
sdk.api.getAuctionEntryByName(nameAsString).catch(handle404),
]);
return {
id: produceNameId(nameAsString),
...name ?? auction,
status: (name && 'CLAIMED') || (auction && 'AUCTION') || 'AVAILABLE',
};
}

// Check if `name` is `AVAILABLE`
export function isAvailable(name) { return name.status === 'AVAILABLE'; }

// Validate `name`
export function validateName(name) {
if (typeof name !== 'string') throw new CliError('Name must be a string');
Expand All @@ -80,3 +82,27 @@ export function decode(data, requiredPrefix) {
}

export const getFullPath = (path) => resolve(process.cwd(), path);

const units = [
['year', 365 * 24 * 60 * 60 * 1000],
['month', 30.5 * 24 * 60 * 60 * 1000],
['day', 24 * 60 * 60 * 1000],
['hour', 60 * 60 * 1000],
['minute', 60 * 1000],
['second', 1000],
];

export function timeAgo(date) {
const diff = Date.now() - date.getTime();
// TODO: revisit linter settings, the below rule is not relevant because babel is not used
// eslint-disable-next-line no-restricted-syntax
for (const [name, size] of units) {
const value = Math.floor(Math.abs(diff) / size);
if (value > 0) {
const plural = value > 1 ? 's' : '';
const description = `${value} ${name}${plural}`;
return diff > 0 ? `${description} ago` : `in ${description}`;
}
}
return 'about now';
}
13 changes: 0 additions & 13 deletions src/utils/print.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,16 +347,3 @@ export function printQueries(queries = [], json) {
print('------------------------------------------------------------------------------');
});
}

// Print `name`
export function printName(name, json) {
if (json) {
print(name);
return;
}
printUnderscored('Status', name.status ?? 'N/A');
printUnderscored('Name hash', name.id ?? 'N/A');
if (name.pointers?.length) name.pointers.forEach(({ key, id }) => printUnderscored(`Pointer ${key}`, id));
else printUnderscored('Pointers', 'N/A');
printUnderscored('TTL', name.ttl ?? 0);
}
30 changes: 18 additions & 12 deletions test/inspect.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { before, describe, it } from 'mocha';
import { expect } from 'chai';
import {
AbiVersion, generateKeyPair, Tag, VmVersion,
AbiVersion, generateKeyPair, produceNameId, Tag, VmVersion,
} from '@aeternity/aepp-sdk';
import { executeProgram, getSdk } from './index.js';
import inspectProgram from '../src/commands/inspect.js';
Expand Down Expand Up @@ -240,15 +240,13 @@ Ttl _____________________________________ ${resJson.ttl}
it('Inspect Unclaimed Name', async () => {
const resJson = await executeInspect([name, '--json']);
expect(resJson).to.eql({
name,
id: produceNameId(name),
status: 'AVAILABLE',
});
const res = await executeInspect([name]);
expect(res).to.equal(`
Status __________________________________ AVAILABLE
Name hash _______________________________ N/A
Pointers ________________________________ N/A
TTL _____________________________________ 0
Name hash _______________________________ ${produceNameId(name)}
`.trim());
});

Expand All @@ -274,27 +272,35 @@ TTL _____________________________________ 0
expect(res).to.equal(`
Status __________________________________ CLAIMED
Name hash _______________________________ ${resJson.id}
Owner ___________________________________ ${sdk.address}
Pointer myKey ___________________________ ${sdk.address}
Pointer account_pubkey __________________ ${sdk.address}
Pointer oracle_pubkey ___________________ ${sdk.address}
TTL _____________________________________ ${resJson.ttl}
TTL _____________________________________ ${resJson.ttl} (in 1 year)
`.trim());
}).timeout(6000);

it('Inspect Running Auction Name', async () => {
const auctionName = `a${Math.random().toString().slice(2, 9)}.chain`;
await (await sdk.aensPreclaim(auctionName)).claim();
const resJson = await executeInspect([auctionName, '--json']);
const endsAt = +resJson.startedAt + 14880;
expect(resJson).to.eql({
name: auctionName,
status: 'AVAILABLE',
endsAt: String(endsAt),
highestBid: '19641800000000000000',
highestBidder: sdk.address,
id: resJson.id,
startedAt: resJson.startedAt,
status: 'AUCTION',
});
const res = await executeInspect([auctionName]);
expect(res).to.equal(`
Status __________________________________ AVAILABLE
Name hash _______________________________ N/A
Pointers ________________________________ N/A
TTL _____________________________________ 0
Status __________________________________ AUCTION
Name hash _______________________________ ${resJson.id}
Highest bidder __________________________ ${sdk.address}
Highest bid _____________________________ 19.6418ae
Ends at height __________________________ ${endsAt} (in 1 month)
Started at height _______________________ ${resJson.startedAt} (about now)
`.trim());
}).timeout(4000);
});
17 changes: 4 additions & 13 deletions test/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('AENS Module', () => {
preClaim.blockHeight.should.be.gt(0);
preClaim.salt.should.be.a('number');
preClaim.commitmentId.should.contain('cm');
nameResult.name.should.be.equal(name2);
nameResult.id.should.satisfy((id) => id.startsWith('nm_'));
nameResult.status.should.equal('AVAILABLE');
}).timeout(4000);

Expand Down Expand Up @@ -231,23 +231,14 @@ describe('AENS Module', () => {
});

it('Fail on open again', async () => {
const preClaim = await executeName([
'pre-claim',
WALLET_NAME,
'--password',
'test',
name,
'--json',
]);
await executeName([
'claim',
'pre-claim',
WALLET_NAME,
'--password',
'test',
name,
preClaim.salt,
'--json',
]).should.be.rejectedWith('error: Transaction not found');
}).timeout(15000);
]).should.be.rejectedWith('AENS name is AUCTION and cannot be preclaimed');
});
});
});