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

feat: new cli commands accounts:notes and accounts:transactions #1086

Merged
merged 15 commits into from Jun 15, 2022
2 changes: 2 additions & 0 deletions ironfish-cli/README.md
Expand Up @@ -27,6 +27,8 @@ Interact with the node in a new terminal window:
- Request a small amount of $IRON for testing payments
- `yarn start accounts:pay`
- Send $IRON to another account
- `yarn start accounts:transactions [account]`
- Display transactions from and to your account

### Start a node and start mining
Run these commands in two different terminals:
Expand Down
59 changes: 59 additions & 0 deletions ironfish-cli/src/commands/accounts/notes.test.ts
@@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { GetAccountNotesResponse } from '@ironfish/sdk'
import { expect as expectCli, test } from '@oclif/test'

describe('accounts:notes', () => {
const responseContent: GetAccountNotesResponse = {
account: 'default',
notes: [
{
spender: true,
amount: 1,
memo: 'foo',
noteTxHash: '1fa5f38c446e52f8842d8c861507744fc3f354992610e1661e033ef316e2d3d1',
},
],
}

beforeAll(() => {
jest.doMock('@ironfish/sdk', () => {
const originalModule = jest.requireActual('@ironfish/sdk')
const client = {
connect: jest.fn(),
getAccountNotes: jest.fn().mockImplementation(() => ({
content: responseContent,
})),
}
const module: typeof jest = {
...originalModule,
IronfishSdk: {
init: jest.fn().mockImplementation(() => ({
connectRpc: jest.fn().mockResolvedValue(client),
client,
})),
},
}
return module
})
})

afterAll(() => {
jest.dontMock('@ironfish/sdk')
})

describe('fetching the notes for an account', () => {
test
.stdout()
.command(['accounts:notes', 'default'])
.exit(0)
.it('logs the notes for the given account', (ctx) => {
expectCli(ctx.stdout).include(responseContent.account)
expectCli(ctx.stdout).include(responseContent.notes[0].spender ? `✔` : `x`)
expectCli(ctx.stdout).include(responseContent.notes[0].amount)
expectCli(ctx.stdout).include(responseContent.notes[0].memo)
expectCli(ctx.stdout).include(responseContent.notes[0].noteTxHash)
})
})
})
56 changes: 56 additions & 0 deletions ironfish-cli/src/commands/accounts/notes.ts
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { oreToIron } from '@ironfish/sdk'
import { CliUx } from '@oclif/core'
import { IronfishCommand } from '../../command'
import { RemoteFlags } from '../../flags'

export class NotesCommand extends IronfishCommand {
static description = `Display the account notes`

static flags = {
...RemoteFlags,
}

static args = [
{
name: 'account',
parse: (input: string): Promise<string> => Promise.resolve(input.trim()),
required: false,
description: 'name of the account to get notes for',
},
]

async start(): Promise<void> {
const { args } = await this.parse(NotesCommand)
const account = args.account as string | undefined

const client = await this.sdk.connectRpc()

const response = await client.getAccountNotes({ account })

const { account: accountResponse, notes } = response.content

this.log(`\n ${accountResponse} - Account notes\n`)

CliUx.ux.table(notes, {
isSpender: {
header: 'Spender',
get: (row) => (row.spender ? `✔` : `x`),
},
amount: {
header: 'Amount ($IRON)',
get: (row) => oreToIron(row.amount),
},
memo: {
header: 'Memo',
},
noteTxHash: {
header: 'From Transaction',
},
})

this.log(`\n`)
}
}
121 changes: 121 additions & 0 deletions ironfish-cli/src/commands/accounts/transactions.test.ts
@@ -0,0 +1,121 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { GetAccountTransactionResponse, GetAccountTransactionsResponse } from '@ironfish/sdk'
import { expect as expectCli, test } from '@oclif/test'

describe('accounts:transactions', () => {
const account = 'default'
const transactionHash = '1fa5f38c446e52f8842d8c861507744fc3f354992610e1661e033ef316e2d3d1'

const responseContentTransactions: GetAccountTransactionsResponse = {
account,
transactions: [
{
creator: true,
status: 'completed',
hash: '1fa5f38c446e52f8842d8c861507744fc3f354992610e1661e033ef316e2d3d1',
isMinersFee: false,
fee: 0.1,
notes: 1,
spends: 1,
},
],
}

const responseContentTransaction: GetAccountTransactionResponse = {
account,
transactionHash,
transactionInfo: {
status: 'completed',
isMinersFee: false,
fee: 0.1,
notes: 1,
spends: 1,
},
transactionNotes: [
{
spender: true,
amount: 1,
memo: 'foo',
},
],
}

beforeAll(() => {
jest.doMock('@ironfish/sdk', () => {
const originalModule = jest.requireActual('@ironfish/sdk')
const client = {
connect: jest.fn(),
getAccountTransactions: jest.fn().mockImplementation(() => ({
content: responseContentTransactions,
})),
getAccountTransaction: jest.fn().mockImplementation(() => ({
content: responseContentTransaction,
})),
}
const module: typeof jest = {
...originalModule,
IronfishSdk: {
init: jest.fn().mockImplementation(() => ({
connectRpc: jest.fn().mockResolvedValue(client),
client,
})),
},
}
return module
})
})

afterAll(() => {
jest.dontMock('@ironfish/sdk')
})

describe('fetching transactions for an account', () => {
test
.stdout()
.command(['accounts:transactions', `-a ${account}`])
.exit(0)
.it('logs the transactions for the given account', (ctx) => {
expectCli(ctx.stdout).include(responseContentTransactions.account)
expectCli(ctx.stdout).include(
responseContentTransactions.transactions[0].creator ? `✔` : `x`,
)
expectCli(ctx.stdout).include(responseContentTransactions.transactions[0].status)
expectCli(ctx.stdout).include(responseContentTransactions.transactions[0].hash)
expectCli(ctx.stdout).include(
responseContentTransactions.transactions[0].isMinersFee ? `✔` : `x`,
)
expectCli(ctx.stdout).include(responseContentTransactions.transactions[0].fee)
expectCli(ctx.stdout).include(responseContentTransactions.transactions[0].notes)
expectCli(ctx.stdout).include(responseContentTransactions.transactions[0].spends)
})
})

describe('fetching details of specific transaction', () => {
test
.stdout()
.command(['accounts:transactions', `-t ${transactionHash}`])
.exit(0)
.it('logs the transaction details and notes for the given hash', (ctx) => {
expectCli(ctx.stdout).include(responseContentTransaction.account)
expectCli(ctx.stdout).include(responseContentTransaction.transactionHash)

// transaction details
expectCli(ctx.stdout).include(responseContentTransaction.transactionInfo?.status)
expectCli(ctx.stdout).include(
responseContentTransaction.transactionInfo?.isMinersFee ? `✔` : `x`,
)
expectCli(ctx.stdout).include(responseContentTransaction.transactionInfo?.fee)
expectCli(ctx.stdout).include(responseContentTransaction.transactionInfo?.notes)
expectCli(ctx.stdout).include(responseContentTransaction.transactionInfo?.spends)

// transaction notes
expectCli(ctx.stdout).include(
responseContentTransaction.transactionNotes[0].spender ? `✔` : `x`,
)
expectCli(ctx.stdout).include(responseContentTransaction.transactionNotes[0].amount)
expectCli(ctx.stdout).include(responseContentTransaction.transactionNotes[0].memo)
})
})
})
117 changes: 117 additions & 0 deletions ironfish-cli/src/commands/accounts/transactions.ts
@@ -0,0 +1,117 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { oreToIron } from '@ironfish/sdk'
import { CliUx, Flags } from '@oclif/core'
import { IronfishCommand } from '../../command'
import { RemoteFlags } from '../../flags'

export class TransactionsCommand extends IronfishCommand {
static description = `Display the account transactions`

static flags = {
...RemoteFlags,
account: Flags.string({
char: 'a',
description: 'account transactions',
}),
hash: Flags.string({
char: 't',
description: 'details of transaction hash',
}),
}

async start(): Promise<void> {
const { flags } = await this.parse(TransactionsCommand)
const account = flags.account?.trim()
const hash = flags.hash?.trim()

if (hash) {
await this.getTransaction(account, hash)
} else {
await this.getTransactions(account)
}
}

async getTransaction(account: string | undefined, hash: string): Promise<void> {
const client = await this.sdk.connectRpc()

const response = await client.getAccountTransaction({ account, hash })

const {
account: accountResponse,
transactionHash,
transactionInfo,
transactionNotes,
} = response.content

this.log(`Account: ${accountResponse}`)

if (transactionInfo !== null) {
this.log(
`Transaction: ${transactionHash}\nStatus: ${transactionInfo.status}\nMiner Fee: ${
transactionInfo.isMinersFee ? `✔` : `x`
}\nFee ($ORE): ${transactionInfo.fee}\nSpends: ${transactionInfo.spends}\n`,
)
}

if (transactionNotes.length > 0) {
this.log(`---Notes---\n`)

CliUx.ux.table(transactionNotes, {
isSpender: {
header: 'Spender',
get: (row) => (row.spender ? `✔` : `x`),
},
amount: {
header: 'Amount ($IRON)',
get: (row) => oreToIron(row.amount),
},
memo: {
header: 'Memo',
},
})
}

this.log(`\n`)
}

async getTransactions(account: string | undefined): Promise<void> {
const client = await this.sdk.connectRpc()

const response = await client.getAccountTransactions({ account })

const { account: accountResponse, transactions } = response.content

this.log(`\n ${String(accountResponse)} - Account transactions\n`)

CliUx.ux.table(transactions, {
status: {
header: 'Status',
},
creator: {
header: 'Creator',
get: (row) => (row.creator ? `✔` : `x`),
},
hash: {
header: 'Hash',
},
isMinersFee: {
header: 'Miner Fee',
get: (row) => (row.isMinersFee ? `✔` : `x`),
},
fee: {
header: 'Fee ($ORE)',
get: (row) => row.fee,
},
notes: {
header: 'Notes',
},
spends: {
header: 'Spends',
},
})

this.log(`\n`)
}
}