Updated on 2026-08-14
This commit is contained in:
parent
6e92e35a38
commit
4d0cb6a243
24 changed files with 697 additions and 887 deletions
|
|
@ -2,11 +2,11 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "36ff7cc1634c100cadc4b04fc2eba1c9",
|
"identityHash": "442ac578743a8b624777711cf49c77e2",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "express_provider",
|
"tableName": "express_provider",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `provider_url` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `image_large` TEXT NOT NULL, `image_small` TEXT NOT NULL, `terms_of_use` TEXT, `privacy_policy` TEXT, `is_recommended` INTEGER NOT NULL, `slippage` TEXT, `is_exchange_only_within_single_address` INTEGER NOT NULL, `is_extra_id_supported` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
|
@ -21,16 +21,55 @@
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "iconUrl",
|
"fieldPath": "type",
|
||||||
"columnName": "icon_url",
|
"columnName": "type",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "providerUrl",
|
"fieldPath": "imageLarge",
|
||||||
"columnName": "provider_url",
|
"columnName": "image_large",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageSmall",
|
||||||
|
"columnName": "image_small",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "termsOfUse",
|
||||||
|
"columnName": "terms_of_use",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "privacyPolicy",
|
||||||
|
"columnName": "privacy_policy",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRecommended",
|
||||||
|
"columnName": "is_recommended",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slippage",
|
||||||
|
"columnName": "slippage",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isExchangeOnlyWithinSingleAddress",
|
||||||
|
"columnName": "is_exchange_only_within_single_address",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isExtraIdSupported",
|
||||||
|
"columnName": "is_extra_id_supported",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -38,13 +77,11 @@
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "express_exchange",
|
"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, `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`))",
|
"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, `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_url` TEXT, `payin_hash` TEXT, `payout_hash` TEXT, `refund_network` TEXT, `refund_contract_address` TEXT, `created_at` TEXT NOT NULL, `updated_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`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "txId",
|
"fieldPath": "txId",
|
||||||
|
|
@ -67,8 +104,7 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "fromAddress",
|
"fieldPath": "fromAddress",
|
||||||
"columnName": "from_address",
|
"columnName": "from_address",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payinAddress",
|
"fieldPath": "payinAddress",
|
||||||
|
|
@ -79,8 +115,7 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "payinExtraId",
|
"fieldPath": "payinExtraId",
|
||||||
"columnName": "payin_extra_id",
|
"columnName": "payin_extra_id",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payoutAddress",
|
"fieldPath": "payoutAddress",
|
||||||
|
|
@ -91,14 +126,12 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "refundAddress",
|
"fieldPath": "refundAddress",
|
||||||
"columnName": "refund_address",
|
"columnName": "refund_address",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "refundExtraId",
|
"fieldPath": "refundExtraId",
|
||||||
"columnName": "refund_extra_id",
|
"columnName": "refund_extra_id",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "rateType",
|
"fieldPath": "rateType",
|
||||||
|
|
@ -115,44 +148,32 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "externalTxId",
|
"fieldPath": "externalTxId",
|
||||||
"columnName": "external_tx_id",
|
"columnName": "external_tx_id",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "externalTxStatus",
|
|
||||||
"columnName": "external_tx_status",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "externalTxUrl",
|
"fieldPath": "externalTxUrl",
|
||||||
"columnName": "external_tx_url",
|
"columnName": "external_tx_url",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payinHash",
|
"fieldPath": "payinHash",
|
||||||
"columnName": "payin_hash",
|
"columnName": "payin_hash",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payoutHash",
|
"fieldPath": "payoutHash",
|
||||||
"columnName": "payout_hash",
|
"columnName": "payout_hash",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "refundNetwork",
|
"fieldPath": "refundNetwork",
|
||||||
"columnName": "refund_network",
|
"columnName": "refund_network",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "refundContractAddress",
|
"fieldPath": "refundContractAddress",
|
||||||
"columnName": "refund_contract_address",
|
"columnName": "refund_contract_address",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "createdAt",
|
"fieldPath": "createdAt",
|
||||||
|
|
@ -160,17 +181,21 @@
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "updatedAt",
|
||||||
|
"columnName": "updated_at",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payTill",
|
"fieldPath": "payTill",
|
||||||
"columnName": "pay_till",
|
"columnName": "pay_till",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "averageDuration",
|
"fieldPath": "averageDuration",
|
||||||
"columnName": "average_duration",
|
"columnName": "average_duration",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "from.contractAddress",
|
"fieldPath": "from.contractAddress",
|
||||||
|
|
@ -199,8 +224,7 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "from.actualAmount",
|
"fieldPath": "from.actualAmount",
|
||||||
"columnName": "from_actual_amount",
|
"columnName": "from_actual_amount",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "to.contractAddress",
|
"fieldPath": "to.contractAddress",
|
||||||
|
|
@ -229,8 +253,7 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "to.actualAmount",
|
"fieldPath": "to.actualAmount",
|
||||||
"columnName": "to_actual_amount",
|
"columnName": "to_actual_amount",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -241,51 +264,33 @@
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
"name": "index_express_exchange_owner_address_from_network_created_at",
|
"name": "index_express_exchange_owner_address_from_network_from_contract_address_created_at",
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"owner_address",
|
"owner_address",
|
||||||
"from_network",
|
"from_network",
|
||||||
|
"from_contract_address",
|
||||||
"created_at"
|
"created_at"
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_from_network_created_at` ON `${TABLE_NAME}` (`owner_address`, `from_network`, `created_at`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_from_network_from_contract_address_created_at` ON `${TABLE_NAME}` (`owner_address`, `from_network`, `from_contract_address`, `created_at`)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "index_express_exchange_owner_address_payin_hash",
|
"name": "index_express_exchange_to_network_to_contract_address_created_at",
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"owner_address",
|
"to_network",
|
||||||
"payin_hash"
|
"to_contract_address",
|
||||||
|
"created_at"
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_payin_hash` ON `${TABLE_NAME}` (`owner_address`, `payin_hash`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_to_network_to_contract_address_created_at` ON `${TABLE_NAME}` (`to_network`, `to_contract_address`, `created_at`)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index_express_exchange_owner_address_payout_hash",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"owner_address",
|
|
||||||
"payout_hash"
|
|
||||||
],
|
|
||||||
"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_provider_id",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"provider_id"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_provider_id` ON `${TABLE_NAME}` (`provider_id`)"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "express_onramp",
|
"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, `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`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `payout_address` TEXT NOT NULL, `status` TEXT NOT NULL, `fail_reason` TEXT, `external_tx_id` TEXT, `external_tx_url` TEXT, `payout_hash` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `from_currency_code` TEXT NOT NULL, `from_amount` TEXT NOT NULL, `from_precision` INTEGER NOT NULL, `payment_method` TEXT NOT NULL, `country_code` TEXT NOT NULL, `to_contract_address` TEXT NOT NULL, `to_network` TEXT NOT NULL, `to_decimals` INTEGER NOT NULL, `to_amount` TEXT, `to_actual_amount` TEXT, PRIMARY KEY(`tx_id`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "txId",
|
"fieldPath": "txId",
|
||||||
|
|
@ -305,48 +310,12 @@
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"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",
|
"fieldPath": "payoutAddress",
|
||||||
"columnName": "payout_address",
|
"columnName": "payout_address",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"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",
|
"fieldPath": "status",
|
||||||
"columnName": "status",
|
"columnName": "status",
|
||||||
|
|
@ -354,46 +323,24 @@
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "externalTxId",
|
"fieldPath": "failReason",
|
||||||
"columnName": "external_tx_id",
|
"columnName": "fail_reason",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "externalTxStatus",
|
"fieldPath": "externalTxId",
|
||||||
"columnName": "external_tx_status",
|
"columnName": "external_tx_id",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "externalTxUrl",
|
"fieldPath": "externalTxUrl",
|
||||||
"columnName": "external_tx_url",
|
"columnName": "external_tx_url",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "payinHash",
|
|
||||||
"columnName": "payin_hash",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payoutHash",
|
"fieldPath": "payoutHash",
|
||||||
"columnName": "payout_hash",
|
"columnName": "payout_hash",
|
||||||
"affinity": "TEXT",
|
"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",
|
"fieldPath": "createdAt",
|
||||||
|
|
@ -402,46 +349,40 @@
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "payTill",
|
"fieldPath": "updatedAt",
|
||||||
"columnName": "pay_till",
|
"columnName": "updated_at",
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "averageDuration",
|
|
||||||
"columnName": "average_duration",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "from.contractAddress",
|
|
||||||
"columnName": "from_contract_address",
|
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "from.network",
|
"fieldPath": "fromCurrencyCode",
|
||||||
"columnName": "from_network",
|
"columnName": "from_currency_code",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "from.decimals",
|
"fieldPath": "fromAmount",
|
||||||
"columnName": "from_decimals",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "from.amount",
|
|
||||||
"columnName": "from_amount",
|
"columnName": "from_amount",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "from.actualAmount",
|
"fieldPath": "fromPrecision",
|
||||||
"columnName": "from_actual_amount",
|
"columnName": "from_precision",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "paymentMethod",
|
||||||
|
"columnName": "payment_method",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "countryCode",
|
||||||
|
"columnName": "country_code",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "to.contractAddress",
|
"fieldPath": "to.contractAddress",
|
||||||
|
|
@ -464,14 +405,12 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "to.amount",
|
"fieldPath": "to.amount",
|
||||||
"columnName": "to_amount",
|
"columnName": "to_amount",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "to.actualAmount",
|
"fieldPath": "to.actualAmount",
|
||||||
"columnName": "to_actual_amount",
|
"columnName": "to_actual_amount",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -482,37 +421,18 @@
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
"name": "index_express_onramp_owner_address_to_network_created_at",
|
"name": "index_express_onramp_owner_address_to_network_to_contract_address_created_at",
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"owner_address",
|
"owner_address",
|
||||||
"to_network",
|
"to_network",
|
||||||
|
"to_contract_address",
|
||||||
"created_at"
|
"created_at"
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_to_network_created_at` ON `${TABLE_NAME}` (`owner_address`, `to_network`, `created_at`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_to_network_to_contract_address_created_at` ON `${TABLE_NAME}` (`owner_address`, `to_network`, `to_contract_address`, `created_at`)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index_express_onramp_owner_address_payout_hash",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"owner_address",
|
|
||||||
"payout_hash"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_payout_hash` ON `${TABLE_NAME}` (`owner_address`, `payout_hash`)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index_express_onramp_provider_id",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"provider_id"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_provider_id` ON `${TABLE_NAME}` (`provider_id`)"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "express_sync_state",
|
"tableName": "express_sync_state",
|
||||||
|
|
@ -539,14 +459,12 @@
|
||||||
{
|
{
|
||||||
"fieldPath": "afterCursor",
|
"fieldPath": "afterCursor",
|
||||||
"columnName": "after_cursor",
|
"columnName": "after_cursor",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "deltaCursor",
|
"fieldPath": "deltaCursor",
|
||||||
"columnName": "delta_cursor",
|
"columnName": "delta_cursor",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -555,15 +473,12 @@
|
||||||
"type",
|
"type",
|
||||||
"address"
|
"address"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '36ff7cc1634c100cadc4b04fc2eba1c9')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '442ac578743a8b624777711cf49c77e2')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +78,7 @@ interface TangemExpressApi {
|
||||||
@Header("user-id") userWalletId: String,
|
@Header("user-id") userWalletId: String,
|
||||||
@Header("refcode") refCode: String?,
|
@Header("refcode") refCode: String?,
|
||||||
@Query("txId") txId: String,
|
@Query("txId") txId: String,
|
||||||
): ApiResponse<ExchangeStatusResponse>
|
): ApiResponse<ExchangeItemResponse>
|
||||||
|
|
||||||
@POST("exchange-sent")
|
@POST("exchange-sent")
|
||||||
suspend fun exchangeSent(
|
suspend fun exchangeSent(
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,12 @@ data class ExchangeItemResponse(
|
||||||
@Json(name = "providerId")
|
@Json(name = "providerId")
|
||||||
val providerId: String,
|
val providerId: String,
|
||||||
|
|
||||||
/** Address from which the source assets were sent */
|
/**
|
||||||
|
* Address from which the `from` assets were taken for the exchange. Optional because the very first
|
||||||
|
* app versions did not send it; for newer versions it can be considered effectively mandatory.
|
||||||
|
*/
|
||||||
@Json(name = "fromAddress")
|
@Json(name = "fromAddress")
|
||||||
val fromAddress: String,
|
val fromAddress: String?,
|
||||||
|
|
||||||
/** Address to which the source assets were transferred for the exchange */
|
/** Address to which the source assets were transferred for the exchange */
|
||||||
@Json(name = "payinAddress")
|
@Json(name = "payinAddress")
|
||||||
|
|
@ -40,17 +43,17 @@ data class ExchangeItemResponse(
|
||||||
@Json(name = "rateType")
|
@Json(name = "rateType")
|
||||||
val rateType: String,
|
val rateType: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw backend status string, kept unparsed so a new value never breaks deserialization.
|
||||||
|
* Typed view: [com.tangem.domain.express.models.ExpressExchangeStatus].
|
||||||
|
*/
|
||||||
@Json(name = "status")
|
@Json(name = "status")
|
||||||
val status: Status,
|
val status: String,
|
||||||
|
|
||||||
/** External transaction ID (CEX only) */
|
/** External transaction ID (CEX only) */
|
||||||
@Json(name = "externalTxId")
|
@Json(name = "externalTxId")
|
||||||
val externalTxId: String?,
|
val externalTxId: String?,
|
||||||
|
|
||||||
/** Transaction status reported by the provider */
|
|
||||||
@Json(name = "externalTxStatus")
|
|
||||||
val externalTxStatus: String?,
|
|
||||||
|
|
||||||
/** URL to view the transaction details (CEX only) */
|
/** URL to view the transaction details (CEX only) */
|
||||||
@Json(name = "externalTxUrl")
|
@Json(name = "externalTxUrl")
|
||||||
val externalTxUrl: String?,
|
val externalTxUrl: String?,
|
||||||
|
|
@ -75,6 +78,10 @@ data class ExchangeItemResponse(
|
||||||
@Json(name = "createdAt")
|
@Json(name = "createdAt")
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
|
|
||||||
|
/** Transaction last-update timestamp in ISO-8601 format */
|
||||||
|
@Json(name = "updatedAt")
|
||||||
|
val updatedAt: String,
|
||||||
|
|
||||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||||
@Json(name = "payTill")
|
@Json(name = "payTill")
|
||||||
val payTill: String?,
|
val payTill: String?,
|
||||||
|
|
@ -107,50 +114,4 @@ data class ExchangeItemResponse(
|
||||||
@Json(name = "toActualAmount")
|
@Json(name = "toActualAmount")
|
||||||
val toActualAmount: String?,
|
val toActualAmount: String?,
|
||||||
// endregion
|
// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ data class ExpressPagination(
|
||||||
val endCursor: String?,
|
val endCursor: String?,
|
||||||
@Json(name = "startDeltaCursor")
|
@Json(name = "startDeltaCursor")
|
||||||
val startDeltaCursor: String?,
|
val startDeltaCursor: String?,
|
||||||
@Json(name = "hasNextPage")
|
@Json(name = "hasMore")
|
||||||
val hasMore: Boolean,
|
val hasMore: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ 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.OnrampHistoryDeltaResponse
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryResponse
|
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.OnrampQuoteResponse
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||||
|
|
@ -87,7 +87,7 @@ interface OnrampApi {
|
||||||
@Header("user-id") userWalletId: String,
|
@Header("user-id") userWalletId: String,
|
||||||
@Header("refcode") refCode: String?,
|
@Header("refcode") refCode: String?,
|
||||||
@Query("txId") txId: String,
|
@Query("txId") txId: String,
|
||||||
): ApiResponse<OnrampStatusResponse>
|
): ApiResponse<OnrampItemResponse>
|
||||||
|
|
||||||
@GET("history/onramp")
|
@GET("history/onramp")
|
||||||
suspend fun getHistory(
|
suspend fun getHistory(
|
||||||
|
|
|
||||||
|
|
@ -12,87 +12,49 @@ data class OnrampItemResponse(
|
||||||
@Json(name = "providerId")
|
@Json(name = "providerId")
|
||||||
val providerId: String,
|
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 */
|
/** Address that received the target assets */
|
||||||
@Json(name = "payoutAddress")
|
@Json(name = "payoutAddress")
|
||||||
val payoutAddress: String,
|
val payoutAddress: String,
|
||||||
|
|
||||||
/** Refund destination address */
|
/**
|
||||||
@Json(name = "refundAddress")
|
* Raw backend status string, kept unparsed so a new value never breaks deserialization.
|
||||||
val refundAddress: String?,
|
* Typed view: [com.tangem.domain.express.models.ExpressOnrampStatus].
|
||||||
|
*/
|
||||||
/** 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")
|
@Json(name = "status")
|
||||||
val status: Status,
|
val status: String,
|
||||||
|
|
||||||
/** External transaction ID (CEX only) */
|
/** Failure reason reported by the provider */
|
||||||
|
@Json(name = "failReason")
|
||||||
|
val failReason: String?,
|
||||||
|
|
||||||
|
/** External transaction ID reported by the provider in the webhook */
|
||||||
@Json(name = "externalTxId")
|
@Json(name = "externalTxId")
|
||||||
val externalTxId: String?,
|
val externalTxId: String?,
|
||||||
|
|
||||||
/** Transaction status reported by the provider */
|
/** URL to view the transaction details on the provider side (not provided by all providers) */
|
||||||
@Json(name = "externalTxStatus")
|
|
||||||
val externalTxStatus: String?,
|
|
||||||
|
|
||||||
/** URL to view the transaction details (CEX only) */
|
|
||||||
@Json(name = "externalTxUrl")
|
@Json(name = "externalTxUrl")
|
||||||
val externalTxUrl: String?,
|
val externalTxUrl: String?,
|
||||||
|
|
||||||
/** Blockchain hash of the pay-in transaction */
|
|
||||||
@Json(name = "payinHash")
|
|
||||||
val payinHash: String?,
|
|
||||||
|
|
||||||
/** Blockchain hash of the payout transaction */
|
/** Blockchain hash of the payout transaction */
|
||||||
@Json(name = "payoutHash")
|
@Json(name = "payoutHash")
|
||||||
val payoutHash: String?,
|
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 */
|
/** Transaction creation timestamp in ISO-8601 format */
|
||||||
@Json(name = "createdAt")
|
@Json(name = "createdAt")
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
|
|
||||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
/** Transaction last-update timestamp in ISO-8601 format */
|
||||||
@Json(name = "payTill")
|
@Json(name = "updatedAt")
|
||||||
val payTill: String?,
|
val updatedAt: String,
|
||||||
|
|
||||||
/** Average provider exchange duration in seconds */
|
|
||||||
@Json(name = "averageDuration")
|
|
||||||
val averageDuration: Long?,
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region fromAsset info
|
// region fromAsset (fiat) info
|
||||||
@Json(name = "fromContractAddress")
|
@Json(name = "fromCurrencyCode")
|
||||||
val fromContractAddress: String,
|
val fromCurrencyCode: String,
|
||||||
@Json(name = "fromNetwork")
|
|
||||||
val fromNetwork: String,
|
|
||||||
@Json(name = "fromDecimals")
|
|
||||||
val fromDecimals: Int,
|
|
||||||
@Json(name = "fromAmount")
|
@Json(name = "fromAmount")
|
||||||
val fromAmount: String,
|
val fromAmount: String,
|
||||||
|
@Json(name = "fromPrecision")
|
||||||
|
val fromPrecision: Int,
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region toAsset info
|
// region toAsset info
|
||||||
|
|
@ -102,55 +64,19 @@ data class OnrampItemResponse(
|
||||||
val toNetwork: String,
|
val toNetwork: String,
|
||||||
@Json(name = "toDecimals")
|
@Json(name = "toDecimals")
|
||||||
val toDecimals: Int,
|
val toDecimals: Int,
|
||||||
|
|
||||||
|
/** Provider-promised amount, received from the provider in the webhook */
|
||||||
@Json(name = "toAmount")
|
@Json(name = "toAmount")
|
||||||
val toAmount: String,
|
val toAmount: String?,
|
||||||
|
|
||||||
|
/** Actual amount delivered to the user, received from the provider in the webhook */
|
||||||
@Json(name = "toActualAmount")
|
@Json(name = "toActualAmount")
|
||||||
val toActualAmount: String?,
|
val toActualAmount: String?,
|
||||||
// endregion
|
// endregion
|
||||||
) {
|
|
||||||
|
|
||||||
enum class Status {
|
@Json(name = "paymentMethod")
|
||||||
|
val paymentMethod: String,
|
||||||
|
|
||||||
@Json(name = "unknown")
|
@Json(name = "countryCode")
|
||||||
UNKNOWN,
|
val countryCode: String,
|
||||||
|
)
|
||||||
@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,4 +1,4 @@
|
||||||
package com.tangem.data.txhistory.repository.converter
|
package com.tangem.datasource.local.converter
|
||||||
|
|
||||||
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||||
|
|
@ -10,7 +10,7 @@ import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEnti
|
||||||
*
|
*
|
||||||
* @param ownerAddress address the history was requested for. Stored as the query key.
|
* @param ownerAddress address the history was requested for. Stored as the query key.
|
||||||
*/
|
*/
|
||||||
internal fun ExchangeItemResponse.toEntity(ownerAddress: String): ExpressExchangeEntity {
|
fun ExchangeItemResponse.toEntity(ownerAddress: String): ExpressExchangeEntity {
|
||||||
return ExpressExchangeEntity(
|
return ExpressExchangeEntity(
|
||||||
txId = txId,
|
txId = txId,
|
||||||
ownerAddress = ownerAddress,
|
ownerAddress = ownerAddress,
|
||||||
|
|
@ -22,15 +22,15 @@ internal fun ExchangeItemResponse.toEntity(ownerAddress: String): ExpressExchang
|
||||||
refundAddress = refundAddress,
|
refundAddress = refundAddress,
|
||||||
refundExtraId = refundExtraId,
|
refundExtraId = refundExtraId,
|
||||||
rateType = rateType,
|
rateType = rateType,
|
||||||
status = status.name,
|
status = status,
|
||||||
externalTxId = externalTxId,
|
externalTxId = externalTxId,
|
||||||
externalTxStatus = externalTxStatus,
|
|
||||||
externalTxUrl = externalTxUrl,
|
externalTxUrl = externalTxUrl,
|
||||||
payinHash = payinHash,
|
payinHash = payinHash,
|
||||||
payoutHash = payoutHash,
|
payoutHash = payoutHash,
|
||||||
refundNetwork = refundNetwork,
|
refundNetwork = refundNetwork,
|
||||||
refundContractAddress = refundContractAddress,
|
refundContractAddress = refundContractAddress,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
payTill = payTill,
|
payTill = payTill,
|
||||||
averageDuration = averageDuration,
|
averageDuration = averageDuration,
|
||||||
from = ExpressExchangeEntity.AssetEmbedded(
|
from = ExpressExchangeEntity.AssetEmbedded(
|
||||||
|
|
@ -50,36 +50,22 @@ internal fun ExchangeItemResponse.toEntity(ownerAddress: String): ExpressExchang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun OnrampItemResponse.toEntity(ownerAddress: String): ExpressOnrampEntity {
|
fun OnrampItemResponse.toEntity(ownerAddress: String): ExpressOnrampEntity {
|
||||||
return ExpressOnrampEntity(
|
return ExpressOnrampEntity(
|
||||||
txId = txId,
|
txId = txId,
|
||||||
ownerAddress = ownerAddress,
|
ownerAddress = ownerAddress,
|
||||||
providerId = providerId,
|
providerId = providerId,
|
||||||
fromAddress = fromAddress,
|
|
||||||
payinAddress = payinAddress,
|
|
||||||
payinExtraId = payinExtraId,
|
|
||||||
payoutAddress = payoutAddress,
|
payoutAddress = payoutAddress,
|
||||||
refundAddress = refundAddress,
|
status = status,
|
||||||
refundExtraId = refundExtraId,
|
failReason = failReason,
|
||||||
rateType = rateType,
|
|
||||||
status = status.name,
|
|
||||||
externalTxId = externalTxId,
|
externalTxId = externalTxId,
|
||||||
externalTxStatus = externalTxStatus,
|
|
||||||
externalTxUrl = externalTxUrl,
|
externalTxUrl = externalTxUrl,
|
||||||
payinHash = payinHash,
|
|
||||||
payoutHash = payoutHash,
|
payoutHash = payoutHash,
|
||||||
refundNetwork = refundNetwork,
|
|
||||||
refundContractAddress = refundContractAddress,
|
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
payTill = payTill,
|
updatedAt = updatedAt,
|
||||||
averageDuration = averageDuration,
|
fromCurrencyCode = fromCurrencyCode,
|
||||||
from = ExpressOnrampEntity.AssetEmbedded(
|
fromAmount = fromAmount,
|
||||||
contractAddress = fromContractAddress,
|
fromPrecision = fromPrecision,
|
||||||
network = fromNetwork,
|
|
||||||
decimals = fromDecimals,
|
|
||||||
amount = fromAmount,
|
|
||||||
actualAmount = null,
|
|
||||||
),
|
|
||||||
to = ExpressOnrampEntity.AssetEmbedded(
|
to = ExpressOnrampEntity.AssetEmbedded(
|
||||||
contractAddress = toContractAddress,
|
contractAddress = toContractAddress,
|
||||||
network = toNetwork,
|
network = toNetwork,
|
||||||
|
|
@ -87,5 +73,7 @@ internal fun OnrampItemResponse.toEntity(ownerAddress: String): ExpressOnrampEnt
|
||||||
amount = toAmount,
|
amount = toAmount,
|
||||||
actualAmount = toActualAmount,
|
actualAmount = toActualAmount,
|
||||||
),
|
),
|
||||||
|
paymentMethod = paymentMethod,
|
||||||
|
countryCode = countryCode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.tangem.datasource.local.converter
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.express.models.response.ExchangeProvider
|
||||||
|
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an [ExchangeProvider] API response into its persisted [ExpressProviderEntity] representation.
|
||||||
|
*
|
||||||
|
* `type` is stored as the [com.tangem.datasource.api.express.models.response.ExchangeProviderType] name
|
||||||
|
* (DEX / CEX / DEX_BRIDGE / ONRAMP); `slippage` as a plain decimal string.
|
||||||
|
*/
|
||||||
|
fun ExchangeProvider.toEntity(): ExpressProviderEntity {
|
||||||
|
return ExpressProviderEntity(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
type = type.name,
|
||||||
|
imageLarge = imageLargeUrl,
|
||||||
|
imageSmall = imageSmallUrl,
|
||||||
|
termsOfUse = termsOfUse,
|
||||||
|
privacyPolicy = privacyPolicy,
|
||||||
|
isRecommended = isRecommended,
|
||||||
|
slippage = slippage?.toPlainString(),
|
||||||
|
isExchangeOnlyWithinSingleAddress = isExchangeOnlyWithinSingleAddress,
|
||||||
|
isExtraIdSupported = isExtraIdSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.local.txhistory.db.dao
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
|
import androidx.room.MapColumn
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||||
|
|
@ -21,56 +22,76 @@ interface ExpressHistoryDao {
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun upsertOnramps(items: List<ExpressOnrampEntity>)
|
suspend fun upsertOnramps(items: List<ExpressOnrampEntity>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All persisted providers keyed by [ExpressProviderEntity.id]
|
||||||
|
*/
|
||||||
|
@Query("SELECT * FROM express_provider")
|
||||||
|
fun getProvidersById(): Flow<Map<@MapColumn(columnName = "id") String, ExpressProviderEntity>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing swaps: the viewed currency is the swap's `from` side, so the row is stored under this
|
||||||
|
* address ([ExpressExchangeEntity.ownerAddress] == fromAddress). Join to on-chain by `payin_hash`.
|
||||||
|
*
|
||||||
|
|
||||||
|
* loading the whole table; [activeStatuses] keeps in-progress deals visible even outside the window.
|
||||||
|
*/
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT * FROM express_exchange
|
||||||
FROM express_exchange
|
|
||||||
WHERE owner_address = :ownerAddress
|
WHERE owner_address = :ownerAddress
|
||||||
|
AND from_network = :network
|
||||||
|
AND from_contract_address = :contract
|
||||||
|
AND (created_at >= :fromCreatedAtIso OR status IN (:activeStatuses))
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
fun observeExchanges(ownerAddress: String): Flow<List<ExpressExchangeEntity>>
|
fun observeOutgoingSwaps(
|
||||||
|
ownerAddress: String,
|
||||||
|
network: String,
|
||||||
|
contract: String,
|
||||||
|
fromCreatedAtIso: String,
|
||||||
|
activeStatuses: List<String>,
|
||||||
|
): Flow<List<ExpressExchangeEntity>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming swaps: the viewed currency is the swap's `to` side. Such a deal was initiated from a
|
||||||
|
* different coin, so the row is stored under that coin's `owner_address` — hence this query is
|
||||||
|
* cross-owner, matched by the `to` asset. Join to on-chain by `payout_hash`.
|
||||||
|
*/
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT * FROM express_exchange
|
||||||
FROM express_onramp
|
WHERE to_network = :network
|
||||||
WHERE owner_address = :ownerAddress
|
AND to_contract_address = :contract
|
||||||
|
AND (created_at >= :fromCreatedAtIso OR status IN (:activeStatuses))
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
fun observeOnramps(ownerAddress: String): Flow<List<ExpressOnrampEntity>>
|
fun observeIncomingSwaps(
|
||||||
|
network: String,
|
||||||
|
contract: String,
|
||||||
|
fromCreatedAtIso: String,
|
||||||
|
activeStatuses: List<String>,
|
||||||
|
): Flow<List<ExpressExchangeEntity>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Onramp is always incoming: [ExpressOnrampEntity.ownerAddress] == payoutAddress. Join by `payout_hash`.
|
||||||
|
*/
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT * FROM express_onramp
|
||||||
FROM express_exchange
|
|
||||||
WHERE owner_address = :ownerAddress
|
WHERE owner_address = :ownerAddress
|
||||||
AND payin_hash = :hash
|
AND to_network = :network
|
||||||
LIMIT 1
|
AND to_contract_address = :contract
|
||||||
|
AND (created_at >= :fromCreatedAtIso OR status IN (:activeStatuses))
|
||||||
|
ORDER BY created_at DESC
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
suspend fun findExchangeByPayinHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
fun observeIncomingOnramps(
|
||||||
|
ownerAddress: String,
|
||||||
@Query(
|
network: String,
|
||||||
"""
|
contract: String,
|
||||||
SELECT *
|
fromCreatedAtIso: String,
|
||||||
FROM express_exchange
|
activeStatuses: List<String>,
|
||||||
WHERE owner_address = :ownerAddress
|
): Flow<List<ExpressOnrampEntity>>
|
||||||
AND payout_hash = :hash
|
|
||||||
LIMIT 1
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
suspend fun findExchangeByPayoutHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
SELECT *
|
|
||||||
FROM express_onramp
|
|
||||||
WHERE owner_address = :ownerAddress
|
|
||||||
AND payout_hash = :hash
|
|
||||||
LIMIT 1
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
suspend fun findOnrampByPayoutHash(ownerAddress: String, hash: String): ExpressOnrampEntity?
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,10 +10,11 @@ import androidx.room.*
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "express_exchange",
|
tableName = "express_exchange",
|
||||||
indices = [
|
indices = [
|
||||||
Index(value = ["owner_address", "from_network", "created_at"]),
|
// Outgoing swaps lookup (observeOutgoingSwaps): owner + from-asset equality, created_at range/sort.
|
||||||
Index(value = ["owner_address", "payin_hash"]),
|
Index(value = ["owner_address", "from_network", "from_contract_address", "created_at"]),
|
||||||
Index(value = ["owner_address", "payout_hash"]),
|
// Incoming (cross-owner) swaps lookup (observeIncomingSwaps): to-asset equality, created_at range/sort.
|
||||||
Index(value = ["provider_id"]),
|
// No owner filter here, so to_contract_address in the index is what keeps a popular to-network selective.
|
||||||
|
Index(value = ["to_network", "to_contract_address", "created_at"]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
data class ExpressExchangeEntity(
|
data class ExpressExchangeEntity(
|
||||||
|
|
@ -31,9 +32,12 @@ data class ExpressExchangeEntity(
|
||||||
@ColumnInfo(name = "provider_id")
|
@ColumnInfo(name = "provider_id")
|
||||||
val providerId: String,
|
val providerId: String,
|
||||||
|
|
||||||
/** Address from which the source assets were sent */
|
/**
|
||||||
|
* Address from which the `from` assets were taken for the exchange. Optional because the very first
|
||||||
|
* app versions did not send it; for newer versions it can be considered effectively mandatory.
|
||||||
|
*/
|
||||||
@ColumnInfo(name = "from_address")
|
@ColumnInfo(name = "from_address")
|
||||||
val fromAddress: String,
|
val fromAddress: String?,
|
||||||
|
|
||||||
/** Address to which the source assets were transferred for the exchange */
|
/** Address to which the source assets were transferred for the exchange */
|
||||||
@ColumnInfo(name = "payin_address")
|
@ColumnInfo(name = "payin_address")
|
||||||
|
|
@ -62,20 +66,8 @@ data class ExpressExchangeEntity(
|
||||||
val rateType: String,
|
val rateType: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unknown
|
* Raw backend status string, persisted as-is (kept unparsed so a new value never breaks anything).
|
||||||
* exchange-tx-sent
|
* Typed view: [com.tangem.domain.express.models.ExpressExchangeStatus].
|
||||||
* waiting
|
|
||||||
* waiting-tx-hash
|
|
||||||
* expired
|
|
||||||
* confirming
|
|
||||||
* exchanging
|
|
||||||
* sending
|
|
||||||
* finished
|
|
||||||
* failed
|
|
||||||
* tx-failed
|
|
||||||
* refunded
|
|
||||||
* verifying
|
|
||||||
* paused
|
|
||||||
*/
|
*/
|
||||||
@ColumnInfo(name = "status")
|
@ColumnInfo(name = "status")
|
||||||
val status: String,
|
val status: String,
|
||||||
|
|
@ -84,10 +76,6 @@ data class ExpressExchangeEntity(
|
||||||
@ColumnInfo(name = "external_tx_id")
|
@ColumnInfo(name = "external_tx_id")
|
||||||
val externalTxId: String?,
|
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) */
|
/** URL to view the transaction details (CEX only) */
|
||||||
@ColumnInfo(name = "external_tx_url")
|
@ColumnInfo(name = "external_tx_url")
|
||||||
val externalTxUrl: String?,
|
val externalTxUrl: String?,
|
||||||
|
|
@ -112,6 +100,10 @@ data class ExpressExchangeEntity(
|
||||||
@ColumnInfo(name = "created_at")
|
@ColumnInfo(name = "created_at")
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
|
|
||||||
|
/** Transaction last-update timestamp in ISO-8601 format */
|
||||||
|
@ColumnInfo(name = "updated_at")
|
||||||
|
val updatedAt: String,
|
||||||
|
|
||||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
/** Pay-in expiration timestamp in ISO-8601 format */
|
||||||
@ColumnInfo(name = "pay_till")
|
@ColumnInfo(name = "pay_till")
|
||||||
val payTill: String?,
|
val payTill: String?,
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ import androidx.room.*
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "express_onramp",
|
tableName = "express_onramp",
|
||||||
indices = [
|
indices = [
|
||||||
Index(value = ["owner_address", "to_network", "created_at"]),
|
// Incoming onramp lookup (observeIncomingOnramps): owner + to-asset equality, created_at range/sort.
|
||||||
Index(value = ["owner_address", "payout_hash"]),
|
Index(value = ["owner_address", "to_network", "to_contract_address", "created_at"]),
|
||||||
Index(value = ["provider_id"]),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
data class ExpressOnrampEntity(
|
data class ExpressOnrampEntity(
|
||||||
|
|
@ -30,100 +29,61 @@ data class ExpressOnrampEntity(
|
||||||
@ColumnInfo(name = "provider_id")
|
@ColumnInfo(name = "provider_id")
|
||||||
val providerId: String,
|
val providerId: String,
|
||||||
|
|
||||||
/** Address from which the source assets were taken for the exchange */
|
|
||||||
@ColumnInfo(name = "from_address")
|
|
||||||
val fromAddress: String,
|
|
||||||
|
|
||||||
/** Address to which the assets were transferred for the exchange */
|
|
||||||
@ColumnInfo(name = "payin_address")
|
|
||||||
val payinAddress: String,
|
|
||||||
|
|
||||||
/** Extra ID used for the pay-in transaction */
|
|
||||||
@ColumnInfo(name = "payin_extra_id")
|
|
||||||
val payinExtraId: String?,
|
|
||||||
|
|
||||||
/** Address that received the target assets */
|
/** Address that received the target assets */
|
||||||
@ColumnInfo(name = "payout_address")
|
@ColumnInfo(name = "payout_address")
|
||||||
val payoutAddress: String,
|
val payoutAddress: String,
|
||||||
|
|
||||||
/** Refund destination address */
|
|
||||||
@ColumnInfo(name = "refund_address")
|
|
||||||
val refundAddress: String?,
|
|
||||||
|
|
||||||
/** Extra ID used for refunds */
|
|
||||||
@ColumnInfo(name = "refund_extra_id")
|
|
||||||
val refundExtraId: String?,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fixed / float
|
* Raw backend status string, persisted as-is (kept unparsed so a new value never breaks anything).
|
||||||
*/
|
* Typed view: [com.tangem.domain.express.models.ExpressOnrampStatus].
|
||||||
@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")
|
@ColumnInfo(name = "status")
|
||||||
val status: String,
|
val status: String,
|
||||||
|
|
||||||
/** External transaction ID (CEX only) */
|
/** Failure reason reported by the provider */
|
||||||
|
@ColumnInfo(name = "fail_reason")
|
||||||
|
val failReason: String?,
|
||||||
|
|
||||||
|
/** External transaction ID reported by the provider in the webhook */
|
||||||
@ColumnInfo(name = "external_tx_id")
|
@ColumnInfo(name = "external_tx_id")
|
||||||
val externalTxId: String?,
|
val externalTxId: String?,
|
||||||
|
|
||||||
/** Transaction status reported by the provider */
|
/** URL to view the transaction details on the provider side (not provided by all providers) */
|
||||||
@ColumnInfo(name = "external_tx_status")
|
|
||||||
val externalTxStatus: String?,
|
|
||||||
|
|
||||||
/** URL to view the transaction details (CEX only) */
|
|
||||||
@ColumnInfo(name = "external_tx_url")
|
@ColumnInfo(name = "external_tx_url")
|
||||||
val externalTxUrl: String?,
|
val externalTxUrl: String?,
|
||||||
|
|
||||||
/** Blockchain hash of the pay-in transaction */
|
|
||||||
@ColumnInfo(name = "payin_hash")
|
|
||||||
val payinHash: String?,
|
|
||||||
|
|
||||||
/** Blockchain hash of the payout transaction */
|
/** Blockchain hash of the payout transaction */
|
||||||
@ColumnInfo(name = "payout_hash")
|
@ColumnInfo(name = "payout_hash")
|
||||||
val payoutHash: String?,
|
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 */
|
/** Transaction creation timestamp in ISO-8601 format */
|
||||||
@ColumnInfo(name = "created_at")
|
@ColumnInfo(name = "created_at")
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
|
|
||||||
/** Pay-in expiration timestamp in ISO-8601 format */
|
/** Transaction last-update timestamp in ISO-8601 format */
|
||||||
@ColumnInfo(name = "pay_till")
|
@ColumnInfo(name = "updated_at")
|
||||||
val payTill: String?,
|
val updatedAt: String,
|
||||||
|
|
||||||
/** Average provider exchange duration in seconds */
|
/** Fiat currency code of the source funds */
|
||||||
@ColumnInfo(name = "average_duration")
|
@ColumnInfo(name = "from_currency_code")
|
||||||
val averageDuration: Long?,
|
val fromCurrencyCode: String,
|
||||||
|
|
||||||
@Embedded(prefix = "from_")
|
/** Fiat amount of the source funds */
|
||||||
val from: AssetEmbedded,
|
@ColumnInfo(name = "from_amount")
|
||||||
|
val fromAmount: String,
|
||||||
|
|
||||||
|
/** Number of decimal places of the source fiat currency */
|
||||||
|
@ColumnInfo(name = "from_precision")
|
||||||
|
val fromPrecision: Int,
|
||||||
|
|
||||||
@Embedded(prefix = "to_")
|
@Embedded(prefix = "to_")
|
||||||
val to: AssetEmbedded,
|
val to: AssetEmbedded,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "payment_method")
|
||||||
|
val paymentMethod: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "country_code")
|
||||||
|
val countryCode: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class AssetEmbedded(
|
data class AssetEmbedded(
|
||||||
|
|
@ -137,10 +97,11 @@ data class ExpressOnrampEntity(
|
||||||
@ColumnInfo(name = "decimals")
|
@ColumnInfo(name = "decimals")
|
||||||
val decimals: Int,
|
val decimals: Int,
|
||||||
|
|
||||||
|
/** Provider-promised amount. Present only if the provider reported it */
|
||||||
@ColumnInfo(name = "amount")
|
@ColumnInfo(name = "amount")
|
||||||
val amount: String,
|
val amount: String?,
|
||||||
|
|
||||||
/** Actual provider-confirmed amount. Present only for the [ExpressOnrampEntity.to] asset */
|
/** Actual provider-confirmed amount delivered to the user */
|
||||||
@ColumnInfo(name = "actual_amount")
|
@ColumnInfo(name = "actual_amount")
|
||||||
val actualAmount: String?,
|
val actualAmount: String?,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(
|
/**
|
||||||
tableName = "express_provider",
|
* Persisted representation of an express provider.
|
||||||
)
|
*
|
||||||
|
* Mirrors [com.tangem.datasource.api.express.models.response.ExchangeProvider]. Mapped into
|
||||||
|
* [com.tangem.domain.express.models.ExpressProvider] when read back.
|
||||||
|
*/
|
||||||
|
@Entity(tableName = "express_provider")
|
||||||
data class ExpressProviderEntity(
|
data class ExpressProviderEntity(
|
||||||
|
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
|
|
@ -16,9 +20,34 @@ data class ExpressProviderEntity(
|
||||||
@ColumnInfo(name = "name")
|
@ColumnInfo(name = "name")
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "icon_url")
|
/** Raw provider type (`dex` / `cex` / `dex-bridge` / `onramp`). Typed view: ExpressProviderType. */
|
||||||
val iconUrl: String,
|
@ColumnInfo(name = "type")
|
||||||
|
val type: String,
|
||||||
|
|
||||||
@ColumnInfo(name = "provider_url")
|
/** Large logo image URL. */
|
||||||
val providerUrl: String,
|
@ColumnInfo(name = "image_large")
|
||||||
|
val imageLarge: String,
|
||||||
|
|
||||||
|
/** Small logo image URL. */
|
||||||
|
@ColumnInfo(name = "image_small")
|
||||||
|
val imageSmall: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "terms_of_use")
|
||||||
|
val termsOfUse: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "privacy_policy")
|
||||||
|
val privacyPolicy: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_recommended")
|
||||||
|
val isRecommended: Boolean,
|
||||||
|
|
||||||
|
/** Raw decimal string (BigDecimal) or `null`. */
|
||||||
|
@ColumnInfo(name = "slippage")
|
||||||
|
val slippage: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_exchange_only_within_single_address")
|
||||||
|
val isExchangeOnlyWithinSingleAddress: Boolean,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_extra_id_supported")
|
||||||
|
val isExtraIdSupported: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
package com.tangem.datasource.local.converter
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
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
|
||||||
|
Truth.assertThat(entity.txId).isEqualTo(item.txId)
|
||||||
|
Truth.assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
|
||||||
|
Truth.assertThat(entity.providerId).isEqualTo(item.providerId)
|
||||||
|
Truth.assertThat(entity.fromAddress).isEqualTo(item.fromAddress)
|
||||||
|
Truth.assertThat(entity.payinAddress).isEqualTo(item.payinAddress)
|
||||||
|
Truth.assertThat(entity.payinExtraId).isEqualTo(item.payinExtraId)
|
||||||
|
Truth.assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
|
||||||
|
Truth.assertThat(entity.refundAddress).isEqualTo(item.refundAddress)
|
||||||
|
Truth.assertThat(entity.refundExtraId).isEqualTo(item.refundExtraId)
|
||||||
|
Truth.assertThat(entity.rateType).isEqualTo(item.rateType)
|
||||||
|
Truth.assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
|
||||||
|
Truth.assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
|
||||||
|
Truth.assertThat(entity.payinHash).isEqualTo(item.payinHash)
|
||||||
|
Truth.assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
|
||||||
|
Truth.assertThat(entity.refundNetwork).isEqualTo(item.refundNetwork)
|
||||||
|
Truth.assertThat(entity.refundContractAddress).isEqualTo(item.refundContractAddress)
|
||||||
|
Truth.assertThat(entity.createdAt).isEqualTo(item.createdAt)
|
||||||
|
Truth.assertThat(entity.updatedAt).isEqualTo(item.updatedAt)
|
||||||
|
Truth.assertThat(entity.payTill).isEqualTo(item.payTill)
|
||||||
|
Truth.assertThat(entity.averageDuration).isEqualTo(item.averageDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN exchange item WHEN toEntity THEN status is stored as raw string`() {
|
||||||
|
// GIVEN
|
||||||
|
val item = createExchangeItem(status = "finished")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
Truth.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
|
||||||
|
Truth.assertThat(entity.from.contractAddress).isEqualTo(item.fromContractAddress)
|
||||||
|
Truth.assertThat(entity.from.network).isEqualTo(item.fromNetwork)
|
||||||
|
Truth.assertThat(entity.from.decimals).isEqualTo(item.fromDecimals)
|
||||||
|
Truth.assertThat(entity.from.amount).isEqualTo(item.fromAmount)
|
||||||
|
// `from` asset never carries an actual amount
|
||||||
|
Truth.assertThat(entity.from.actualAmount).isNull()
|
||||||
|
|
||||||
|
Truth.assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
|
||||||
|
Truth.assertThat(entity.to.network).isEqualTo(item.toNetwork)
|
||||||
|
Truth.assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
|
||||||
|
Truth.assertThat(entity.to.amount).isEqualTo(item.toAmount)
|
||||||
|
Truth.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,
|
||||||
|
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
|
||||||
|
Truth.assertThat(entity.payinExtraId).isNull()
|
||||||
|
Truth.assertThat(entity.refundAddress).isNull()
|
||||||
|
Truth.assertThat(entity.refundExtraId).isNull()
|
||||||
|
Truth.assertThat(entity.externalTxId).isNull()
|
||||||
|
Truth.assertThat(entity.externalTxUrl).isNull()
|
||||||
|
Truth.assertThat(entity.payinHash).isNull()
|
||||||
|
Truth.assertThat(entity.payoutHash).isNull()
|
||||||
|
Truth.assertThat(entity.refundNetwork).isNull()
|
||||||
|
Truth.assertThat(entity.refundContractAddress).isNull()
|
||||||
|
Truth.assertThat(entity.payTill).isNull()
|
||||||
|
Truth.assertThat(entity.averageDuration).isNull()
|
||||||
|
Truth.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
|
||||||
|
Truth.assertThat(entity.txId).isEqualTo(item.txId)
|
||||||
|
Truth.assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
|
||||||
|
Truth.assertThat(entity.providerId).isEqualTo(item.providerId)
|
||||||
|
Truth.assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
|
||||||
|
Truth.assertThat(entity.failReason).isEqualTo(item.failReason)
|
||||||
|
Truth.assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
|
||||||
|
Truth.assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
|
||||||
|
Truth.assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
|
||||||
|
Truth.assertThat(entity.createdAt).isEqualTo(item.createdAt)
|
||||||
|
Truth.assertThat(entity.updatedAt).isEqualTo(item.updatedAt)
|
||||||
|
Truth.assertThat(entity.fromCurrencyCode).isEqualTo(item.fromCurrencyCode)
|
||||||
|
Truth.assertThat(entity.fromAmount).isEqualTo(item.fromAmount)
|
||||||
|
Truth.assertThat(entity.fromPrecision).isEqualTo(item.fromPrecision)
|
||||||
|
Truth.assertThat(entity.paymentMethod).isEqualTo(item.paymentMethod)
|
||||||
|
Truth.assertThat(entity.countryCode).isEqualTo(item.countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN onramp item WHEN toEntity THEN status is stored as raw string`() {
|
||||||
|
// GIVEN
|
||||||
|
val item = createOnrampItem(status = "waiting-for-payment")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
Truth.assertThat(entity.status).isEqualTo("waiting-for-payment")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN onramp item WHEN toEntity THEN to asset is mapped`() {
|
||||||
|
// GIVEN
|
||||||
|
val item = createOnrampItem()
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
Truth.assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
|
||||||
|
Truth.assertThat(entity.to.network).isEqualTo(item.toNetwork)
|
||||||
|
Truth.assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
|
||||||
|
Truth.assertThat(entity.to.amount).isEqualTo(item.toAmount)
|
||||||
|
Truth.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(
|
||||||
|
failReason = null,
|
||||||
|
externalTxId = null,
|
||||||
|
externalTxUrl = null,
|
||||||
|
payoutHash = null,
|
||||||
|
toAmount = null,
|
||||||
|
toActualAmount = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
Truth.assertThat(entity.failReason).isNull()
|
||||||
|
Truth.assertThat(entity.externalTxId).isNull()
|
||||||
|
Truth.assertThat(entity.externalTxUrl).isNull()
|
||||||
|
Truth.assertThat(entity.payoutHash).isNull()
|
||||||
|
Truth.assertThat(entity.to.amount).isNull()
|
||||||
|
Truth.assertThat(entity.to.actualAmount).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createExchangeItem(
|
||||||
|
status: String = "waiting",
|
||||||
|
payinExtraId: String? = "payin-extra",
|
||||||
|
refundAddress: String? = "refund-address",
|
||||||
|
refundExtraId: String? = "refund-extra",
|
||||||
|
externalTxId: String? = "external-tx-id",
|
||||||
|
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,
|
||||||
|
externalTxUrl = externalTxUrl,
|
||||||
|
payinHash = payinHash,
|
||||||
|
payoutHash = payoutHash,
|
||||||
|
refundNetwork = refundNetwork,
|
||||||
|
refundContractAddress = refundContractAddress,
|
||||||
|
createdAt = "2026-06-01T00:00:00Z",
|
||||||
|
updatedAt = "2026-06-01T00:05: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: String = "waiting-for-payment",
|
||||||
|
failReason: String? = "fail-reason",
|
||||||
|
externalTxId: String? = "external-tx-id",
|
||||||
|
externalTxUrl: String? = "https://provider.example/tx",
|
||||||
|
payoutHash: String? = "payout-hash",
|
||||||
|
toAmount: String? = "0.001",
|
||||||
|
toActualAmount: String? = "0.00099",
|
||||||
|
) = OnrampItemResponse(
|
||||||
|
txId = "onramp-tx-1",
|
||||||
|
providerId = "mercuryo",
|
||||||
|
payoutAddress = "0xpayout",
|
||||||
|
status = status,
|
||||||
|
failReason = failReason,
|
||||||
|
externalTxId = externalTxId,
|
||||||
|
externalTxUrl = externalTxUrl,
|
||||||
|
payoutHash = payoutHash,
|
||||||
|
createdAt = "2026-06-01T00:00:00Z",
|
||||||
|
updatedAt = "2026-06-01T00:05:00Z",
|
||||||
|
fromCurrencyCode = "USD",
|
||||||
|
fromAmount = "100.0",
|
||||||
|
fromPrecision = 2,
|
||||||
|
toContractAddress = "0xtoContract",
|
||||||
|
toNetwork = "bitcoin",
|
||||||
|
toDecimals = 8,
|
||||||
|
toAmount = toAmount,
|
||||||
|
toActualAmount = toActualAmount,
|
||||||
|
paymentMethod = "card",
|
||||||
|
countryCode = "US",
|
||||||
|
)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val OWNER_ADDRESS = "0xowner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,9 @@ import com.tangem.data.express.converter.ExpressProviderConverter
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||||
|
import com.tangem.datasource.local.converter.toEntity
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.express.ExpressRepository
|
import com.tangem.domain.express.ExpressRepository
|
||||||
import com.tangem.domain.express.models.ExpressProvider
|
import com.tangem.domain.express.models.ExpressProvider
|
||||||
import com.tangem.domain.express.models.ExpressProviderType
|
import com.tangem.domain.express.models.ExpressProviderType
|
||||||
|
|
@ -16,6 +18,7 @@ import com.tangem.utils.logging.TangemLogger
|
||||||
|
|
||||||
internal class DefaultExpressRepository(
|
internal class DefaultExpressRepository(
|
||||||
private val tangemExpressApi: TangemExpressApi,
|
private val tangemExpressApi: TangemExpressApi,
|
||||||
|
private val expressHistoryDao: ExpressHistoryDao,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : ExpressRepository {
|
) : ExpressRepository {
|
||||||
|
|
@ -26,13 +29,17 @@ internal class DefaultExpressRepository(
|
||||||
): List<ExpressProvider> = with(dispatchers.io) {
|
): List<ExpressProvider> = with(dispatchers.io) {
|
||||||
safeApiCall(
|
safeApiCall(
|
||||||
call = {
|
call = {
|
||||||
tangemExpressApi.getProviders(
|
val providers = tangemExpressApi.getProviders(
|
||||||
userWalletId = userWallet.walletId.stringValue,
|
userWalletId = userWallet.walletId.stringValue,
|
||||||
refCode = ExpressUtils.getRefCode(
|
refCode = ExpressUtils.getRefCode(
|
||||||
userWallet = userWallet,
|
userWallet = userWallet,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
),
|
),
|
||||||
).getOrThrow().map(ExpressProviderConverter()::convert)
|
).getOrThrow()
|
||||||
|
|
||||||
|
expressHistoryDao.upsertProviders(providers.map { it.toEntity() })
|
||||||
|
|
||||||
|
providers.map(ExpressProviderConverter()::convert)
|
||||||
.filterIf(filterProviderTypes.isNotEmpty()) { it.type in filterProviderTypes }
|
.filterIf(filterProviderTypes.isNotEmpty()) { it.type in filterProviderTypes }
|
||||||
},
|
},
|
||||||
onError = { error ->
|
onError = { error ->
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||||
import com.tangem.datasource.di.NetworkMoshi
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.express.ExpressErrorResolver
|
import com.tangem.domain.express.ExpressErrorResolver
|
||||||
import com.tangem.domain.express.ExpressRepository
|
import com.tangem.domain.express.ExpressRepository
|
||||||
import com.tangem.domain.express.ExpressServiceFetcher
|
import com.tangem.domain.express.ExpressServiceFetcher
|
||||||
|
|
@ -36,11 +37,13 @@ internal object ExpressDataModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideExpressRepository(
|
fun provideExpressRepository(
|
||||||
tangemExpressApi: TangemExpressApi,
|
tangemExpressApi: TangemExpressApi,
|
||||||
|
expressHistoryDao: ExpressHistoryDao,
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): ExpressRepository {
|
): ExpressRepository {
|
||||||
return DefaultExpressRepository(
|
return DefaultExpressRepository(
|
||||||
tangemExpressApi = tangemExpressApi,
|
tangemExpressApi = tangemExpressApi,
|
||||||
|
expressHistoryDao = expressHistoryDao,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||||
|
import com.tangem.datasource.local.converter.toEntity
|
||||||
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
||||||
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
||||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||||
|
|
@ -35,6 +36,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getObject
|
import com.tangem.datasource.local.preferences.utils.getObject
|
||||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.express.models.ExpressAsset
|
import com.tangem.domain.express.models.ExpressAsset
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
|
@ -72,12 +74,13 @@ internal class DefaultOnrampRepository(
|
||||||
private val currenciesStore: OnrampCurrenciesStore,
|
private val currenciesStore: OnrampCurrenciesStore,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||||
|
private val expressHistoryDao: ExpressHistoryDao,
|
||||||
moshi: Moshi,
|
moshi: Moshi,
|
||||||
) : OnrampRepository {
|
) : OnrampRepository {
|
||||||
|
|
||||||
private val currencyConverter = CurrencyConverter()
|
private val currencyConverter = CurrencyConverter()
|
||||||
private val countryConverter = CountryConverter(currencyConverter)
|
private val countryConverter = CountryConverter(currencyConverter)
|
||||||
private val statusConverter = StatusConverter()
|
private val statusConverter = StatusConverter(moshi)
|
||||||
private val paymentMethodsConverter = PaymentMethodConverter()
|
private val paymentMethodsConverter = PaymentMethodConverter()
|
||||||
private val onrampDataAdapter = moshi.adapter(OnrampDataJson::class.java)
|
private val onrampDataAdapter = moshi.adapter(OnrampDataJson::class.java)
|
||||||
private val onrampErrorAdapter = moshi.adapter(ExpressErrorResponse::class.java)
|
private val onrampErrorAdapter = moshi.adapter(ExpressErrorResponse::class.java)
|
||||||
|
|
@ -162,6 +165,8 @@ internal class DefaultOnrampRepository(
|
||||||
)
|
)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
|
|
||||||
|
expressHistoryDao.upsertOnramps(listOf(response.toEntity(ownerAddress = response.payoutAddress)))
|
||||||
|
|
||||||
statusConverter.convert(response)
|
statusConverter.convert(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
package com.tangem.data.onramp.converters
|
package com.tangem.data.onramp.converters
|
||||||
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.Status
|
||||||
import com.tangem.domain.onramp.model.OnrampStatus
|
import com.tangem.domain.onramp.model.OnrampStatus
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
internal class StatusConverter : Converter<OnrampStatusResponse, OnrampStatus> {
|
internal class StatusConverter(moshi: Moshi) : Converter<OnrampItemResponse, OnrampStatus> {
|
||||||
override fun convert(value: OnrampStatusResponse): OnrampStatus {
|
|
||||||
|
private val responseStatusAdapter = moshi.adapter(Status::class.java)
|
||||||
|
|
||||||
|
override fun convert(value: OnrampItemResponse): OnrampStatus {
|
||||||
return OnrampStatus(
|
return OnrampStatus(
|
||||||
txId = value.txId,
|
txId = value.txId,
|
||||||
providerId = value.providerId,
|
providerId = value.providerId,
|
||||||
payoutAddress = value.payoutAddress,
|
payoutAddress = value.payoutAddress,
|
||||||
status = OnrampStatus.Status.valueOf(value.status.name),
|
status = OnrampStatus.Status.valueOf(value.status.toResponseStatus().name),
|
||||||
failReason = value.failReason,
|
failReason = value.failReason,
|
||||||
externalTxId = value.externalTxId,
|
externalTxId = value.externalTxId,
|
||||||
externalTxUrl = value.externalTxUrl,
|
externalTxUrl = value.externalTxUrl,
|
||||||
|
|
@ -20,11 +25,14 @@ internal class StatusConverter : Converter<OnrampStatusResponse, OnrampStatus> {
|
||||||
fromAmount = value.fromAmount,
|
fromAmount = value.fromAmount,
|
||||||
toContractAddress = value.toContractAddress,
|
toContractAddress = value.toContractAddress,
|
||||||
toNetwork = value.toNetwork,
|
toNetwork = value.toNetwork,
|
||||||
toDecimals = value.toDecimals,
|
toDecimals = value.toDecimals.toString(),
|
||||||
toAmount = value.toAmount,
|
toAmount = value.toAmount,
|
||||||
toActualAmount = value.toActualAmount,
|
toActualAmount = value.toActualAmount,
|
||||||
paymentMethod = value.paymentMethod,
|
paymentMethod = value.paymentMethod,
|
||||||
countryCode = value.countryCode,
|
countryCode = value.countryCode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toResponseStatus(): Status = responseStatusAdapter.fromJsonValue(this)
|
||||||
|
?: error("Unknown onramp status: $this")
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsSto
|
||||||
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
||||||
import com.tangem.datasource.local.onramp.country.OnrampCurrentCountryByIPStore
|
import com.tangem.datasource.local.onramp.country.OnrampCurrentCountryByIPStore
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
import com.tangem.domain.onramp.repositories.*
|
import com.tangem.domain.onramp.repositories.*
|
||||||
|
|
@ -56,6 +57,7 @@ internal object OnrampDataModule {
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
dataSignatureVerifier: DataSignatureVerifier,
|
dataSignatureVerifier: DataSignatureVerifier,
|
||||||
onrampCurrentCountryByIPStore: OnrampCurrentCountryByIPStore,
|
onrampCurrentCountryByIPStore: OnrampCurrentCountryByIPStore,
|
||||||
|
expressHistoryDao: ExpressHistoryDao,
|
||||||
@NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
): OnrampRepository {
|
): OnrampRepository {
|
||||||
return DefaultOnrampRepository(
|
return DefaultOnrampRepository(
|
||||||
|
|
@ -71,6 +73,7 @@ internal object OnrampDataModule {
|
||||||
countriesStore = countriesStore,
|
countriesStore = countriesStore,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
dataSignatureVerifier = dataSignatureVerifier,
|
dataSignatureVerifier = dataSignatureVerifier,
|
||||||
|
expressHistoryDao = expressHistoryDao,
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.tangem.data.txhistory.repository
|
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.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
import com.tangem.datasource.api.express.models.response.ExchangeHistoryDeltaResponse
|
import com.tangem.datasource.api.express.models.response.ExchangeHistoryDeltaResponse
|
||||||
|
|
@ -12,6 +11,7 @@ import com.tangem.datasource.api.onramp.OnrampApi
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampHistoryDeltaResponse
|
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.OnrampHistoryResponse
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
|
||||||
|
import com.tangem.datasource.local.converter.toEntity
|
||||||
import com.tangem.datasource.local.txhistory.db.dao.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.dao.ExpressSyncStateDao
|
||||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateEntity
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.data.txhistory.repository
|
package com.tangem.data.txhistory.repository
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.data.txhistory.repository.converter.toEntity
|
import com.tangem.datasource.local.converter.toEntity
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
|
|
@ -305,15 +305,15 @@ internal class ExpressHistoryRepositoryTest {
|
||||||
refundAddress = null,
|
refundAddress = null,
|
||||||
refundExtraId = null,
|
refundExtraId = null,
|
||||||
rateType = "float",
|
rateType = "float",
|
||||||
status = ExchangeItemResponse.Status.FINISHED,
|
status = "finished",
|
||||||
externalTxId = null,
|
externalTxId = null,
|
||||||
externalTxStatus = null,
|
|
||||||
externalTxUrl = null,
|
externalTxUrl = null,
|
||||||
payinHash = "payin-hash",
|
payinHash = "payin-hash",
|
||||||
payoutHash = "payout-hash",
|
payoutHash = "payout-hash",
|
||||||
refundNetwork = null,
|
refundNetwork = null,
|
||||||
refundContractAddress = null,
|
refundContractAddress = null,
|
||||||
createdAt = "2026-06-01T00:00:00Z",
|
createdAt = "2026-06-01T00:00:00Z",
|
||||||
|
updatedAt = "2026-06-01T00:05:00Z",
|
||||||
payTill = null,
|
payTill = null,
|
||||||
averageDuration = null,
|
averageDuration = null,
|
||||||
fromContractAddress = "0xfromContract",
|
fromContractAddress = "0xfromContract",
|
||||||
|
|
@ -330,33 +330,24 @@ internal class ExpressHistoryRepositoryTest {
|
||||||
private fun createOnrampItem(txId: String = "onramp-tx-1") = OnrampItemResponse(
|
private fun createOnrampItem(txId: String = "onramp-tx-1") = OnrampItemResponse(
|
||||||
txId = txId,
|
txId = txId,
|
||||||
providerId = "mercuryo",
|
providerId = "mercuryo",
|
||||||
fromAddress = "0xfrom",
|
|
||||||
payinAddress = "0xpayin",
|
|
||||||
payinExtraId = null,
|
|
||||||
payoutAddress = "0xpayout",
|
payoutAddress = "0xpayout",
|
||||||
refundAddress = null,
|
status = "finished",
|
||||||
refundExtraId = null,
|
failReason = null,
|
||||||
rateType = "fixed",
|
|
||||||
status = OnrampItemResponse.Status.FINISHED,
|
|
||||||
externalTxId = null,
|
externalTxId = null,
|
||||||
externalTxStatus = null,
|
|
||||||
externalTxUrl = null,
|
externalTxUrl = null,
|
||||||
payinHash = "payin-hash",
|
|
||||||
payoutHash = "payout-hash",
|
payoutHash = "payout-hash",
|
||||||
refundNetwork = null,
|
|
||||||
refundContractAddress = null,
|
|
||||||
createdAt = "2026-06-01T00:00:00Z",
|
createdAt = "2026-06-01T00:00:00Z",
|
||||||
payTill = null,
|
updatedAt = "2026-06-01T00:05:00Z",
|
||||||
averageDuration = null,
|
fromCurrencyCode = "USD",
|
||||||
fromContractAddress = "0xfromContract",
|
|
||||||
fromNetwork = "usd",
|
|
||||||
fromDecimals = 2,
|
|
||||||
fromAmount = "100.0",
|
fromAmount = "100.0",
|
||||||
|
fromPrecision = 2,
|
||||||
toContractAddress = "0xtoContract",
|
toContractAddress = "0xtoContract",
|
||||||
toNetwork = "bitcoin",
|
toNetwork = "bitcoin",
|
||||||
toDecimals = 8,
|
toDecimals = 8,
|
||||||
toAmount = "0.001",
|
toAmount = "0.001",
|
||||||
toActualAmount = "0.99",
|
toActualAmount = "0.99",
|
||||||
|
paymentMethod = "card",
|
||||||
|
countryCode = "US",
|
||||||
)
|
)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
|
||||||
|
|
@ -1,316 +0,0 @@
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,10 +18,12 @@ import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
|
||||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||||
|
import com.tangem.datasource.local.converter.toEntity
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.express.models.ExpressOperationType
|
import com.tangem.domain.express.models.ExpressOperationType
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
|
@ -52,13 +54,14 @@ internal class DefaultSwapRepository(
|
||||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val rampStateManager: RampStateManager,
|
private val rampStateManager: RampStateManager,
|
||||||
|
private val expressHistoryDao: ExpressHistoryDao,
|
||||||
moshi: Moshi,
|
moshi: Moshi,
|
||||||
) : SwapRepository {
|
) : SwapRepository {
|
||||||
|
|
||||||
private val expressDataConverter = ExpressDataConverter()
|
private val expressDataConverter = ExpressDataConverter()
|
||||||
private val leastTokenInfoConverter = LeastTokenInfoConverter()
|
private val leastTokenInfoConverter = LeastTokenInfoConverter()
|
||||||
private val swapPairInfoConverter = SwapPairInfoConverter()
|
private val swapPairInfoConverter = SwapPairInfoConverter()
|
||||||
private val exchangeStatusConverter = ExchangeStatusConverter()
|
private val exchangeStatusConverter = ExchangeStatusConverter(moshi)
|
||||||
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
||||||
|
|
||||||
override suspend fun getPairs(
|
override suspend fun getPairs(
|
||||||
|
|
@ -238,8 +241,7 @@ internal class DefaultSwapRepository(
|
||||||
either {
|
either {
|
||||||
catch(
|
catch(
|
||||||
block = {
|
block = {
|
||||||
exchangeStatusConverter.convert(
|
val response = tangemExpressApi
|
||||||
tangemExpressApi
|
|
||||||
.getExchangeStatus(
|
.getExchangeStatus(
|
||||||
userWalletId = userWalletId.stringValue,
|
userWalletId = userWalletId.stringValue,
|
||||||
refCode = ExpressUtils.getRefCode(
|
refCode = ExpressUtils.getRefCode(
|
||||||
|
|
@ -248,8 +250,12 @@ internal class DefaultSwapRepository(
|
||||||
),
|
),
|
||||||
txId = txId,
|
txId = txId,
|
||||||
)
|
)
|
||||||
.getOrThrow(),
|
.getOrThrow()
|
||||||
)
|
|
||||||
|
val entity = response.toEntity(ownerAddress = response.fromAddress.orEmpty())
|
||||||
|
expressHistoryDao.upsertExchanges(listOf(entity))
|
||||||
|
|
||||||
|
exchangeStatusConverter.convert(response)
|
||||||
},
|
},
|
||||||
catch = { exception ->
|
catch = { exception ->
|
||||||
TangemLogger.e("getExchangeStatus error", exception)
|
TangemLogger.e("getExchangeStatus error", exception)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,33 @@
|
||||||
package com.tangem.feature.swap.converters
|
package com.tangem.feature.swap.converters
|
||||||
|
|
||||||
import com.tangem.datasource.api.express.models.response.ExchangeStatusResponse
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
|
||||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
|
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import com.tangem.datasource.api.express.models.response.ExchangeStatus as ResponseExchangeStatus
|
||||||
|
|
||||||
internal class ExchangeStatusConverter : Converter<ExchangeStatusResponse, ExchangeStatusModel> {
|
internal class ExchangeStatusConverter(moshi: Moshi) : Converter<ExchangeItemResponse, ExchangeStatusModel> {
|
||||||
override fun convert(value: ExchangeStatusResponse): ExchangeStatusModel {
|
|
||||||
|
private val responseStatusAdapter = moshi.adapter(ResponseExchangeStatus::class.java)
|
||||||
|
|
||||||
|
override fun convert(value: ExchangeItemResponse): ExchangeStatusModel {
|
||||||
return ExchangeStatusModel(
|
return ExchangeStatusModel(
|
||||||
providerId = value.providerId,
|
providerId = value.providerId,
|
||||||
status = ExchangeStatus.entries.firstOrNull {
|
status = value.status.toExchangeStatus(),
|
||||||
it.name.lowercase() == value.status.name.lowercase()
|
|
||||||
},
|
|
||||||
txId = value.externalTxId,
|
txId = value.externalTxId,
|
||||||
txExternalUrl = value.externalTxUrl,
|
txExternalUrl = value.externalTxUrl,
|
||||||
txExternalId = value.externalTxId,
|
txExternalId = value.externalTxId,
|
||||||
refundNetwork = value.refundNetwork,
|
refundNetwork = value.refundNetwork,
|
||||||
refundContractAddress = value.refundContractAddress,
|
refundContractAddress = value.refundContractAddress,
|
||||||
createdAt = value.createdAt,
|
createdAt = runCatching { DateTime.parse(value.createdAt) }.getOrNull(),
|
||||||
averageDuration = value.averageDuration,
|
averageDuration = value.averageDuration?.toInt(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toExchangeStatus(): ExchangeStatus? {
|
||||||
|
val responseStatus = responseStatusAdapter.fromJsonValue(this) ?: return null
|
||||||
|
return ExchangeStatus.entries.firstOrNull { it.name == responseStatus.name }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||||
import com.tangem.datasource.di.NetworkMoshi
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
|
|
@ -43,6 +44,7 @@ internal class SwapDataModule {
|
||||||
@NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
rampStateManager: RampStateManager,
|
rampStateManager: RampStateManager,
|
||||||
|
expressHistoryDao: ExpressHistoryDao,
|
||||||
): SwapRepository {
|
): SwapRepository {
|
||||||
return DefaultSwapRepository(
|
return DefaultSwapRepository(
|
||||||
tangemExpressApi = tangemExpressApi,
|
tangemExpressApi = tangemExpressApi,
|
||||||
|
|
@ -53,6 +55,7 @@ internal class SwapDataModule {
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
rampStateManager = rampStateManager,
|
rampStateManager = rampStateManager,
|
||||||
|
expressHistoryDao = expressHistoryDao,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue