Skip to content

Commit

Permalink
withdrawals update
Browse files Browse the repository at this point in the history
  • Loading branch information
owl352 committed Oct 14, 2024
1 parent a90515e commit 091d0db
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/api/src/controllers/IdentitiesController.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const IdentitiesDAO = require('../dao/IdentitiesDAO')
const TransactionsDAO = require('../dao/TransactionsDAO')

class IdentitiesController {
constructor (knex, dapi) {
this.identitiesDAO = new IdentitiesDAO(knex)
this.transactionsDAO = new TransactionsDAO(knex)
this.dapi = dapi
}

Expand Down Expand Up @@ -82,6 +84,15 @@ class IdentitiesController {

response.send(transfers)
}

getWithdrawalsByIdentity = async (request, response) => {
const { identifier } = request.params
const { page = 1, limit = 10, order = 'asc' } = request.query

const withdrawals = await this.transactionsDAO.getWithdrawalsByIdentifier(identifier, Number(page ?? 1), Number(limit ?? 10), order ?? 'asc')

response.send(withdrawals)
}
}

module.exports = IdentitiesController
41 changes: 41 additions & 0 deletions packages/api/src/dao/TransactionsDAO.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const Transaction = require('../models/Transaction')
const PaginatedResultSet = require('../models/PaginatedResultSet')
const SeriesData = require('../models/SeriesData')
const { IDENTITY_CREDIT_WITHDRAWAL } = require('../enums/StateTransitionEnum')
const Withdrawal = require('../models/Witdrawal')

module.exports = class TransactionsDAO {
constructor (knex) {
Expand Down Expand Up @@ -130,4 +132,43 @@ module.exports = class TransactionsDAO {

return Number(row.total_collected_fees ?? 0)
}

getWithdrawalsByIdentifier = async (identifier, page, limit, order) => {
const fromRank = ((page - 1) * limit) + 1
const toRank = fromRank + limit - 1

const subquery = this.knex('state_transitions')
.select(
'state_transitions.id as state_transition_id',
'state_transitions.hash as tx_hash',
'state_transitions.block_hash as block_hash',
'state_transitions.owner as owner',
'blocks.timestamp as timestamp',
'transfers.amount as amount'
)
.select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`))
.select()
.where('state_transitions.owner', '=', identifier)
.andWhere('state_transitions.type', '=', IDENTITY_CREDIT_WITHDRAWAL)
.leftJoin('blocks', 'state_transitions.block_hash', 'blocks.hash')
.leftJoin('transfers', 'transfers.state_transition_hash', 'state_transitions.hash')
.as('subquery')

const rows = await this.knex(subquery)
.select(
'rank',
'state_transition_id as id',
'tx_hash', 'block_hash',
'timestamp', 'amount', 'owner',
this.knex(subquery).count().as('total_count')
)
.orderBy('state_transition_id', order)
.whereBetween('rank', [fromRank, toRank])

const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0

const resultSet = rows.map((row) => Withdrawal.fromRow(row))

return new PaginatedResultSet(resultSet, page, limit, totalCount)
}
}
21 changes: 21 additions & 0 deletions packages/api/src/models/Witdrawal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = class Withdrawal {
id
amount
owner
txHash
timestamp
blockHash

constructor (amount, owner, txHash, timestamp, blockHash) {
this.amount = amount ?? null
this.owner = owner ? owner.trim() : null
this.txHash = txHash ?? null
this.timestamp = timestamp ?? null
this.blockHash = blockHash ?? null
}

// eslint-disable-next-line camelcase
static fromRow ({ amount, owner, tx_hash, timestamp, block_hash }) {
return new Withdrawal(parseInt(amount), owner, tx_hash, timestamp, block_hash)
}
}
14 changes: 14 additions & 0 deletions packages/api/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ module.exports = ({
querystring: { $ref: 'paginationOptions#' }
}
},
{
path: '/identity/:identifier/withdrawals',
method: 'GET',
handler: identitiesController.getWithdrawalsByIdentity,
schema: {
querystring: { $ref: 'paginationOptions#' },
params: {
type: 'object',
properties: {
validator: { $ref: 'hash#' }
}
}
}
},
{
path: '/identity/:identifier/dataContracts',
method: 'GET',
Expand Down
178 changes: 178 additions & 0 deletions packages/api/test/integration/identities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1195,4 +1195,182 @@ describe('Identities routes', () => {
assert.deepEqual(body.resultSet, expectedTransfers)
})
})

describe('getWithdrawalsByIdentity()', async () => {
it('should return default set of withdrawals by identity', async () => {
block = await fixtures.block(knex, { height: 1 })
identity = await fixtures.identity(knex, { block_hash: block.hash })
transfers = []

for (let i = 1; i <= 30; i++) {
block = await fixtures.block(knex, { height: i + 1 })
transaction = await fixtures.transaction(knex, {
block_hash: block.hash,
owner: identity.identifier,
type: StateTransitionEnum.IDENTITY_CREDIT_WITHDRAWAL
})
transfer = await fixtures.transfer(knex, {
amount: 1000,
recipient: identity.identifier,
sender: null,
state_transition_hash: transaction.hash
})
transfers.push({ transfer, transaction, block })
}

const { body } = await client.get(`/identity/${identity.identifier}/withdrawals`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.resultSet.length, 10)
assert.equal(body.pagination.total, transactions.length)
assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, 10)

const expectedTransfers = transfers
.sort((a, b) => a.block.height - b.block.height)
.slice(0, 10)
.map((_transfer, i) => ({
amount: parseInt(_transfer.transfer.amount),
txHash: _transfer.transaction.hash,
owner: _transfer.transaction.owner,
timestamp: _transfer.block.timestamp.toISOString(),
blockHash: _transfer.block.hash
}))

assert.deepEqual(body.resultSet, expectedTransfers)
})

it('should return default set of withdrawals by identity desc', async () => {
block = await fixtures.block(knex, { height: 1 })
identity = await fixtures.identity(knex, { block_hash: block.hash })
transfers = []

for (let i = 1; i <= 30; i++) {
block = await fixtures.block(knex, { height: i + 1 })
transaction = await fixtures.transaction(knex, {
block_hash: block.hash,
owner: identity.identifier,
type: StateTransitionEnum.IDENTITY_CREDIT_WITHDRAWAL
})
transfer = await fixtures.transfer(knex, {
amount: 1000,
recipient: identity.identifier,
sender: null,
state_transition_hash: transaction.hash
})
transfers.push({ transfer, transaction, block })
}

const { body } = await client.get(`/identity/${identity.identifier}/withdrawals?order=desc`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.resultSet.length, 10)
assert.equal(body.pagination.total, transactions.length)
assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, 10)

const expectedTransfers = transfers
.sort((a, b) => b.block.height - a.block.height)
.slice(0, 10)
.map((_transfer, i) => ({
amount: parseInt(_transfer.transfer.amount),
txHash: _transfer.transaction.hash,
owner: _transfer.transaction.owner,
timestamp: _transfer.block.timestamp.toISOString(),
blockHash: _transfer.block.hash
}))

assert.deepEqual(body.resultSet, expectedTransfers)
})

it('should allow to walk through pages', async () => {
block = await fixtures.block(knex, { height: 1 })
identity = await fixtures.identity(knex, { block_hash: block.hash })
transfers = []

for (let i = 1; i <= 30; i++) {
block = await fixtures.block(knex, { height: i + 1 })
transaction = await fixtures.transaction(knex, {
block_hash: block.hash,
owner: identity.identifier,
type: StateTransitionEnum.IDENTITY_CREDIT_WITHDRAWAL
})
transfer = await fixtures.transfer(knex, {
amount: 1000,
recipient: identity.identifier,
sender: null,
state_transition_hash: transaction.hash
})
transfers.push({ transfer, transaction, block })
}

const { body } = await client.get(`/identity/${identity.identifier}/withdrawals?page=2&limit=5`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.resultSet.length, 5)
assert.equal(body.pagination.total, transactions.length)
assert.equal(body.pagination.page, 2)
assert.equal(body.pagination.limit, 5)

const expectedTransfers = transfers
.sort((a, b) => a.block.height - b.block.height)
.slice(5, 10)
.map((_transfer, i) => ({
amount: parseInt(_transfer.transfer.amount),
txHash: _transfer.transaction.hash,
owner: _transfer.transaction.owner,
timestamp: _transfer.block.timestamp.toISOString(),
blockHash: _transfer.block.hash
}))

assert.deepEqual(body.resultSet, expectedTransfers)
})

it('should allow to walk through pages desc', async () => {
block = await fixtures.block(knex, { height: 1 })
identity = await fixtures.identity(knex, { block_hash: block.hash })
transfers = []

for (let i = 1; i <= 30; i++) {
block = await fixtures.block(knex, { height: i + 1 })
transaction = await fixtures.transaction(knex, {
block_hash: block.hash,
owner: identity.identifier,
type: StateTransitionEnum.IDENTITY_CREDIT_WITHDRAWAL
})
transfer = await fixtures.transfer(knex, {
amount: 1000,
recipient: identity.identifier,
sender: null,
state_transition_hash: transaction.hash
})
transfers.push({ transfer, transaction, block })
}

const { body } = await client.get(`/identity/${identity.identifier}/withdrawals?page=2&limit=5&order=desc`)
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.resultSet.length, 5)
assert.equal(body.pagination.total, transactions.length)
assert.equal(body.pagination.page, 2)
assert.equal(body.pagination.limit, 5)

const expectedTransfers = transfers
.sort((a, b) => b.block.height - a.block.height)
.slice(5, 10)
.map((_transfer, i) => ({
amount: parseInt(_transfer.transfer.amount),
txHash: _transfer.transaction.hash,
owner: _transfer.transaction.owner,
timestamp: _transfer.block.timestamp.toISOString(),
blockHash: _transfer.block.hash
}))

assert.deepEqual(body.resultSet, expectedTransfers)
})
})
})

0 comments on commit 091d0db

Please sign in to comment.