Skip to content
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

The command-line interface for versum.

- [versum-cli](#versum-cli)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started to add a list of documents that hopefully will help people (and us in the future) to keep track of everything.

- [Usage](#usage)
- [Contributing](#contributing)
- [How to use](#how-to-use)
- [Export](#export)

## Usage

To install the latest version of Versum CLI, run this command:
Expand All @@ -23,3 +29,28 @@ versum
- [Code of Conduct](https://github.com/versumstudios/cli/blob/main/CODE_OF_CONDUCT.md)
- [Contributing Guidelines](https://github.com/versumstudios/cli/blob/main/CONTRIBUTING.md)
- [Apache-2.0 License](https://github.com/versumstudios/cli/blob/main/LICENSE)

## How to use

```bash
versum -h
```

### Export

The versum CLI allows you to export several chunks of data into `.csv` files.

### Export `token-collectors`

```bash
versum export token-collectors
versum export token-collectors --contract KT1LjmAdYQCLBjwv4S2oFkEzyHVkomAf5MrW --token 0
```

### Export `wallet-collectors`

```bash
versum export wallet-collectors
versum export wallet-collectors --wallet tz1eht4WAjkqU7kaupJd8qCDmec9HuKfGf68
versum export wallet-collectors --wallet tz1eht4WAjkqU7kaupJd8qCDmec9HuKfGf68 --platform versum
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"inquirer": "8.0.0",
"node-fetch": "2.6.2",
"objects-to-csv": "^1.3.6",
"ora-classic": "^5.4.2",
"ora": "5.4.1",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverting to ora as per this sindresorhus/ora#188

"update-notifier": "5.1.0"
}
}
97 changes: 0 additions & 97 deletions src/actions/export-collectors.ts

This file was deleted.

86 changes: 51 additions & 35 deletions src/actions/export-token-collectors.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,70 @@
import inquirer, { Answers } from 'inquirer';
import inquirer, { Answers, Separator } from 'inquirer';
import fetch from 'node-fetch';
import ora from 'ora-classic';
import ora from 'ora';

import { CONTRACT_VERSUM, getContractFromPlatform, PLATFORMS, TEZTOK_API, MESSAGES } from '@constants';
import { CONTRACT_VERSUM, ERRORS, getContractFromPlatform, MESSAGES, PLATFORMS, TEZTOK_API } from '@constants';
import { CollectorsType } from '@custom-types/collectors';
import { validateContractAddress } from '@taquito/utils';
import { SaveToFile } from '@utils/csv';
import { error } from '@utils/logger';
import { error, info } from '@utils/logger';

type CollectorsType = {
holder_address: string;
amount: number;
}[];
const handleAction = (address: string, token: string) => {
const query = `
query GetTokenCollectors($address: String!, $token: String!) {
tokens_by_pk(fa2_address: $address, token_id: $token) {
holdings(where: { amount: { _gt: "0" } }) {
holder_address
amount
}
}
}`;

return fetch(TEZTOK_API, { method: 'POST', body: JSON.stringify({ query, variables: { address, token } }) })
.then((e) => e.json())
.then((e) => e.data.tokens_by_pk.holdings)
.then(async (collectors: CollectorsType) => {
// parse data
const data = collectors.map(({ holder_address, amount }) => ({
address: holder_address,
amount,
}));
const filename = await SaveToFile(`token-collectors-${address}-${token}.csv`, data);
return `File saved ${filename}`;
});
};

export const action = (options: Record<string, string>) => {
// if options are present, bypass the user promp
if (options.contract && options.token) {
handleAction(options.contract, options.token)
.then((message) => info(message))
.catch(() => {
error(ERRORS.ERROR_EXPORT_COLLECTOR);
});
return;
}

export const action = () => {
// otherwise show user promp
const questions = [
{
type: 'list',
name: 'platform',
message: MESSAGES.SELECT_PLATFORM,
choices: [PLATFORMS.VERSUM, PLATFORMS.HICETNUNC, PLATFORMS.FXHASH, PLATFORMS.OTHER],
choices: [PLATFORMS.VERSUM, PLATFORMS.HICETNUNC, PLATFORMS.FXHASH, new Separator(), PLATFORMS.OTHER],
default() {
return PLATFORMS.VERSUM;
},
},
{
type: 'input',
name: 'contract',
message: 'Enter contract address',
message: MESSAGES.ENTER_CONTRACT_ADDRESS,
when: (answers: Answers) => answers.platform === PLATFORMS.OTHER,
validate: async (input: string) => {
if ((await validateContractAddress(input)) === 3) {
return true;
}
error('\nInvalid contract address');
return false;
return ERRORS.ERROR_INVALID_ADDRESS;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is how the inquirer API is meant to be used around validation. by returning a string it gives the users the hability to try again.

},
default() {
return CONTRACT_VERSUM;
Expand All @@ -42,41 +73,26 @@ export const action = () => {
{
type: 'input',
name: 'token',
message: 'Enter token id',
message: MESSAGES.ENTER_TOKEN_ID,
default() {
return '0';
},
},
];
const query = `
query GetTokenCollectors($address: String!, $token: String!) {
tokens_by_pk(fa2_address: $address, token_id: $token) {
holdings(where: { amount: { _gt: "0" } }) {
holder_address
amount
}
}
}`;

inquirer.prompt(questions).then(({ platform, contract, token }: Record<string, string>) => {
// select contract address from platform
// select contract address from platform alias
const address: string = getContractFromPlatform(platform as PLATFORMS, token as string) || contract;
const spinner = ora(MESSAGES.FETCHING_DATA).start();

fetch(TEZTOK_API, { method: 'POST', body: JSON.stringify({ query, variables: { address, token } }) })
.then((e) => e.json())
.then((e) => e.data.tokens_by_pk.holdings)
.then(async (collectors: CollectorsType) => {
// parse data
const data = collectors.map(({ holder_address, amount }) => ({
address: holder_address,
amount,
}));
handleAction(address, token)
.then((message: string) => {
spinner.succeed();
await SaveToFile(`collectors-${platform}-${address}-${token}.csv`, data);
info(message);
})
.catch(() => {
spinner.fail(MESSAGES.ERROR_COLLECTOR_EXPORT);
spinner.fail();
error(ERRORS.ERROR_EXPORT_COLLECTOR);
});
});
};
109 changes: 109 additions & 0 deletions src/actions/export-wallet-collectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import inquirer, { Separator } from 'inquirer';
import fetch from 'node-fetch';
import ora from 'ora';

import { ERRORS, getKeyFromPlatform, MESSAGES, PLATFORMS, TEZTOK_API } from '@constants';
import { CollectorsType } from '@custom-types/collectors';
import { validateAddress } from '@taquito/utils';
import { SaveToFile } from '@utils/csv';
import { error, info } from '@utils/logger';

const handleAction = (address: string, platform?: PLATFORMS) => {
const query = `
query GetUserCollectors($address: String!) {
holdings(where: {token: {artist_address: {_eq: $address}}, amount: {_gt: "0"}, holder_address: {_nregex: "^KT*", _neq: "tz1burnburnburnburnburnburnburjAYjjX"}}) {
amount
holder_address
token {
platform
fa2_address
token_id
}
}
}`;

return fetch(TEZTOK_API, { method: 'POST', body: JSON.stringify({ query, variables: { address } }) })
.then((e) => e.json())
.then((e) => e.data.holdings)
.then(async (collectors: CollectorsType) => {
// parse data
const data = collectors
.map(({ holder_address, amount, token }) => ({
address: holder_address,
amount,
platform: token ? token.platform : '',
token: token ? token.token_id : '',
}))
.filter((e) => {
if (platform === PLATFORMS.ALL) {
return true;
}
return e.platform === getKeyFromPlatform(platform as PLATFORMS);
});
const filename = await SaveToFile(`wallet-collectors-${address}-${platform}.csv`, data);
return `File saved ${filename}`;
});
};

export const action = (options: Record<string, string>) => {
// if options are present, bypass the user promp
if (options.wallet) {
handleAction(options.wallet, (options.platform as PLATFORMS) || PLATFORMS.ALL)
.then((message) => info(message))
.catch(() => {
error(ERRORS.ERROR_EXPORT_COLLECTOR);
});
return;
}

const questions = [
{
type: 'input',
name: 'address',
message: MESSAGES.ENTER_USER_ADDRESS,
validate: async (input: string) => {
if ((await validateAddress(input)) === 3) {
return true;
}

return ERRORS.ERROR_INVALID_ADDRESS;
},
// TODO: https://github.com/versumstudios/cli/issues/20
// default() {
// return 'tz1gi68wGST7UtzkNpnnc354mpqCcVNQVcSw';
// },
},
{
type: 'list',
name: 'platform',
message: MESSAGES.SELECT_PLATFORM,
choices: [
PLATFORMS.VERSUM,
PLATFORMS.HICETNUNC,
PLATFORMS.FXHASH,
PLATFORMS.OBJKTCOM,
PLATFORMS.EIGHTBIDOU,
PLATFORMS.TYPED,
new Separator(),
PLATFORMS.ALL,
],
default() {
return PLATFORMS.ALL;
},
},
];

inquirer.prompt(questions).then(({ address, platform }: Record<string, string>) => {
const spinner = ora(MESSAGES.FETCHING_DATA).start();

handleAction(address, platform as PLATFORMS)
.then((message: string) => {
spinner.succeed();
info(message);
})
.catch(() => {
spinner.fail();
error(ERRORS.ERROR_EXPORT_COLLECTOR);
});
});
};
Loading