Updated on 2026-08-14
This commit is contained in:
parent
7c9112fa70
commit
4390b59231
25 changed files with 1810 additions and 570 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "aafa8b51b5a5a32d0ec2b0720cec6c1e",
|
||||
"identityHash": "942246bf975439606ad20e05b930827c",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "express_provider",
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
},
|
||||
{
|
||||
"tableName": "express_exchange",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `status` TEXT NOT NULL, `to_is_actual` INTEGER NOT NULL DEFAULT 0, `payin_hash` TEXT, `payout_hash` TEXT, `external_tx_id` TEXT, `external_tx_url` TEXT, `rate_type` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, `from_network` TEXT NOT NULL, `from_token_id` TEXT, `from_raw_amount` TEXT NOT NULL, `from_decimals` INTEGER NOT NULL, `to_network` TEXT NOT NULL, `to_token_id` TEXT, `to_raw_amount` TEXT NOT NULL, `to_decimals` INTEGER NOT NULL, `refund_network` TEXT, `refund_token_id` TEXT, `refund_raw_amount` TEXT, `refund_decimals` INTEGER, `refund_hash` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `from_address` TEXT NOT NULL, `payin_address` TEXT NOT NULL, `payin_extra_id` TEXT, `payout_address` TEXT NOT NULL, `refund_address` TEXT, `refund_extra_id` TEXT, `rate_type` TEXT NOT NULL, `status` TEXT NOT NULL, `external_tx_id` TEXT, `external_tx_status` TEXT, `external_tx_url` TEXT, `payin_hash` TEXT, `payout_hash` TEXT, `refund_network` TEXT, `refund_contract_address` TEXT, `created_at` TEXT NOT NULL, `pay_till` TEXT, `average_duration` INTEGER, `from_contract_address` TEXT NOT NULL, `from_network` TEXT NOT NULL, `from_decimals` INTEGER NOT NULL, `from_amount` TEXT NOT NULL, `from_actual_amount` TEXT, `to_contract_address` TEXT NOT NULL, `to_network` TEXT NOT NULL, `to_decimals` INTEGER NOT NULL, `to_amount` TEXT NOT NULL, `to_actual_amount` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "txId",
|
||||
|
|
@ -64,6 +64,48 @@
|
|||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromAddress",
|
||||
"columnName": "from_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "payinAddress",
|
||||
"columnName": "payin_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "payinExtraId",
|
||||
"columnName": "payin_extra_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "payoutAddress",
|
||||
"columnName": "payout_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refundAddress",
|
||||
"columnName": "refund_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refundExtraId",
|
||||
"columnName": "refund_extra_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rateType",
|
||||
"columnName": "rate_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
|
|
@ -71,11 +113,22 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toIsActual",
|
||||
"columnName": "to_is_actual",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxStatus",
|
||||
"columnName": "external_tx_status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "payinHash",
|
||||
|
|
@ -90,33 +143,39 @@
|
|||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"fieldPath": "refundNetwork",
|
||||
"columnName": "refund_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"fieldPath": "refundContractAddress",
|
||||
"columnName": "refund_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rateType",
|
||||
"columnName": "rate_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "created_at",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updated_at",
|
||||
"fieldPath": "payTill",
|
||||
"columnName": "pay_till",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageDuration",
|
||||
"columnName": "average_duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.contractAddress",
|
||||
"columnName": "from_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
|
|
@ -125,18 +184,6 @@
|
|||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.tokenId",
|
||||
"columnName": "from_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.rawAmount",
|
||||
"columnName": "from_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.decimals",
|
||||
"columnName": "from_decimals",
|
||||
|
|
@ -144,20 +191,26 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.network",
|
||||
"columnName": "to_network",
|
||||
"fieldPath": "from.amount",
|
||||
"columnName": "from_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.tokenId",
|
||||
"columnName": "to_token_id",
|
||||
"fieldPath": "from.actualAmount",
|
||||
"columnName": "from_actual_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.rawAmount",
|
||||
"columnName": "to_raw_amount",
|
||||
"fieldPath": "to.contractAddress",
|
||||
"columnName": "to_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.network",
|
||||
"columnName": "to_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
|
|
@ -168,32 +221,14 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.network",
|
||||
"columnName": "refund_network",
|
||||
"fieldPath": "to.amount",
|
||||
"columnName": "to_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.tokenId",
|
||||
"columnName": "refund_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.rawAmount",
|
||||
"columnName": "refund_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.decimals",
|
||||
"columnName": "refund_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.hash",
|
||||
"columnName": "refund_hash",
|
||||
"fieldPath": "to.actualAmount",
|
||||
"columnName": "to_actual_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
|
|
@ -206,15 +241,15 @@
|
|||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_from_network_updated_at",
|
||||
"name": "index_express_exchange_owner_address_from_network_created_at",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"from_network",
|
||||
"updated_at"
|
||||
"created_at"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_from_network_updated_at` ON `${TABLE_NAME}` (`owner_address`, `from_network`, `updated_at`)"
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_from_network_created_at` ON `${TABLE_NAME}` (`owner_address`, `from_network`, `created_at`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_payin_hash",
|
||||
|
|
@ -235,16 +270,6 @@
|
|||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_payout_hash` ON `${TABLE_NAME}` (`owner_address`, `payout_hash`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_refund_hash",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"refund_hash"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_refund_hash` ON `${TABLE_NAME}` (`owner_address`, `refund_hash`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
|
|
@ -263,7 +288,7 @@
|
|||
},
|
||||
{
|
||||
"tableName": "express_onramp",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `status` TEXT NOT NULL, `from_currency_code` TEXT NOT NULL, `from_amount` TEXT NOT NULL, `to_network` TEXT NOT NULL, `to_token_id` TEXT, `to_expected_raw_amount` TEXT NOT NULL, `to_actual_raw_amount` TEXT, `to_decimals` INTEGER NOT NULL, `payout_hash` TEXT, `external_tx_id` TEXT, `external_tx_url` TEXT, `rate_type` TEXT NOT NULL, `fail_reason` TEXT, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, `refund_currency_code` TEXT, `refund_amount` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `from_address` TEXT NOT NULL, `payin_address` TEXT NOT NULL, `payin_extra_id` TEXT, `payout_address` TEXT NOT NULL, `refund_address` TEXT, `refund_extra_id` TEXT, `rate_type` TEXT NOT NULL, `status` TEXT NOT NULL, `external_tx_id` TEXT, `external_tx_status` TEXT, `external_tx_url` TEXT, `payin_hash` TEXT, `payout_hash` TEXT, `refund_network` TEXT, `refund_contract_address` TEXT, `created_at` TEXT NOT NULL, `pay_till` TEXT, `average_duration` INTEGER, `from_contract_address` TEXT NOT NULL, `from_network` TEXT NOT NULL, `from_decimals` INTEGER NOT NULL, `from_amount` TEXT NOT NULL, `from_actual_amount` TEXT, `to_contract_address` TEXT NOT NULL, `to_network` TEXT NOT NULL, `to_decimals` INTEGER NOT NULL, `to_amount` TEXT NOT NULL, `to_actual_amount` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "txId",
|
||||
|
|
@ -284,68 +309,38 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"fieldPath": "fromAddress",
|
||||
"columnName": "from_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromCurrencyCode",
|
||||
"columnName": "from_currency_code",
|
||||
"fieldPath": "payinAddress",
|
||||
"columnName": "payin_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromAmount",
|
||||
"columnName": "from_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toNetwork",
|
||||
"columnName": "to_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toTokenId",
|
||||
"columnName": "to_token_id",
|
||||
"fieldPath": "payinExtraId",
|
||||
"columnName": "payin_extra_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toExpectedRawAmount",
|
||||
"columnName": "to_expected_raw_amount",
|
||||
"fieldPath": "payoutAddress",
|
||||
"columnName": "payout_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toActualRawAmount",
|
||||
"columnName": "to_actual_raw_amount",
|
||||
"fieldPath": "refundAddress",
|
||||
"columnName": "refund_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toDecimals",
|
||||
"columnName": "to_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "payoutHash",
|
||||
"columnName": "payout_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"fieldPath": "refundExtraId",
|
||||
"columnName": "refund_extra_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -356,32 +351,128 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "failReason",
|
||||
"columnName": "fail_reason",
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxStatus",
|
||||
"columnName": "external_tx_status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "payinHash",
|
||||
"columnName": "payin_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "payoutHash",
|
||||
"columnName": "payout_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refundNetwork",
|
||||
"columnName": "refund_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refundContractAddress",
|
||||
"columnName": "refund_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "created_at",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updated_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.currencyCode",
|
||||
"columnName": "refund_currency_code",
|
||||
"fieldPath": "payTill",
|
||||
"columnName": "pay_till",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.amount",
|
||||
"columnName": "refund_amount",
|
||||
"fieldPath": "averageDuration",
|
||||
"columnName": "average_duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.contractAddress",
|
||||
"columnName": "from_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.network",
|
||||
"columnName": "from_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.decimals",
|
||||
"columnName": "from_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.amount",
|
||||
"columnName": "from_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.actualAmount",
|
||||
"columnName": "from_actual_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.contractAddress",
|
||||
"columnName": "to_contract_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.network",
|
||||
"columnName": "to_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.decimals",
|
||||
"columnName": "to_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.amount",
|
||||
"columnName": "to_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.actualAmount",
|
||||
"columnName": "to_actual_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
|
|
@ -394,15 +485,15 @@
|
|||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_express_onramp_owner_address_to_network_updated_at",
|
||||
"name": "index_express_onramp_owner_address_to_network_created_at",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"to_network",
|
||||
"updated_at"
|
||||
"created_at"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_to_network_updated_at` ON `${TABLE_NAME}` (`owner_address`, `to_network`, `updated_at`)"
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_to_network_created_at` ON `${TABLE_NAME}` (`owner_address`, `to_network`, `created_at`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_onramp_owner_address_payout_hash",
|
||||
|
|
@ -428,12 +519,57 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "express_sync_state",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `address` TEXT NOT NULL, `is_initial_completed` INTEGER NOT NULL, `after_cursor` TEXT, `delta_cursor` TEXT, PRIMARY KEY(`type`, `address`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "address",
|
||||
"columnName": "address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isInitialCompleted",
|
||||
"columnName": "is_initial_completed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "afterCursor",
|
||||
"columnName": "after_cursor",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deltaCursor",
|
||||
"columnName": "delta_cursor",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"type",
|
||||
"address"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'aafa8b51b5a5a32d0ec2b0720cec6c1e')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '942246bf975439606ad20e05b930827c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -87,10 +87,17 @@ interface TangemExpressApi {
|
|||
@Body body: ExchangeSentRequestBody,
|
||||
): ApiResponse<ExchangeSentResponseBody>
|
||||
|
||||
@GET("exchange/history")
|
||||
@GET("history/exchange")
|
||||
suspend fun getHistory(
|
||||
@Query("wallet_address") walletAddress: String,
|
||||
@Query("cursor") cursor: String?,
|
||||
@Query("fromAddress") fromAddress: String,
|
||||
@Query("afterCursor") cursor: String?,
|
||||
@Query("limit") limit: Int = 100,
|
||||
): ApiResponse<ExchangeHistoryResponse>
|
||||
|
||||
@GET("history/delta/exchange")
|
||||
suspend fun getHistoryDelta(
|
||||
@Query("fromAddress") fromAddress: String,
|
||||
@Query("beforeCursor") cursor: String?,
|
||||
@Query("limit") limit: Int = 100,
|
||||
): ApiResponse<ExchangeHistoryDeltaResponse>
|
||||
}
|
||||
|
|
@ -5,81 +5,16 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeHistoryResponse(
|
||||
@Json(name = "data")
|
||||
val data: List<ExchangeRecord>,
|
||||
@Json(name = "next_cursor")
|
||||
val nextCursor: String,
|
||||
@Json(name = "has_more")
|
||||
val hasMore: Boolean,
|
||||
) {
|
||||
@Json(name = "items")
|
||||
val items: List<ExchangeItemResponse>,
|
||||
@Json(name = "pagination")
|
||||
val pagination: ExpressPagination,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeRecord(
|
||||
@Json(name = "tx_id")
|
||||
val txId: String,
|
||||
@Json(name = "status")
|
||||
val status: String,
|
||||
@Json(name = "provider")
|
||||
val provider: Provider,
|
||||
@Json(name = "from")
|
||||
val from: AssetRef,
|
||||
@Json(name = "to")
|
||||
val to: AssetRef,
|
||||
@Json(name = "payin_hash")
|
||||
val payinHash: String?,
|
||||
@Json(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
@Json(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
@Json(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
@Json(name = "refund")
|
||||
val refund: RefundInfo?,
|
||||
@Json(name = "rate_type")
|
||||
val rateType: String,
|
||||
@Json(name = "created_at")
|
||||
val createdAt: Long,
|
||||
@Json(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Provider(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "icon_url")
|
||||
val iconUrl: String,
|
||||
@Json(name = "provider_url")
|
||||
val providerUrl: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssetRef(
|
||||
@Json(name = "network")
|
||||
val network: String,
|
||||
@Json(name = "token_id")
|
||||
val tokenId: String?,
|
||||
@Json(name = "raw_amount")
|
||||
val rawAmount: String,
|
||||
@Json(name = "decimals")
|
||||
val decimals: Int,
|
||||
@Json(name = "is_actual")
|
||||
val isActual: Boolean?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefundInfo(
|
||||
@Json(name = "network")
|
||||
val network: String,
|
||||
@Json(name = "token_id")
|
||||
val tokenId: String?,
|
||||
@Json(name = "raw_amount")
|
||||
val rawAmount: String,
|
||||
@Json(name = "decimals")
|
||||
val decimals: Int,
|
||||
@Json(name = "hash")
|
||||
val hash: String?,
|
||||
)
|
||||
}
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeHistoryDeltaResponse(
|
||||
@Json(name = "items")
|
||||
val items: List<ExchangeItemResponse>,
|
||||
@Json(name = "pagination")
|
||||
val pagination: ExpressPaginationDelta,
|
||||
)
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeItemResponse(
|
||||
// region transaction info
|
||||
@Json(name = "txId")
|
||||
val txId: String,
|
||||
|
||||
@Json(name = "providerId")
|
||||
val providerId: String,
|
||||
|
||||
/** Address from which the source assets were sent */
|
||||
@Json(name = "fromAddress")
|
||||
val fromAddress: String,
|
||||
|
||||
/** Address to which the source assets were transferred for the exchange */
|
||||
@Json(name = "payinAddress")
|
||||
val payinAddress: String,
|
||||
|
||||
/** Extra ID used for the pay-in transaction */
|
||||
@Json(name = "payinExtraId")
|
||||
val payinExtraId: String?,
|
||||
|
||||
/** Address that received the target assets */
|
||||
@Json(name = "payoutAddress")
|
||||
val payoutAddress: String,
|
||||
|
||||
/** Refund destination address */
|
||||
@Json(name = "refundAddress")
|
||||
val refundAddress: String?,
|
||||
|
||||
/** Extra ID used for refunds */
|
||||
@Json(name = "refundExtraId")
|
||||
val refundExtraId: String?,
|
||||
|
||||
/** Exchange rate type (e.g. float, fixed) */
|
||||
@Json(name = "rateType")
|
||||
val rateType: String,
|
||||
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
|
||||
/** External transaction ID (CEX only) */
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String?,
|
||||
|
||||
/** Transaction status reported by the provider */
|
||||
@Json(name = "externalTxStatus")
|
||||
val externalTxStatus: String?,
|
||||
|
||||
/** URL to view the transaction details (CEX only) */
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/** Blockchain hash of the pay-in transaction */
|
||||
@Json(name = "payinHash")
|
||||
val payinHash: String?,
|
||||
|
||||
/** Blockchain hash of the payout transaction */
|
||||
@Json(name = "payoutHash")
|
||||
val payoutHash: String?,
|
||||
|
||||
/** Network used for the refund transaction */
|
||||
@Json(name = "refundNetwork")
|
||||
val refundNetwork: String?,
|
||||
|
||||
/** Refunded token contract address */
|
||||
@Json(name = "refundContractAddress")
|
||||
val refundContractAddress: String?,
|
||||
|
||||
/** Transaction creation timestamp in ISO-8601 format */
|
||||
@Json(name = "createdAt")
|
||||
val createdAt: String,
|
||||
|
||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||
@Json(name = "payTill")
|
||||
val payTill: String?,
|
||||
|
||||
/** Average provider exchange duration in seconds */
|
||||
@Json(name = "averageDuration")
|
||||
val averageDuration: Long?,
|
||||
// endregion
|
||||
|
||||
// region fromAsset info
|
||||
@Json(name = "fromContractAddress")
|
||||
val fromContractAddress: String,
|
||||
@Json(name = "fromNetwork")
|
||||
val fromNetwork: String,
|
||||
@Json(name = "fromDecimals")
|
||||
val fromDecimals: Int,
|
||||
@Json(name = "fromAmount")
|
||||
val fromAmount: String,
|
||||
// endregion
|
||||
|
||||
// region toAsset info
|
||||
@Json(name = "toContractAddress")
|
||||
val toContractAddress: String,
|
||||
@Json(name = "toNetwork")
|
||||
val toNetwork: String,
|
||||
@Json(name = "toDecimals")
|
||||
val toDecimals: Int,
|
||||
@Json(name = "toAmount")
|
||||
val toAmount: String,
|
||||
@Json(name = "toActualAmount")
|
||||
val toActualAmount: String?,
|
||||
// endregion
|
||||
) {
|
||||
|
||||
enum class Status {
|
||||
|
||||
@Json(name = "unknown")
|
||||
UNKNOWN,
|
||||
|
||||
@Json(name = "exchange-tx-sent")
|
||||
EXCHANGE_TX_SENT,
|
||||
|
||||
@Json(name = "waiting")
|
||||
WAITING,
|
||||
|
||||
@Json(name = "waiting-tx-hash")
|
||||
WAITING_TX_HASH,
|
||||
|
||||
@Json(name = "expired")
|
||||
EXPIRED,
|
||||
|
||||
@Json(name = "confirming")
|
||||
CONFIRMING,
|
||||
|
||||
@Json(name = "exchanging")
|
||||
EXCHANGING,
|
||||
|
||||
@Json(name = "sending")
|
||||
SENDING,
|
||||
|
||||
@Json(name = "finished")
|
||||
FINISHED,
|
||||
|
||||
@Json(name = "failed")
|
||||
FAILED,
|
||||
|
||||
@Json(name = "tx-failed")
|
||||
TX_FAILED,
|
||||
|
||||
@Json(name = "refunded")
|
||||
REFUNDED,
|
||||
|
||||
@Json(name = "verifying")
|
||||
VERIFYING,
|
||||
|
||||
@Json(name = "paused")
|
||||
PAUSED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpressPagination(
|
||||
@Json(name = "endCursor")
|
||||
val endCursor: String?,
|
||||
@Json(name = "startDeltaCursor")
|
||||
val startDeltaCursor: String?,
|
||||
@Json(name = "hasMore")
|
||||
val hasMore: Boolean,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpressPaginationDelta(
|
||||
@Json(name = "startCursor")
|
||||
val startCursor: String?,
|
||||
@Json(name = "hasMore")
|
||||
val hasMore: Boolean,
|
||||
)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.datasource.api.onramp
|
|||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryDeltaResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||
|
|
@ -88,10 +89,17 @@ interface OnrampApi {
|
|||
@Query("txId") txId: String,
|
||||
): ApiResponse<OnrampStatusResponse>
|
||||
|
||||
@GET("onramp/history")
|
||||
@GET("history/onramp")
|
||||
suspend fun getHistory(
|
||||
@Query("wallet_address") walletAddress: String,
|
||||
@Query("cursor") cursor: String?,
|
||||
@Query("payoutAddress") payoutAddress: String,
|
||||
@Query("afterCursor") afterCursor: String?,
|
||||
@Query("limit") limit: Int = 100,
|
||||
): ApiResponse<OnrampHistoryResponse>
|
||||
|
||||
@GET("history/delta/onramp")
|
||||
suspend fun getHistoryDelta(
|
||||
@Query("payoutAddress") payoutAddress: String,
|
||||
@Query("beforeCursor") cursor: String?,
|
||||
@Query("limit") limit: Int = 100,
|
||||
): ApiResponse<OnrampHistoryDeltaResponse>
|
||||
}
|
||||
|
|
@ -2,86 +2,21 @@ package com.tangem.datasource.api.onramp.models.response
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.express.models.response.ExpressPagination
|
||||
import com.tangem.datasource.api.express.models.response.ExpressPaginationDelta
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampHistoryResponse(
|
||||
@Json(name = "data")
|
||||
val data: List<OnrampRecord>,
|
||||
@Json(name = "next_cursor")
|
||||
val nextCursor: String,
|
||||
@Json(name = "has_more")
|
||||
val hasMore: Boolean,
|
||||
) {
|
||||
@Json(name = "items")
|
||||
val items: List<OnrampItemResponse>,
|
||||
@Json(name = "pagination")
|
||||
val pagination: ExpressPagination,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampRecord(
|
||||
@Json(name = "tx_id")
|
||||
val txId: String,
|
||||
@Json(name = "status")
|
||||
val status: String,
|
||||
@Json(name = "provider")
|
||||
val provider: Provider,
|
||||
@Json(name = "from")
|
||||
val from: FiatRef,
|
||||
@Json(name = "to")
|
||||
val to: OnrampAssetRef,
|
||||
@Json(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
@Json(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
@Json(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
@Json(name = "refund")
|
||||
val refund: OnrampRefundInfo?,
|
||||
@Json(name = "rate_type")
|
||||
val rateType: String,
|
||||
@Json(name = "fail_reason")
|
||||
val failReason: String?,
|
||||
@Json(name = "created_at")
|
||||
val createdAt: Long,
|
||||
@Json(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Provider(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "icon_url")
|
||||
val iconUrl: String,
|
||||
@Json(name = "provider_url")
|
||||
val providerUrl: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FiatRef(
|
||||
@Json(name = "currency_code")
|
||||
val currencyCode: String,
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampAssetRef(
|
||||
@Json(name = "network")
|
||||
val network: String,
|
||||
@Json(name = "token_id")
|
||||
val tokenId: String?,
|
||||
@Json(name = "expected_raw_amount")
|
||||
val expectedRawAmount: String,
|
||||
@Json(name = "actual_raw_amount")
|
||||
val actualRawAmount: String?,
|
||||
@Json(name = "decimals")
|
||||
val decimals: Int,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampRefundInfo(
|
||||
@Json(name = "currency_code")
|
||||
val currencyCode: String,
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
)
|
||||
}
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampHistoryDeltaResponse(
|
||||
@Json(name = "items")
|
||||
val items: List<OnrampItemResponse>,
|
||||
@Json(name = "pagination")
|
||||
val pagination: ExpressPaginationDelta,
|
||||
)
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package com.tangem.datasource.api.onramp.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampItemResponse(
|
||||
// region transaction info
|
||||
@Json(name = "txId")
|
||||
val txId: String,
|
||||
|
||||
@Json(name = "providerId")
|
||||
val providerId: String,
|
||||
|
||||
/** Address from which the source assets were taken for the exchange */
|
||||
@Json(name = "fromAddress")
|
||||
val fromAddress: String,
|
||||
|
||||
/** Address to which the assets were transferred for the exchange */
|
||||
@Json(name = "payinAddress")
|
||||
val payinAddress: String,
|
||||
|
||||
/** Extra ID used for the pay-in transaction */
|
||||
@Json(name = "payinExtraId")
|
||||
val payinExtraId: String?,
|
||||
|
||||
/** Address that received the target assets */
|
||||
@Json(name = "payoutAddress")
|
||||
val payoutAddress: String,
|
||||
|
||||
/** Refund destination address */
|
||||
@Json(name = "refundAddress")
|
||||
val refundAddress: String?,
|
||||
|
||||
/** Extra ID used for refunds */
|
||||
@Json(name = "refundExtraId")
|
||||
val refundExtraId: String?,
|
||||
|
||||
/** Exchange rate type used in the transaction (float, fixed) */
|
||||
@Json(name = "rateType")
|
||||
val rateType: String,
|
||||
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
|
||||
/** External transaction ID (CEX only) */
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String?,
|
||||
|
||||
/** Transaction status reported by the provider */
|
||||
@Json(name = "externalTxStatus")
|
||||
val externalTxStatus: String?,
|
||||
|
||||
/** URL to view the transaction details (CEX only) */
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/** Blockchain hash of the pay-in transaction */
|
||||
@Json(name = "payinHash")
|
||||
val payinHash: String?,
|
||||
|
||||
/** Blockchain hash of the payout transaction */
|
||||
@Json(name = "payoutHash")
|
||||
val payoutHash: String?,
|
||||
|
||||
/** Network used for the refund transaction (when status is refunded) */
|
||||
@Json(name = "refundNetwork")
|
||||
val refundNetwork: String?,
|
||||
|
||||
/** Refunded token contract address */
|
||||
@Json(name = "refundContractAddress")
|
||||
val refundContractAddress: String?,
|
||||
|
||||
/** Transaction creation timestamp in ISO-8601 format */
|
||||
@Json(name = "createdAt")
|
||||
val createdAt: String,
|
||||
|
||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||
@Json(name = "payTill")
|
||||
val payTill: String?,
|
||||
|
||||
/** Average provider exchange duration in seconds */
|
||||
@Json(name = "averageDuration")
|
||||
val averageDuration: Long?,
|
||||
// endregion
|
||||
|
||||
// region fromAsset info
|
||||
@Json(name = "fromContractAddress")
|
||||
val fromContractAddress: String,
|
||||
@Json(name = "fromNetwork")
|
||||
val fromNetwork: String,
|
||||
@Json(name = "fromDecimals")
|
||||
val fromDecimals: Int,
|
||||
@Json(name = "fromAmount")
|
||||
val fromAmount: String,
|
||||
// endregion
|
||||
|
||||
// region toAsset info
|
||||
@Json(name = "toContractAddress")
|
||||
val toContractAddress: String,
|
||||
@Json(name = "toNetwork")
|
||||
val toNetwork: String,
|
||||
@Json(name = "toDecimals")
|
||||
val toDecimals: Int,
|
||||
@Json(name = "toAmount")
|
||||
val toAmount: String,
|
||||
@Json(name = "toActualAmount")
|
||||
val toActualAmount: String?,
|
||||
// endregion
|
||||
) {
|
||||
|
||||
enum class Status {
|
||||
|
||||
@Json(name = "unknown")
|
||||
UNKNOWN,
|
||||
|
||||
@Json(name = "exchange-tx-sent")
|
||||
EXCHANGE_TX_SENT,
|
||||
|
||||
@Json(name = "waiting")
|
||||
WAITING,
|
||||
|
||||
@Json(name = "waiting-tx-hash")
|
||||
WAITING_TX_HASH,
|
||||
|
||||
@Json(name = "expired")
|
||||
EXPIRED,
|
||||
|
||||
@Json(name = "confirming")
|
||||
CONFIRMING,
|
||||
|
||||
@Json(name = "exchanging")
|
||||
EXCHANGING,
|
||||
|
||||
@Json(name = "sending")
|
||||
SENDING,
|
||||
|
||||
@Json(name = "finished")
|
||||
FINISHED,
|
||||
|
||||
@Json(name = "failed")
|
||||
FAILED,
|
||||
|
||||
@Json(name = "tx-failed")
|
||||
TX_FAILED,
|
||||
|
||||
@Json(name = "refunded")
|
||||
REFUNDED,
|
||||
|
||||
@Json(name = "verifying")
|
||||
VERIFYING,
|
||||
|
||||
@Json(name = "paused")
|
||||
PAUSED,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,15 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import androidx.room.Room
|
||||
import com.tangem.datasource.local.txhistory.db.TxHistoryDatabase
|
||||
import com.tangem.datasource.local.txhistory.store.CommonSyncState
|
||||
import com.tangem.datasource.local.txhistory.store.CommonSyncStateKey
|
||||
import com.tangem.datasource.local.txhistory.store.DefaultTxHistoryStore
|
||||
import com.tangem.datasource.local.txhistory.store.TxHistoryStore
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer.Companion.jsonBuilder
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressSyncStateDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -35,38 +27,15 @@ internal interface TxHistoryModule {
|
|||
context = context,
|
||||
klass = TxHistoryDatabase::class.java,
|
||||
name = TX_HISTORY_DATABASE_NAME,
|
||||
).build()
|
||||
)
|
||||
.fallbackToDestructiveMigration(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryStore(@ApplicationContext context: Context, appScope: AppCoroutineScope): TxHistoryStore {
|
||||
val commonSerializer = KotlinxDataStoreSerializer(
|
||||
defaultValue = emptyMap(),
|
||||
serializer = MapSerializer(
|
||||
CommonSyncStateKey.serializer(),
|
||||
CommonSyncState.serializer(),
|
||||
),
|
||||
json = jsonBuilder {
|
||||
allowStructuredMapKeys = true
|
||||
},
|
||||
)
|
||||
fun provideExpressHistoryDao(database: TxHistoryDatabase): ExpressHistoryDao = database.expressHistoryDao()
|
||||
|
||||
val expressExchangeStore = DataStoreFactory.create(
|
||||
serializer = commonSerializer,
|
||||
produceFile = { context.dataStoreFile(fileName = "TxHistoryExpressExchangeStore") },
|
||||
scope = appScope,
|
||||
)
|
||||
val expressOnrampStore = DataStoreFactory.create(
|
||||
serializer = commonSerializer,
|
||||
produceFile = { context.dataStoreFile(fileName = "TxHistoryExpressOnrampStore") },
|
||||
scope = appScope,
|
||||
)
|
||||
|
||||
return DefaultTxHistoryStore(
|
||||
expressExchangeStore = expressExchangeStore,
|
||||
expressOnrampStore = expressOnrampStore,
|
||||
)
|
||||
}
|
||||
@Provides
|
||||
fun provideSyncStateDao(database: TxHistoryDatabase): ExpressSyncStateDao = database.syncStateDao()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.datasource.local.txhistory.db
|
|||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.tangem.datasource.local.txhistory.db.entity.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressSyncStateDao
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
|
|
@ -13,9 +15,12 @@ import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEn
|
|||
ExpressProviderEntity::class,
|
||||
ExpressExchangeEntity::class,
|
||||
ExpressOnrampEntity::class,
|
||||
ExpressSyncStateEntity::class,
|
||||
],
|
||||
)
|
||||
abstract class TxHistoryDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun expressHistoryDao(): ExpressHistoryDao
|
||||
|
||||
abstract fun syncStateDao(): ExpressSyncStateDao
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity
|
||||
package com.tangem.datasource.local.txhistory.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
|
|
@ -26,7 +26,7 @@ interface ExpressHistoryDao {
|
|||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
ORDER BY updated_at DESC
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
)
|
||||
fun observeExchanges(ownerAddress: String): Flow<List<ExpressExchangeEntity>>
|
||||
|
|
@ -36,7 +36,7 @@ interface ExpressHistoryDao {
|
|||
SELECT *
|
||||
FROM express_onramp
|
||||
WHERE owner_address = :ownerAddress
|
||||
ORDER BY updated_at DESC
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
)
|
||||
fun observeOnramps(ownerAddress: String): Flow<List<ExpressOnrampEntity>>
|
||||
|
|
@ -63,17 +63,6 @@ interface ExpressHistoryDao {
|
|||
)
|
||||
suspend fun findExchangeByPayoutHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
AND refund_hash = :hash
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun findExchangeByRefundHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.datasource.local.txhistory.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ExpressSyncStateDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsert(item: ExpressSyncStateEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_sync_state
|
||||
WHERE type = :type
|
||||
AND address = :address
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
fun observe(type: String, address: String): Flow<ExpressSyncStateEntity?>
|
||||
}
|
||||
|
|
@ -2,7 +2,11 @@ package com.tangem.datasource.local.txhistory.db.entity.express
|
|||
|
||||
import androidx.room.*
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
/**
|
||||
* Persisted representation of a single exchange transaction.
|
||||
*
|
||||
* Mirrors [com.tangem.datasource.api.express.models.response.ExchangeItemResponse].
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "express_exchange",
|
||||
foreignKeys = [
|
||||
|
|
@ -14,10 +18,9 @@ import androidx.room.*
|
|||
),
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["owner_address", "from_network", "updated_at"]),
|
||||
Index(value = ["owner_address", "from_network", "created_at"]),
|
||||
Index(value = ["owner_address", "payin_hash"]),
|
||||
Index(value = ["owner_address", "payout_hash"]),
|
||||
Index(value = ["owner_address", "refund_hash"]),
|
||||
],
|
||||
)
|
||||
data class ExpressExchangeEntity(
|
||||
|
|
@ -26,55 +29,38 @@ data class ExpressExchangeEntity(
|
|||
@ColumnInfo(name = "tx_id")
|
||||
val txId: String,
|
||||
|
||||
/**
|
||||
* Address used to query the history. For exchange it matches [fromAddress].
|
||||
*/
|
||||
@ColumnInfo(name = "owner_address")
|
||||
val ownerAddress: String,
|
||||
|
||||
@ColumnInfo(name = "provider_id")
|
||||
val providerId: String,
|
||||
|
||||
/**
|
||||
* waiting
|
||||
* confirming
|
||||
* exchanging
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* refunded
|
||||
* expired
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
/** Address from which the source assets were sent */
|
||||
@ColumnInfo(name = "from_address")
|
||||
val fromAddress: String,
|
||||
|
||||
@Embedded(prefix = "from_")
|
||||
val from: AssetEmbedded,
|
||||
/** Address to which the source assets were transferred for the exchange */
|
||||
@ColumnInfo(name = "payin_address")
|
||||
val payinAddress: String,
|
||||
|
||||
@Embedded(prefix = "to_")
|
||||
val to: AssetEmbedded,
|
||||
/** Extra ID used for the pay-in transaction */
|
||||
@ColumnInfo(name = "payin_extra_id")
|
||||
val payinExtraId: String?,
|
||||
|
||||
/**
|
||||
* true -> actual provider-confirmed amount
|
||||
* false -> estimated amount
|
||||
*/
|
||||
@ColumnInfo(name = "to_is_actual", defaultValue = "0")
|
||||
val toIsActual: Boolean,
|
||||
/** Address that received the target assets */
|
||||
@ColumnInfo(name = "payout_address")
|
||||
val payoutAddress: String,
|
||||
|
||||
/**
|
||||
* Match key for PAYIN leg
|
||||
*/
|
||||
@ColumnInfo(name = "payin_hash")
|
||||
val payinHash: String?,
|
||||
/** Refund destination address */
|
||||
@ColumnInfo(name = "refund_address")
|
||||
val refundAddress: String?,
|
||||
|
||||
/**
|
||||
* Match key for PAYOUT leg
|
||||
*/
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
/** Extra ID used for refunds */
|
||||
@ColumnInfo(name = "refund_extra_id")
|
||||
val refundExtraId: String?,
|
||||
|
||||
/**
|
||||
* fixed / float
|
||||
|
|
@ -82,49 +68,88 @@ data class ExpressExchangeEntity(
|
|||
@ColumnInfo(name = "rate_type")
|
||||
val rateType: String,
|
||||
|
||||
/**
|
||||
* unknown
|
||||
* exchange-tx-sent
|
||||
* waiting
|
||||
* waiting-tx-hash
|
||||
* expired
|
||||
* confirming
|
||||
* exchanging
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* tx-failed
|
||||
* refunded
|
||||
* verifying
|
||||
* paused
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
|
||||
/** External transaction ID (CEX only) */
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
/** Transaction status reported by the provider */
|
||||
@ColumnInfo(name = "external_tx_status")
|
||||
val externalTxStatus: String?,
|
||||
|
||||
/** URL to view the transaction details (CEX only) */
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/** Blockchain hash of the pay-in transaction */
|
||||
@ColumnInfo(name = "payin_hash")
|
||||
val payinHash: String?,
|
||||
|
||||
/** Blockchain hash of the payout transaction */
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
/** Network used for the refund transaction */
|
||||
@ColumnInfo(name = "refund_network")
|
||||
val refundNetwork: String?,
|
||||
|
||||
/** Refunded token contract address */
|
||||
@ColumnInfo(name = "refund_contract_address")
|
||||
val refundContractAddress: String?,
|
||||
|
||||
/** Transaction creation timestamp in ISO-8601 format */
|
||||
@ColumnInfo(name = "created_at")
|
||||
val createdAt: Long,
|
||||
val createdAt: String,
|
||||
|
||||
@ColumnInfo(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||
@ColumnInfo(name = "pay_till")
|
||||
val payTill: String?,
|
||||
|
||||
@Embedded(prefix = "refund_")
|
||||
val refund: RefundEmbedded?,
|
||||
/** Average provider exchange duration in seconds */
|
||||
@ColumnInfo(name = "average_duration")
|
||||
val averageDuration: Long?,
|
||||
|
||||
@Embedded(prefix = "from_")
|
||||
val from: AssetEmbedded,
|
||||
|
||||
@Embedded(prefix = "to_")
|
||||
val to: AssetEmbedded,
|
||||
) {
|
||||
|
||||
data class AssetEmbedded(
|
||||
|
||||
@ColumnInfo(name = "contract_address")
|
||||
val contractAddress: String,
|
||||
|
||||
@ColumnInfo(name = "network")
|
||||
val network: String,
|
||||
|
||||
@ColumnInfo(name = "token_id")
|
||||
val tokenId: String?,
|
||||
|
||||
@ColumnInfo(name = "raw_amount")
|
||||
val rawAmount: String,
|
||||
|
||||
@ColumnInfo(name = "decimals")
|
||||
val decimals: Int,
|
||||
)
|
||||
|
||||
data class RefundEmbedded(
|
||||
@ColumnInfo(name = "amount")
|
||||
val amount: String,
|
||||
|
||||
@ColumnInfo(name = "network")
|
||||
val network: String?,
|
||||
|
||||
@ColumnInfo(name = "token_id")
|
||||
val tokenId: String?,
|
||||
|
||||
@ColumnInfo(name = "raw_amount")
|
||||
val rawAmount: String?,
|
||||
|
||||
@ColumnInfo(name = "decimals")
|
||||
val decimals: Int?,
|
||||
|
||||
/**
|
||||
* Match key for REFUND leg
|
||||
*/
|
||||
@ColumnInfo(name = "hash")
|
||||
val hash: String?,
|
||||
/** Actual provider-confirmed amount. Present only for the [ExpressExchangeEntity.to] asset */
|
||||
@ColumnInfo(name = "actual_amount")
|
||||
val actualAmount: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,11 @@ package com.tangem.datasource.local.txhistory.db.entity.express
|
|||
|
||||
import androidx.room.*
|
||||
|
||||
/**
|
||||
* Persisted representation of a single onramp transaction.
|
||||
*
|
||||
* Mirrors [com.tangem.datasource.api.onramp.models.response.OnrampItemResponse].
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "express_onramp",
|
||||
foreignKeys = [
|
||||
|
|
@ -13,7 +18,7 @@ import androidx.room.*
|
|||
),
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["owner_address", "to_network", "updated_at"]),
|
||||
Index(value = ["owner_address", "to_network", "created_at"]),
|
||||
Index(value = ["owner_address", "payout_hash"]),
|
||||
],
|
||||
)
|
||||
|
|
@ -23,71 +28,38 @@ data class ExpressOnrampEntity(
|
|||
@ColumnInfo(name = "tx_id")
|
||||
val txId: String,
|
||||
|
||||
/**
|
||||
* Address used to query the history. For onramp it matches [payoutAddress].
|
||||
*/
|
||||
@ColumnInfo(name = "owner_address")
|
||||
val ownerAddress: String,
|
||||
|
||||
@ColumnInfo(name = "provider_id")
|
||||
val providerId: String,
|
||||
|
||||
/**
|
||||
/** Address from which the source assets were taken for the exchange */
|
||||
@ColumnInfo(name = "from_address")
|
||||
val fromAddress: String,
|
||||
|
||||
* waiting-for-payment
|
||||
* payment-processing
|
||||
* paused
|
||||
* verifying
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* expired
|
||||
* refunded
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
/** Address to which the assets were transferred for the exchange */
|
||||
@ColumnInfo(name = "payin_address")
|
||||
val payinAddress: String,
|
||||
|
||||
/**
|
||||
* ISO-4217
|
||||
*/
|
||||
@ColumnInfo(name = "from_currency_code")
|
||||
val fromCurrencyCode: String,
|
||||
/** Extra ID used for the pay-in transaction */
|
||||
@ColumnInfo(name = "payin_extra_id")
|
||||
val payinExtraId: String?,
|
||||
|
||||
/**
|
||||
* Decimal string
|
||||
*/
|
||||
@ColumnInfo(name = "from_amount")
|
||||
val fromAmount: String,
|
||||
/** Address that received the target assets */
|
||||
@ColumnInfo(name = "payout_address")
|
||||
val payoutAddress: String,
|
||||
|
||||
@ColumnInfo(name = "to_network")
|
||||
val toNetwork: String,
|
||||
/** Refund destination address */
|
||||
@ColumnInfo(name = "refund_address")
|
||||
val refundAddress: String?,
|
||||
|
||||
@ColumnInfo(name = "to_token_id")
|
||||
val toTokenId: String?,
|
||||
|
||||
/**
|
||||
* Estimated amount at creation moment
|
||||
*/
|
||||
@ColumnInfo(name = "to_expected_raw_amount")
|
||||
val toExpectedRawAmount: String,
|
||||
|
||||
/**
|
||||
* Actual provider-confirmed amount
|
||||
*/
|
||||
@ColumnInfo(name = "to_actual_raw_amount")
|
||||
val toActualRawAmount: String?,
|
||||
|
||||
@ColumnInfo(name = "to_decimals")
|
||||
val toDecimals: Int,
|
||||
|
||||
/**
|
||||
* Match key with gateway_tx.hash
|
||||
*/
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
/** Extra ID used for refunds */
|
||||
@ColumnInfo(name = "refund_extra_id")
|
||||
val refundExtraId: String?,
|
||||
|
||||
/**
|
||||
* fixed / float
|
||||
|
|
@ -95,28 +67,88 @@ data class ExpressOnrampEntity(
|
|||
@ColumnInfo(name = "rate_type")
|
||||
val rateType: String,
|
||||
|
||||
@ColumnInfo(name = "fail_reason")
|
||||
val failReason: String?,
|
||||
/**
|
||||
* unknown
|
||||
* exchange-tx-sent
|
||||
* waiting
|
||||
* waiting-tx-hash
|
||||
* expired
|
||||
* confirming
|
||||
* exchanging
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* tx-failed
|
||||
* refunded
|
||||
* verifying
|
||||
* paused
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
|
||||
/** External transaction ID (CEX only) */
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
/** Transaction status reported by the provider */
|
||||
@ColumnInfo(name = "external_tx_status")
|
||||
val externalTxStatus: String?,
|
||||
|
||||
/** URL to view the transaction details (CEX only) */
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/** Blockchain hash of the pay-in transaction */
|
||||
@ColumnInfo(name = "payin_hash")
|
||||
val payinHash: String?,
|
||||
|
||||
/** Blockchain hash of the payout transaction */
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
/** Network used for the refund transaction (when status is refunded) */
|
||||
@ColumnInfo(name = "refund_network")
|
||||
val refundNetwork: String?,
|
||||
|
||||
/** Refunded token contract address */
|
||||
@ColumnInfo(name = "refund_contract_address")
|
||||
val refundContractAddress: String?,
|
||||
|
||||
/** Transaction creation timestamp in ISO-8601 format */
|
||||
@ColumnInfo(name = "created_at")
|
||||
val createdAt: Long,
|
||||
val createdAt: String,
|
||||
|
||||
@ColumnInfo(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||
@ColumnInfo(name = "pay_till")
|
||||
val payTill: String?,
|
||||
|
||||
@Embedded(prefix = "refund_")
|
||||
val refund: RefundEmbedded?,
|
||||
/** Average provider exchange duration in seconds */
|
||||
@ColumnInfo(name = "average_duration")
|
||||
val averageDuration: Long?,
|
||||
|
||||
@Embedded(prefix = "from_")
|
||||
val from: AssetEmbedded,
|
||||
|
||||
@Embedded(prefix = "to_")
|
||||
val to: AssetEmbedded,
|
||||
) {
|
||||
|
||||
data class RefundEmbedded(
|
||||
data class AssetEmbedded(
|
||||
|
||||
/**
|
||||
* ISO-4217
|
||||
*/
|
||||
@ColumnInfo(name = "currency_code")
|
||||
val currencyCode: String?,
|
||||
@ColumnInfo(name = "contract_address")
|
||||
val contractAddress: String,
|
||||
|
||||
@ColumnInfo(name = "network")
|
||||
val network: String,
|
||||
|
||||
@ColumnInfo(name = "decimals")
|
||||
val decimals: Int,
|
||||
|
||||
@ColumnInfo(name = "amount")
|
||||
val amount: String?,
|
||||
val amount: String,
|
||||
|
||||
/** Actual provider-confirmed amount. Present only for the [ExpressOnrampEntity.to] asset */
|
||||
@ColumnInfo(name = "actual_amount")
|
||||
val actualAmount: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity.express
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
/**
|
||||
* Persisted sync state of the express tx history.
|
||||
*
|
||||
* Stored inside [com.tangem.datasource.local.txhistory.db.TxHistoryDatabase] on purpose: if the history tables are
|
||||
* dropped (e.g. destructive migration), the sync state is wiped together with them and the history is re-synced
|
||||
* from scratch.
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "express_sync_state",
|
||||
primaryKeys = ["type", "address"],
|
||||
)
|
||||
data class ExpressSyncStateEntity(
|
||||
|
||||
@ColumnInfo(name = "type")
|
||||
val type: String,
|
||||
|
||||
@ColumnInfo(name = "address")
|
||||
val address: String,
|
||||
|
||||
@ColumnInfo(name = "is_initial_completed")
|
||||
val isInitialCompleted: Boolean,
|
||||
|
||||
@ColumnInfo(name = "after_cursor")
|
||||
val afterCursor: String?,
|
||||
|
||||
@ColumnInfo(name = "delta_cursor")
|
||||
val deltaCursor: String?,
|
||||
) {
|
||||
|
||||
enum class Type {
|
||||
EXCHANGE,
|
||||
ONRAMP,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultTxHistoryStore(
|
||||
private val expressExchangeStore: DataStore<Map<CommonSyncStateKey, CommonSyncState>>,
|
||||
private val expressOnrampStore: DataStore<Map<CommonSyncStateKey, CommonSyncState>>,
|
||||
) : TxHistoryStore {
|
||||
|
||||
override fun expressExchangeSyncState(key: CommonSyncStateKey): Flow<CommonSyncState> {
|
||||
return expressExchangeStore.data.map { map -> map.getOrDefault(key) }
|
||||
}
|
||||
|
||||
override fun expressOnrampSyncState(key: CommonSyncStateKey): Flow<CommonSyncState> {
|
||||
return expressOnrampStore.data.map { map -> map.getOrDefault(key) }
|
||||
}
|
||||
|
||||
override suspend fun updateExpressExchangeSyncState(
|
||||
key: CommonSyncStateKey,
|
||||
value: CommonSyncState,
|
||||
): CommonSyncState {
|
||||
return expressExchangeStore.updateData { map -> map.plus(key to value) }
|
||||
.getOrDefault(key)
|
||||
}
|
||||
|
||||
override suspend fun updateExpressOnrampSyncState(
|
||||
key: CommonSyncStateKey,
|
||||
value: CommonSyncState,
|
||||
): CommonSyncState {
|
||||
return expressOnrampStore.updateData { map -> map.plus(key to value) }
|
||||
.getOrDefault(key)
|
||||
}
|
||||
|
||||
private fun Map<CommonSyncStateKey, CommonSyncState>.getOrDefault(key: CommonSyncStateKey): CommonSyncState =
|
||||
this.getOrDefault(key, CommonSyncState.default(key))
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CommonSyncStateKey(
|
||||
val accountId: AccountId,
|
||||
val address: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CommonSyncState(
|
||||
val accountId: AccountId,
|
||||
val address: String,
|
||||
val isInitialCompleted: Boolean,
|
||||
val cursor: String?,
|
||||
) {
|
||||
companion object {
|
||||
fun default(key: CommonSyncStateKey) = CommonSyncState(
|
||||
accountId = key.accountId,
|
||||
address = key.address,
|
||||
isInitialCompleted = false,
|
||||
cursor = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TxHistoryStore {
|
||||
|
||||
fun expressExchangeSyncState(key: CommonSyncStateKey): Flow<CommonSyncState>
|
||||
fun expressOnrampSyncState(key: CommonSyncStateKey): Flow<CommonSyncState>
|
||||
|
||||
suspend fun updateExpressExchangeSyncState(key: CommonSyncStateKey, value: CommonSyncState): CommonSyncState
|
||||
suspend fun updateExpressOnrampSyncState(key: CommonSyncStateKey, value: CommonSyncState): CommonSyncState
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.tangem.data.txhistory.repository
|
||||
|
||||
import com.tangem.data.txhistory.repository.converter.toEntity
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeHistoryDeltaResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeHistoryResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryDeltaResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressSyncStateDao
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Fetches express (exchange & onramp) transaction history from the API and persists it into the local database.
|
||||
*
|
||||
*/
|
||||
internal class ExpressHistoryRepository @Inject constructor(
|
||||
private val exchangeApi: TangemExpressApi,
|
||||
private val onrampApi: OnrampApi,
|
||||
private val expressHistoryDao: ExpressHistoryDao,
|
||||
private val expressSyncStateDao: ExpressSyncStateDao,
|
||||
) {
|
||||
|
||||
suspend fun fetchExchangeHistory(fromAddress: String, limit: Int = DEFAULT_LIMIT): ExchangeHistoryResponse {
|
||||
val state = syncState(ExpressSyncStateEntity.Type.EXCHANGE, fromAddress)
|
||||
|
||||
val response = exchangeApi.getHistory(
|
||||
fromAddress = fromAddress,
|
||||
cursor = state?.afterCursor,
|
||||
limit = limit,
|
||||
).getOrThrow()
|
||||
|
||||
saveExchanges(ownerAddress = fromAddress, items = response.items)
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun fetchExchangeHistoryDelta(
|
||||
fromAddress: String,
|
||||
limit: Int = DEFAULT_LIMIT,
|
||||
): ExchangeHistoryDeltaResponse {
|
||||
val state = syncState(ExpressSyncStateEntity.Type.EXCHANGE, fromAddress)
|
||||
|
||||
val response = exchangeApi.getHistoryDelta(
|
||||
fromAddress = fromAddress,
|
||||
cursor = state?.deltaCursor,
|
||||
limit = limit,
|
||||
).getOrThrow()
|
||||
|
||||
saveExchanges(ownerAddress = fromAddress, items = response.items)
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun fetchOnrampHistory(payoutAddress: String, limit: Int = DEFAULT_LIMIT): OnrampHistoryResponse {
|
||||
val state = syncState(ExpressSyncStateEntity.Type.ONRAMP, payoutAddress)
|
||||
|
||||
val response = onrampApi.getHistory(
|
||||
payoutAddress = payoutAddress,
|
||||
afterCursor = state?.afterCursor,
|
||||
limit = limit,
|
||||
).getOrThrow()
|
||||
|
||||
saveOnramps(ownerAddress = payoutAddress, items = response.items)
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun fetchOnrampHistoryDelta(
|
||||
payoutAddress: String,
|
||||
limit: Int = DEFAULT_LIMIT,
|
||||
): OnrampHistoryDeltaResponse {
|
||||
val state = syncState(ExpressSyncStateEntity.Type.ONRAMP, payoutAddress)
|
||||
|
||||
val response = onrampApi.getHistoryDelta(
|
||||
payoutAddress = payoutAddress,
|
||||
cursor = state?.deltaCursor,
|
||||
limit = limit,
|
||||
).getOrThrow()
|
||||
|
||||
saveOnramps(ownerAddress = payoutAddress, items = response.items)
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun syncState(type: ExpressSyncStateEntity.Type, address: String): ExpressSyncStateEntity? {
|
||||
return expressSyncStateDao.observe(type = type.name, address = address).first()
|
||||
}
|
||||
|
||||
private suspend fun saveExchanges(ownerAddress: String, items: List<ExchangeItemResponse>) {
|
||||
expressHistoryDao.upsertExchanges(items.map { it.toEntity(ownerAddress) })
|
||||
}
|
||||
|
||||
private suspend fun saveOnramps(ownerAddress: String, items: List<OnrampItemResponse>) {
|
||||
expressHistoryDao.upsertOnramps(items.map { it.toEntity(ownerAddress) })
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DEFAULT_LIMIT = 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.data.txhistory.repository.converter
|
||||
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
|
||||
/**
|
||||
* Maps API history items into their persisted [androidx.room.Entity] representations.
|
||||
*
|
||||
* @param ownerAddress address the history was requested for. Stored as the query key.
|
||||
*/
|
||||
internal fun ExchangeItemResponse.toEntity(ownerAddress: String): ExpressExchangeEntity {
|
||||
return ExpressExchangeEntity(
|
||||
txId = txId,
|
||||
ownerAddress = ownerAddress,
|
||||
providerId = providerId,
|
||||
fromAddress = fromAddress,
|
||||
payinAddress = payinAddress,
|
||||
payinExtraId = payinExtraId,
|
||||
payoutAddress = payoutAddress,
|
||||
refundAddress = refundAddress,
|
||||
refundExtraId = refundExtraId,
|
||||
rateType = rateType,
|
||||
status = status.name,
|
||||
externalTxId = externalTxId,
|
||||
externalTxStatus = externalTxStatus,
|
||||
externalTxUrl = externalTxUrl,
|
||||
payinHash = payinHash,
|
||||
payoutHash = payoutHash,
|
||||
refundNetwork = refundNetwork,
|
||||
refundContractAddress = refundContractAddress,
|
||||
createdAt = createdAt,
|
||||
payTill = payTill,
|
||||
averageDuration = averageDuration,
|
||||
from = ExpressExchangeEntity.AssetEmbedded(
|
||||
contractAddress = fromContractAddress,
|
||||
network = fromNetwork,
|
||||
decimals = fromDecimals,
|
||||
amount = fromAmount,
|
||||
actualAmount = null,
|
||||
),
|
||||
to = ExpressExchangeEntity.AssetEmbedded(
|
||||
contractAddress = toContractAddress,
|
||||
network = toNetwork,
|
||||
decimals = toDecimals,
|
||||
amount = toAmount,
|
||||
actualAmount = toActualAmount,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun OnrampItemResponse.toEntity(ownerAddress: String): ExpressOnrampEntity {
|
||||
return ExpressOnrampEntity(
|
||||
txId = txId,
|
||||
ownerAddress = ownerAddress,
|
||||
providerId = providerId,
|
||||
fromAddress = fromAddress,
|
||||
payinAddress = payinAddress,
|
||||
payinExtraId = payinExtraId,
|
||||
payoutAddress = payoutAddress,
|
||||
refundAddress = refundAddress,
|
||||
refundExtraId = refundExtraId,
|
||||
rateType = rateType,
|
||||
status = status.name,
|
||||
externalTxId = externalTxId,
|
||||
externalTxStatus = externalTxStatus,
|
||||
externalTxUrl = externalTxUrl,
|
||||
payinHash = payinHash,
|
||||
payoutHash = payoutHash,
|
||||
refundNetwork = refundNetwork,
|
||||
refundContractAddress = refundContractAddress,
|
||||
createdAt = createdAt,
|
||||
payTill = payTill,
|
||||
averageDuration = averageDuration,
|
||||
from = ExpressOnrampEntity.AssetEmbedded(
|
||||
contractAddress = fromContractAddress,
|
||||
network = fromNetwork,
|
||||
decimals = fromDecimals,
|
||||
amount = fromAmount,
|
||||
actualAmount = null,
|
||||
),
|
||||
to = ExpressOnrampEntity.AssetEmbedded(
|
||||
contractAddress = toContractAddress,
|
||||
network = toNetwork,
|
||||
decimals = toDecimals,
|
||||
amount = toAmount,
|
||||
actualAmount = toActualAmount,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.data.txhistory.fetcher
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.TestAppCoroutineScope
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.data.txhistory.fetcher
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.TestAppCoroutineScope
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.data.txhistory.fetcher
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.TestAppCoroutineScope
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,367 @@
|
|||
package com.tangem.data.txhistory.repository
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.data.txhistory.repository.converter.toEntity
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeHistoryDeltaResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeHistoryResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExpressPagination
|
||||
import com.tangem.datasource.api.express.models.response.ExpressPaginationDelta
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryDeltaResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressSyncStateDao
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ExpressHistoryRepositoryTest {
|
||||
|
||||
private val exchangeApi: TangemExpressApi = mockk()
|
||||
private val onrampApi: OnrampApi = mockk()
|
||||
private val expressHistoryDao: ExpressHistoryDao = mockk(relaxUnitFun = true)
|
||||
private val expressSyncStateDao: ExpressSyncStateDao = mockk()
|
||||
|
||||
private val repository = ExpressHistoryRepository(
|
||||
exchangeApi = exchangeApi,
|
||||
onrampApi = onrampApi,
|
||||
expressHistoryDao = expressHistoryDao,
|
||||
expressSyncStateDao = expressSyncStateDao,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(exchangeApi, onrampApi, expressHistoryDao, expressSyncStateDao)
|
||||
}
|
||||
|
||||
// region exchange history
|
||||
|
||||
@Test
|
||||
fun `GIVEN sync state WHEN fetchExchangeHistory THEN passes after cursor and persists items`() = runTest {
|
||||
// GIVEN
|
||||
val item = createExchangeItem()
|
||||
val response = ExchangeHistoryResponse(items = listOf(item), pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
coEvery {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
val result = repository.fetchExchangeHistory(fromAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(response)
|
||||
coVerify(exactly = 1) {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
coVerify(exactly = 1) { expressHistoryDao.upsertExchanges(listOf(item.toEntity(ADDRESS))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no sync state WHEN fetchExchangeHistory THEN passes null cursor`() = runTest {
|
||||
// GIVEN
|
||||
val response = ExchangeHistoryResponse(items = emptyList(), pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, state = null)
|
||||
coEvery {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = null, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
repository.fetchExchangeHistory(fromAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = null, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN custom limit WHEN fetchExchangeHistory THEN forwards limit to api`() = runTest {
|
||||
// GIVEN
|
||||
val response = ExchangeHistoryResponse(items = emptyList(), pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
coEvery {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
repository.fetchExchangeHistory(fromAddress = ADDRESS, limit = 25)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = 25)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN api error WHEN fetchExchangeHistory THEN throws and does not persist`() = runTest {
|
||||
// GIVEN
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
val error = httpError()
|
||||
coEvery {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Error(error).cast()
|
||||
|
||||
// WHEN
|
||||
val thrown = runCatching { repository.fetchExchangeHistory(fromAddress = ADDRESS) }.exceptionOrNull()
|
||||
|
||||
// THEN
|
||||
assertThat(thrown).isEqualTo(error)
|
||||
coVerify(exactly = 0) { expressHistoryDao.upsertExchanges(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sync state WHEN fetchExchangeHistoryDelta THEN passes delta cursor and persists items`() = runTest {
|
||||
// GIVEN
|
||||
val item = createExchangeItem()
|
||||
val response = ExchangeHistoryDeltaResponse(items = listOf(item), pagination = paginationDelta())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, syncState(deltaCursor = DELTA_CURSOR))
|
||||
coEvery {
|
||||
exchangeApi.getHistoryDelta(fromAddress = ADDRESS, cursor = DELTA_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
val result = repository.fetchExchangeHistoryDelta(fromAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(response)
|
||||
coVerify(exactly = 1) {
|
||||
exchangeApi.getHistoryDelta(fromAddress = ADDRESS, cursor = DELTA_CURSOR, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
coVerify(exactly = 1) { expressHistoryDao.upsertExchanges(listOf(item.toEntity(ADDRESS))) }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region onramp history
|
||||
|
||||
@Test
|
||||
fun `GIVEN sync state WHEN fetchOnrampHistory THEN passes after cursor and persists items`() = runTest {
|
||||
// GIVEN
|
||||
val item = createOnrampItem()
|
||||
val response = OnrampHistoryResponse(items = listOf(item), pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.ONRAMP, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
coEvery {
|
||||
onrampApi.getHistory(payoutAddress = ADDRESS, afterCursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
val result = repository.fetchOnrampHistory(payoutAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(response)
|
||||
coVerify(exactly = 1) {
|
||||
onrampApi.getHistory(payoutAddress = ADDRESS, afterCursor = AFTER_CURSOR, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
coVerify(exactly = 1) { expressHistoryDao.upsertOnramps(listOf(item.toEntity(ADDRESS))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no sync state WHEN fetchOnrampHistory THEN passes null cursor`() = runTest {
|
||||
// GIVEN
|
||||
val response = OnrampHistoryResponse(items = emptyList(), pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.ONRAMP, ADDRESS, state = null)
|
||||
coEvery {
|
||||
onrampApi.getHistory(payoutAddress = ADDRESS, afterCursor = null, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
repository.fetchOnrampHistory(payoutAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
onrampApi.getHistory(payoutAddress = ADDRESS, afterCursor = null, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sync state WHEN fetchOnrampHistoryDelta THEN passes delta cursor and persists items`() = runTest {
|
||||
// GIVEN
|
||||
val item = createOnrampItem()
|
||||
val response = OnrampHistoryDeltaResponse(items = listOf(item), pagination = paginationDelta())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.ONRAMP, ADDRESS, syncState(deltaCursor = DELTA_CURSOR))
|
||||
coEvery {
|
||||
onrampApi.getHistoryDelta(payoutAddress = ADDRESS, cursor = DELTA_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
|
||||
// WHEN
|
||||
val result = repository.fetchOnrampHistoryDelta(payoutAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(response)
|
||||
coVerify(exactly = 1) {
|
||||
onrampApi.getHistoryDelta(payoutAddress = ADDRESS, cursor = DELTA_CURSOR, limit = DEFAULT_LIMIT)
|
||||
}
|
||||
coVerify(exactly = 1) { expressHistoryDao.upsertOnramps(listOf(item.toEntity(ADDRESS))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN api error WHEN fetchOnrampHistory THEN throws and does not persist`() = runTest {
|
||||
// GIVEN
|
||||
stubSyncState(ExpressSyncStateEntity.Type.ONRAMP, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
val error = httpError()
|
||||
coEvery {
|
||||
onrampApi.getHistory(payoutAddress = ADDRESS, afterCursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Error(error).cast()
|
||||
|
||||
// WHEN
|
||||
val thrown = runCatching { repository.fetchOnrampHistory(payoutAddress = ADDRESS) }.exceptionOrNull()
|
||||
|
||||
// THEN
|
||||
assertThat(thrown).isEqualTo(error)
|
||||
coVerify(exactly = 0) { expressHistoryDao.upsertOnramps(any()) }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region syncState
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored sync state WHEN syncState THEN returns first emitted value`() = runTest {
|
||||
// GIVEN
|
||||
val state = syncState(afterCursor = AFTER_CURSOR, deltaCursor = DELTA_CURSOR)
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, state)
|
||||
|
||||
// WHEN
|
||||
val result = repository.syncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(state)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple items WHEN fetchExchangeHistory THEN maps every item with owner address`() = runTest {
|
||||
// GIVEN
|
||||
val items = listOf(
|
||||
createExchangeItem(txId = "tx-1"),
|
||||
createExchangeItem(txId = "tx-2"),
|
||||
)
|
||||
val response = ExchangeHistoryResponse(items = items, pagination = pagination())
|
||||
stubSyncState(ExpressSyncStateEntity.Type.EXCHANGE, ADDRESS, syncState(afterCursor = AFTER_CURSOR))
|
||||
coEvery {
|
||||
exchangeApi.getHistory(fromAddress = ADDRESS, cursor = AFTER_CURSOR, limit = any())
|
||||
} returns ApiResponse.Success(response)
|
||||
val saved = slot<List<ExpressExchangeEntity>>()
|
||||
coEvery { expressHistoryDao.upsertExchanges(capture(saved)) } returns Unit
|
||||
|
||||
// WHEN
|
||||
repository.fetchExchangeHistory(fromAddress = ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(saved.captured).isEqualTo(items.map { it.toEntity(ADDRESS) })
|
||||
assertThat(saved.captured.map { it.ownerAddress }.toSet()).containsExactly(ADDRESS)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private fun stubSyncState(type: ExpressSyncStateEntity.Type, address: String, state: ExpressSyncStateEntity?) {
|
||||
coEvery { expressSyncStateDao.observe(type = type.name, address = address) } returns flowOf(state)
|
||||
}
|
||||
|
||||
private fun syncState(afterCursor: String? = null, deltaCursor: String? = null) = ExpressSyncStateEntity(
|
||||
type = ExpressSyncStateEntity.Type.EXCHANGE.name,
|
||||
address = ADDRESS,
|
||||
isInitialCompleted = true,
|
||||
afterCursor = afterCursor,
|
||||
deltaCursor = deltaCursor,
|
||||
)
|
||||
|
||||
private fun pagination() = ExpressPagination(endCursor = "end", startDeltaCursor = "delta", hasMore = false)
|
||||
|
||||
private fun paginationDelta() = ExpressPaginationDelta(startCursor = "start", hasMore = false)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T : Any> ApiResponse.Error.cast(): ApiResponse<T> = this as ApiResponse<T>
|
||||
|
||||
private fun httpError() = ApiResponseError.HttpException(
|
||||
code = ApiResponseError.HttpException.Code.INTERNAL_SERVER_ERROR,
|
||||
message = "boom",
|
||||
errorBody = null,
|
||||
)
|
||||
|
||||
private fun createExchangeItem(txId: String = "exchange-tx-1") = ExchangeItemResponse(
|
||||
txId = txId,
|
||||
providerId = "changelly",
|
||||
fromAddress = "0xfrom",
|
||||
payinAddress = "0xpayin",
|
||||
payinExtraId = null,
|
||||
payoutAddress = "0xpayout",
|
||||
refundAddress = null,
|
||||
refundExtraId = null,
|
||||
rateType = "float",
|
||||
status = ExchangeItemResponse.Status.FINISHED,
|
||||
externalTxId = null,
|
||||
externalTxStatus = null,
|
||||
externalTxUrl = null,
|
||||
payinHash = "payin-hash",
|
||||
payoutHash = "payout-hash",
|
||||
refundNetwork = null,
|
||||
refundContractAddress = null,
|
||||
createdAt = "2026-06-01T00:00:00Z",
|
||||
payTill = null,
|
||||
averageDuration = null,
|
||||
fromContractAddress = "0xfromContract",
|
||||
fromNetwork = "ethereum",
|
||||
fromDecimals = 18,
|
||||
fromAmount = "1.0",
|
||||
toContractAddress = "0xtoContract",
|
||||
toNetwork = "bitcoin",
|
||||
toDecimals = 8,
|
||||
toAmount = "1.0",
|
||||
toActualAmount = "0.99",
|
||||
)
|
||||
|
||||
private fun createOnrampItem(txId: String = "onramp-tx-1") = OnrampItemResponse(
|
||||
txId = txId,
|
||||
providerId = "mercuryo",
|
||||
fromAddress = "0xfrom",
|
||||
payinAddress = "0xpayin",
|
||||
payinExtraId = null,
|
||||
payoutAddress = "0xpayout",
|
||||
refundAddress = null,
|
||||
refundExtraId = null,
|
||||
rateType = "fixed",
|
||||
status = OnrampItemResponse.Status.FINISHED,
|
||||
externalTxId = null,
|
||||
externalTxStatus = null,
|
||||
externalTxUrl = null,
|
||||
payinHash = "payin-hash",
|
||||
payoutHash = "payout-hash",
|
||||
refundNetwork = null,
|
||||
refundContractAddress = null,
|
||||
createdAt = "2026-06-01T00:00:00Z",
|
||||
payTill = null,
|
||||
averageDuration = null,
|
||||
fromContractAddress = "0xfromContract",
|
||||
fromNetwork = "usd",
|
||||
fromDecimals = 2,
|
||||
fromAmount = "100.0",
|
||||
toContractAddress = "0xtoContract",
|
||||
toNetwork = "bitcoin",
|
||||
toDecimals = 8,
|
||||
toAmount = "0.001",
|
||||
toActualAmount = "0.99",
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val ADDRESS = "0xowner"
|
||||
const val AFTER_CURSOR = "after-cursor"
|
||||
const val DELTA_CURSOR = "delta-cursor"
|
||||
const val DEFAULT_LIMIT = 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
package com.tangem.data.txhistory.repository.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ExpressHistoryConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN exchange item WHEN toEntity THEN all transaction fields are mapped`() {
|
||||
// GIVEN
|
||||
val item = createExchangeItem()
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.txId).isEqualTo(item.txId)
|
||||
assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
|
||||
assertThat(entity.providerId).isEqualTo(item.providerId)
|
||||
assertThat(entity.fromAddress).isEqualTo(item.fromAddress)
|
||||
assertThat(entity.payinAddress).isEqualTo(item.payinAddress)
|
||||
assertThat(entity.payinExtraId).isEqualTo(item.payinExtraId)
|
||||
assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
|
||||
assertThat(entity.refundAddress).isEqualTo(item.refundAddress)
|
||||
assertThat(entity.refundExtraId).isEqualTo(item.refundExtraId)
|
||||
assertThat(entity.rateType).isEqualTo(item.rateType)
|
||||
assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
|
||||
assertThat(entity.externalTxStatus).isEqualTo(item.externalTxStatus)
|
||||
assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
|
||||
assertThat(entity.payinHash).isEqualTo(item.payinHash)
|
||||
assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
|
||||
assertThat(entity.refundNetwork).isEqualTo(item.refundNetwork)
|
||||
assertThat(entity.refundContractAddress).isEqualTo(item.refundContractAddress)
|
||||
assertThat(entity.createdAt).isEqualTo(item.createdAt)
|
||||
assertThat(entity.payTill).isEqualTo(item.payTill)
|
||||
assertThat(entity.averageDuration).isEqualTo(item.averageDuration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN exchange item WHEN toEntity THEN status is stored as enum name`() {
|
||||
// GIVEN
|
||||
val item = createExchangeItem(status = ExchangeItemResponse.Status.FINISHED)
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.status).isEqualTo("FINISHED")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN exchange item WHEN toEntity THEN from and to assets are mapped`() {
|
||||
// GIVEN
|
||||
val item = createExchangeItem()
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.from.contractAddress).isEqualTo(item.fromContractAddress)
|
||||
assertThat(entity.from.network).isEqualTo(item.fromNetwork)
|
||||
assertThat(entity.from.decimals).isEqualTo(item.fromDecimals)
|
||||
assertThat(entity.from.amount).isEqualTo(item.fromAmount)
|
||||
// `from` asset never carries an actual amount
|
||||
assertThat(entity.from.actualAmount).isNull()
|
||||
|
||||
assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
|
||||
assertThat(entity.to.network).isEqualTo(item.toNetwork)
|
||||
assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
|
||||
assertThat(entity.to.amount).isEqualTo(item.toAmount)
|
||||
assertThat(entity.to.actualAmount).isEqualTo(item.toActualAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN exchange item with null optional fields WHEN toEntity THEN nulls are preserved`() {
|
||||
// GIVEN
|
||||
val item = createExchangeItem(
|
||||
payinExtraId = null,
|
||||
refundAddress = null,
|
||||
refundExtraId = null,
|
||||
externalTxId = null,
|
||||
externalTxStatus = null,
|
||||
externalTxUrl = null,
|
||||
payinHash = null,
|
||||
payoutHash = null,
|
||||
refundNetwork = null,
|
||||
refundContractAddress = null,
|
||||
payTill = null,
|
||||
averageDuration = null,
|
||||
toActualAmount = null,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.payinExtraId).isNull()
|
||||
assertThat(entity.refundAddress).isNull()
|
||||
assertThat(entity.refundExtraId).isNull()
|
||||
assertThat(entity.externalTxId).isNull()
|
||||
assertThat(entity.externalTxStatus).isNull()
|
||||
assertThat(entity.externalTxUrl).isNull()
|
||||
assertThat(entity.payinHash).isNull()
|
||||
assertThat(entity.payoutHash).isNull()
|
||||
assertThat(entity.refundNetwork).isNull()
|
||||
assertThat(entity.refundContractAddress).isNull()
|
||||
assertThat(entity.payTill).isNull()
|
||||
assertThat(entity.averageDuration).isNull()
|
||||
assertThat(entity.to.actualAmount).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN onramp item WHEN toEntity THEN all transaction fields are mapped`() {
|
||||
// GIVEN
|
||||
val item = createOnrampItem()
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.txId).isEqualTo(item.txId)
|
||||
assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
|
||||
assertThat(entity.providerId).isEqualTo(item.providerId)
|
||||
assertThat(entity.fromAddress).isEqualTo(item.fromAddress)
|
||||
assertThat(entity.payinAddress).isEqualTo(item.payinAddress)
|
||||
assertThat(entity.payinExtraId).isEqualTo(item.payinExtraId)
|
||||
assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
|
||||
assertThat(entity.refundAddress).isEqualTo(item.refundAddress)
|
||||
assertThat(entity.refundExtraId).isEqualTo(item.refundExtraId)
|
||||
assertThat(entity.rateType).isEqualTo(item.rateType)
|
||||
assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
|
||||
assertThat(entity.externalTxStatus).isEqualTo(item.externalTxStatus)
|
||||
assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
|
||||
assertThat(entity.payinHash).isEqualTo(item.payinHash)
|
||||
assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
|
||||
assertThat(entity.refundNetwork).isEqualTo(item.refundNetwork)
|
||||
assertThat(entity.refundContractAddress).isEqualTo(item.refundContractAddress)
|
||||
assertThat(entity.createdAt).isEqualTo(item.createdAt)
|
||||
assertThat(entity.payTill).isEqualTo(item.payTill)
|
||||
assertThat(entity.averageDuration).isEqualTo(item.averageDuration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN onramp item WHEN toEntity THEN status is stored as enum name`() {
|
||||
// GIVEN
|
||||
val item = createOnrampItem(status = OnrampItemResponse.Status.WAITING_TX_HASH)
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.status).isEqualTo("WAITING_TX_HASH")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN onramp item WHEN toEntity THEN from and to assets are mapped`() {
|
||||
// GIVEN
|
||||
val item = createOnrampItem()
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.from.contractAddress).isEqualTo(item.fromContractAddress)
|
||||
assertThat(entity.from.network).isEqualTo(item.fromNetwork)
|
||||
assertThat(entity.from.decimals).isEqualTo(item.fromDecimals)
|
||||
assertThat(entity.from.amount).isEqualTo(item.fromAmount)
|
||||
assertThat(entity.from.actualAmount).isNull()
|
||||
|
||||
assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
|
||||
assertThat(entity.to.network).isEqualTo(item.toNetwork)
|
||||
assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
|
||||
assertThat(entity.to.amount).isEqualTo(item.toAmount)
|
||||
assertThat(entity.to.actualAmount).isEqualTo(item.toActualAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN onramp item with null optional fields WHEN toEntity THEN nulls are preserved`() {
|
||||
// GIVEN
|
||||
val item = createOnrampItem(
|
||||
payinExtraId = null,
|
||||
refundAddress = null,
|
||||
refundExtraId = null,
|
||||
externalTxId = null,
|
||||
externalTxStatus = null,
|
||||
externalTxUrl = null,
|
||||
payinHash = null,
|
||||
payoutHash = null,
|
||||
refundNetwork = null,
|
||||
refundContractAddress = null,
|
||||
payTill = null,
|
||||
averageDuration = null,
|
||||
toActualAmount = null,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||
|
||||
// THEN
|
||||
assertThat(entity.payinExtraId).isNull()
|
||||
assertThat(entity.refundAddress).isNull()
|
||||
assertThat(entity.refundExtraId).isNull()
|
||||
assertThat(entity.externalTxId).isNull()
|
||||
assertThat(entity.externalTxStatus).isNull()
|
||||
assertThat(entity.externalTxUrl).isNull()
|
||||
assertThat(entity.payinHash).isNull()
|
||||
assertThat(entity.payoutHash).isNull()
|
||||
assertThat(entity.refundNetwork).isNull()
|
||||
assertThat(entity.refundContractAddress).isNull()
|
||||
assertThat(entity.payTill).isNull()
|
||||
assertThat(entity.averageDuration).isNull()
|
||||
assertThat(entity.to.actualAmount).isNull()
|
||||
}
|
||||
|
||||
private fun createExchangeItem(
|
||||
status: ExchangeItemResponse.Status = ExchangeItemResponse.Status.WAITING,
|
||||
payinExtraId: String? = "payin-extra",
|
||||
refundAddress: String? = "refund-address",
|
||||
refundExtraId: String? = "refund-extra",
|
||||
externalTxId: String? = "external-tx-id",
|
||||
externalTxStatus: String? = "external-status",
|
||||
externalTxUrl: String? = "https://provider.example/tx",
|
||||
payinHash: String? = "payin-hash",
|
||||
payoutHash: String? = "payout-hash",
|
||||
refundNetwork: String? = "ethereum",
|
||||
refundContractAddress: String? = "0xrefund",
|
||||
payTill: String? = "2026-06-01T00:10:00Z",
|
||||
averageDuration: Long? = 600L,
|
||||
toActualAmount: String? = "0.99",
|
||||
) = ExchangeItemResponse(
|
||||
txId = "exchange-tx-1",
|
||||
providerId = "changelly",
|
||||
fromAddress = "0xfrom",
|
||||
payinAddress = "0xpayin",
|
||||
payinExtraId = payinExtraId,
|
||||
payoutAddress = "0xpayout",
|
||||
refundAddress = refundAddress,
|
||||
refundExtraId = refundExtraId,
|
||||
rateType = "float",
|
||||
status = status,
|
||||
externalTxId = externalTxId,
|
||||
externalTxStatus = externalTxStatus,
|
||||
externalTxUrl = externalTxUrl,
|
||||
payinHash = payinHash,
|
||||
payoutHash = payoutHash,
|
||||
refundNetwork = refundNetwork,
|
||||
refundContractAddress = refundContractAddress,
|
||||
createdAt = "2026-06-01T00:00:00Z",
|
||||
payTill = payTill,
|
||||
averageDuration = averageDuration,
|
||||
fromContractAddress = "0xfromContract",
|
||||
fromNetwork = "ethereum",
|
||||
fromDecimals = 18,
|
||||
fromAmount = "1.0",
|
||||
toContractAddress = "0xtoContract",
|
||||
toNetwork = "bitcoin",
|
||||
toDecimals = 8,
|
||||
toAmount = "1.0",
|
||||
toActualAmount = toActualAmount,
|
||||
)
|
||||
|
||||
private fun createOnrampItem(
|
||||
status: OnrampItemResponse.Status = OnrampItemResponse.Status.WAITING,
|
||||
payinExtraId: String? = "payin-extra",
|
||||
refundAddress: String? = "refund-address",
|
||||
refundExtraId: String? = "refund-extra",
|
||||
externalTxId: String? = "external-tx-id",
|
||||
externalTxStatus: String? = "external-status",
|
||||
externalTxUrl: String? = "https://provider.example/tx",
|
||||
payinHash: String? = "payin-hash",
|
||||
payoutHash: String? = "payout-hash",
|
||||
refundNetwork: String? = "ethereum",
|
||||
refundContractAddress: String? = "0xrefund",
|
||||
payTill: String? = "2026-06-01T00:10:00Z",
|
||||
averageDuration: Long? = 600L,
|
||||
toActualAmount: String? = "0.99",
|
||||
) = OnrampItemResponse(
|
||||
txId = "onramp-tx-1",
|
||||
providerId = "mercuryo",
|
||||
fromAddress = "0xfrom",
|
||||
payinAddress = "0xpayin",
|
||||
payinExtraId = payinExtraId,
|
||||
payoutAddress = "0xpayout",
|
||||
refundAddress = refundAddress,
|
||||
refundExtraId = refundExtraId,
|
||||
rateType = "fixed",
|
||||
status = status,
|
||||
externalTxId = externalTxId,
|
||||
externalTxStatus = externalTxStatus,
|
||||
externalTxUrl = externalTxUrl,
|
||||
payinHash = payinHash,
|
||||
payoutHash = payoutHash,
|
||||
refundNetwork = refundNetwork,
|
||||
refundContractAddress = refundContractAddress,
|
||||
createdAt = "2026-06-01T00:00:00Z",
|
||||
payTill = payTill,
|
||||
averageDuration = averageDuration,
|
||||
fromContractAddress = "0xfromContract",
|
||||
fromNetwork = "usd",
|
||||
fromDecimals = 2,
|
||||
fromAmount = "100.0",
|
||||
toContractAddress = "0xtoContract",
|
||||
toNetwork = "bitcoin",
|
||||
toDecimals = 8,
|
||||
toAmount = "0.001",
|
||||
toActualAmount = toActualAmount,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val OWNER_ADDRESS = "0xowner"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue