# FBX client improvements
- `fireblocks-client` gem is used by `wallets-fbx-*` services, cursor-based pagination for `/transactions` endpoint is used only by `wallets-fbx-control` to iterate through Txs
## Fireblocks API
Responds either with:
* a single resource
* OR a collection of resources.
ONE endpoint responds with a collection of a resources, using a cursor based pagination.
## Current
Every endpoint/method responds with `FireblocksResult` object.
hey there
## Suggestions
### Simple: Hash + Array of Hash
- `fireblocks-client` gem: methods should return as a response Hash with symbol keys or Array of Hashes
- `fireblocks-client` gem: methods should return as a response `FireblocksResult` only for endpoints that support pagination (e.g. only `/transactions`)
```ruby=
client = FireblockClient.new(...)
client.transaction(id) # => Hash
client.transactions(...) # => Array<Hash> (Collection<Hash>)
```
### Iteration
```ruby=
client.transaction(id) # => FireblocksResult
client.transactions(...) # => FireblocksResults
```
### Models
Idea: Domain models for clear highlevel data structure, avoid primitives.
```ruby=
client = FireblocksClient.new(...)
tx = client.transaction(id) # => Models::Transaction
client.transactions(...) # => Collection<Transaction>
tx # Models::Transaction < Struct
tx.id # => String
tx.amount # => BigDecimal
tx.raw
```
pros:
- straightforwarding FBX complexity (e.g. `amount` to understand how much value was transfered)
- rubyfi: (types convetion - Timestamps, BigDecimal)
cons:
- additional level of complexity on top of FBX API
#### Singular resource
how:
* convert keys: camelCase -> snake_case
* allow subset of keys, only documented attributes are presented
* convert values:
* amounts: in String or etc -> BigDecimal
* timestamps: Integer or etc -> Time OR DateTime, TBD
* nested objects: as another Struct
* e.g. `Transaction::Source`
* array (values): as another Struct
* e.g. `Transaction::DestinationsValue`
* present enums(values) as constants
* e.g.: `Transaction::VAULT_ACCOUNT` (eq `"VAULT_ACCOUNT"`)
```ruby=
class FireblocksClient::Model < Mimi::Struct
def initialize(original)
@raw = original
super
end
def raw
@raw
end
end
class Transaction < FireblocksClient::Model
attribute :created_at, from: :createdAt
attribute :created_at, using: -> (o) { Time.at(o[:createdAt]) }
end
QRY wallets.fireblocks.transactions/show id:..., raw: true
=> FireblocksClient::Transaction #
```
Messaging:
```
QRY wallets.fireblocks.transactions/list scope.before:...
```
#### Collections
Domain Array of Domain Models
```ruby=
txs = client.transactions(...) # Fireblocks::Collection < Array
txs.prev_page # => Fireblocks::Cursor or nil
txs.next_page # => Fireblocks::Cursor or nil
txs = client.transactions(..., page: txs.next_page)
```
#### Querying collections
Introducing a Anti-Corruption layer to ensure the data we send to FBX is ledgit and blow up early if not. With that the integration/usage
is not only more intuative/documented in code, but even allows to apply test on this.
```ruby=
q = Transactions::Query << { before: Time.now }
q.before = Time.now
q.order_by = Transactions::Query::CREATED_AT # [ Transactions::Query::CREATED_AT, Transactions::Query::LAST_UPDATED]
##
## Example of a collection:
##
client.transactions(query: { after: Time.now, order_by: }, page: txs.next_page, raw: true)
def transactions(query)
query = Transactions::Query << query if query.is_a?(Hash)
query.validate?
transport.get("/transaction", query.to_query_hash)
transport.post("/transaction", body: query.to_json)
end
## wallets-fireblocks-control
def fetch_new_transactions
last_tx = ...
txs = client.transactions(query: { after: last_tx.created_at })
while txs.next_page
txs = client.transactions(query: { after: last_tx.created_at }, page: txs.next_page)
end
end
```
Messagging results in:
```
## QRY wallets.fireblocks.transactions/list scope:.. pagination:.. order:..
## QRY wallets.fireblocks.transactions/list
{
"items": [ ... ],
"pagination": {
"prev": null,
"next": "AASASA"
},
"scope": { ... },
"order": { ... }
}
```
#### Creating resource
```ruby=
client = FireblocksClient.new(...)
m = Transaction::CreateModel << { asset_id: 1, net_amount: 0.23, source: { ... } }
client.create_transaction({ asset_id: 1, net_amount: 0.23, source: { ... } })
def create_transaction(m)
m = Transactions::CreateModel << m if m.is_a?(Hash)
m.validate?
transport.post("/transaction", body: m.to_json)
end
class Transaction::CreateModel
def to_json
{
netAmount: ammount.to_s
}
end
end
```
## Additional info
Fireblocks API https://docs.fireblocks.com/api/#transactions
sBank ADR proposal https://confluence.solarisbank.de/display/ARC/Provider+Integration
### Appendix
#### Fireblocks Transaction
```json=
{
"id": "ffdc2452-f617-428d-ab1d-0e53774fb564",
"createdAt": 1643053075812,
"lastUpdated": 1643053132111,
"assetId": "ETH",
"source": {
"id": "100581377",
"type": "VAULT_ACCOUNT",
"name": "Vault Account #100581377",
"subType": ""
},
"destination": {
"type": "ONE_TIME_ADDRESS",
"name": "N/A",
"subType": ""
},
"amount": "0.02312599",
"fee": "0.003234",
"networkFee": "0.003234",
"netAmount": "0.02312599",
"sourceAddress": "0x05b5e1D9B278cEe00A469104716F741BE21261A6",
"destinationAddress": "0x6031e496fC310bF687969C884bFf9051c2039927",
"destinationAddressDescription": "",
"destinationTag": "",
"status": "COMPLETED",
"txHash": "0xdca10dcc97a52cadd150e402c33450183ff85e56cfd17c55537af07ce668e706",
"subStatus": "CONFIRMED",
"signedBy": [
"58a06c7a-8df0-4c69-c4cf-00115e862c2a"
],
"createdBy": "58a06c7a-8df0-4c69-c4cf-00115e862c2a",
"rejectedBy": "",
"amountUSD": "54.73518229",
"addressType": "",
"note": "",
"exchangeTxId": "",
"requestedAmount": "0.02312599",
"feeCurrency": "ETH",
"operation": "TRANSFER",
"numOfConfirmations": 1,
"amountInfo": {
"amount": "0.02312599",
"requestedAmount": "0.02312599",
"netAmount": "0.02312599",
"amountUSD": "54.73518229"
},
"feeInfo": {
"networkFee": "0.003234"
},
"destinations": [
],
"externalTxId": "49b40dce01fd15e1c431b383bc4e95a3outx",
"blockInfo": {
"blockHeight": "14070252",
"blockHash": "0x89146b03579ec3aa771f961805943d91165d53e10ccc5b886c833760106aceb2"
},
"signedMessages": [
]
}
```