---
# System prepended metadata

title: FBX client improvements

---

# 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": [

  ]
}
```

