@2bad/walt 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 2BAD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,106 @@
1
- # Temporary Holding Version
1
+ # @2bad/walt
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![NPM version](https://img.shields.io/npm/v/@2bad/walt)](https://www.npmjs.com/package/@2bad/walt)
4
+ [![License](https://img.shields.io/npm/l/@2bad/walt)](https://www.npmjs.com/package/@2bad/walt)
5
+ [![Code coverage](https://img.shields.io/codecov/c/github/2BAD/walt)](https://codecov.io/gh/2BAD/walt)
6
+ [![GitHub Build Status](https://img.shields.io/github/actions/workflow/status/2BAD/walt/build.yml)](https://github.com/2BAD/walt/actions/workflows/build.yml)
7
+
8
+ Client for Walt, the crypto app in Telegram (ex @wallet).
9
+
10
+ > [!WARNING]
11
+ > This is an unofficial client. It is not affiliated with, endorsed by or supported by Walt or Telegram. It uses a
12
+ > private, undocumented API. Using it likely violates the Walt and Telegram terms of service and may get your account
13
+ > restricted or banned. The software is provided as is, without warranty of any kind. The authors accept no liability
14
+ > for any loss of funds, account access or data, or any other damage arising from its use. Use it at your own risk.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @2bad/walt
20
+ ```
21
+
22
+ ESM only. Requires Node.js 26 or later.
23
+
24
+ ## Quick start
25
+
26
+ ```ts
27
+ import { TelegramTokenSource, WaltClient } from '@2bad/walt'
28
+
29
+ const source = new TelegramTokenSource({ session: 'SESSION_STRING', apiId: 12345, apiHash: 'API_HASH' })
30
+ const client = new WaltClient({ tokenProvider: (rejected) => source.token(rejected) })
31
+
32
+ const { entries, total, fiatCurrency } = await client.portfolio()
33
+ console.log(entries[0], total, fiatCurrency) // { currency: 'TON', amount: '120.5', fiatValue: 381.2 } 1250.4 USD
34
+ await source.disconnect()
35
+ ```
36
+
37
+ ## Examples
38
+
39
+ - [Connecting](examples/connecting.md): Telegram credentials, token storage, retries and pacing
40
+ - [Balances](examples/balances.md): accounts, portfolio, profit and loss, staked funds
41
+ - [Prices](examples/prices.md): coin rates, charts and past prices
42
+ - [Exchange](examples/exchange.md): available pairs, quotes, exchanges and limit orders
43
+ - [Transactions](examples/transactions.md): history, details and reading a transaction
44
+ - [Errors](examples/errors.md): `WaltRequestError`, `WaltAuthError` and what is retried
45
+
46
+ A runnable script is in [examples/portfolio.ts](examples/portfolio.ts).
47
+
48
+ ## API
49
+
50
+ ```ts
51
+ new TelegramTokenSource(options: TelegramTokenSourceOptions)
52
+
53
+ type TelegramTokenSource = {
54
+ token(rejected?: string): Promise<string>
55
+ disconnect(): Promise<void>
56
+ }
57
+
58
+ new WaltClient(options: WaltClientOptions)
59
+
60
+ type WaltClient = {
61
+ accounts(): Promise<AccountsResponse>
62
+ portfolio(): Promise<Portfolio>
63
+ pnl(): Promise<PnlResponse>
64
+ portfolioHistory(dateFrom: Date | string, resolution: '1d' | '1m'): Promise<PortfolioHistory>
65
+ frozenBalances<T = unknown>(): Promise<T[]>
66
+
67
+ earnCampaigns(): Promise<EarnCampaignsResponse>
68
+ earnCampaignTransactions(campaignId: number | string, limit?: number, after?: EarnTransactionsPage): Promise<EarnTransactionsPage>
69
+ allEarnCampaignTransactions(campaignId: number | string, limit?: number): AsyncGenerator<EarnTransaction>
70
+ earnRate(from: string, to: string): Promise<EarnRate>
71
+
72
+ coins(fiatCurrency: FiatCurrency): Promise<Coin[]>
73
+ coinDetails(currency: string, fiatCurrency: FiatCurrency): Promise<CoinDetails>
74
+ charts(currency: string, fiatCurrency: FiatCurrency): Promise<Charts>
75
+ priceForFiatAtTime(crypto: string, fiat: FiatCurrency, amount: number | string, time: Date | string | number): Promise<PriceAtTime>
76
+ localCurrency(): Promise<FiatCurrency>
77
+
78
+ availableExchanges(): Promise<AvailableExchanges>
79
+ quote(fromCurrency: string, toCurrency: string, fromAmount: number): Promise<ExchangeQuote>
80
+ createExchange(request: ExchangeRequest): Promise<Exchange> // moves real money, never retried
81
+
82
+ limitOrders(options?: { status?: string[]; limit?: number }): Promise<LimitOrder[]>
83
+ limitOrder(id: string): Promise<LimitOrder>
84
+ limitOrderQuote(baseCurrency: string, side: 'BUY' | 'SELL', priceLimit: number | string, size: { amount: number | string } | { quantity: number | string }): Promise<LimitOrderQuote>
85
+ limitOrderBounds(baseCurrency: string): Promise<LimitOrderBounds>
86
+ limitOrderTokens(): Promise<LimitOrderToken[]>
87
+
88
+ transactions(limit?: number, cursor?: string): Promise<TransactionsPage>
89
+ accountTransactions(currency: string, limit?: number, cursor?: string): Promise<TransactionsPage>
90
+ allTransactions(limit?: number): AsyncGenerator<Transaction>
91
+ allAccountTransactions(currency: string, limit?: number): AsyncGenerator<Transaction>
92
+ transactionDetails(id: number | string, gateway?: TransactionGateway): Promise<TransactionDetails>
93
+ detailsFor(transactions: AsyncIterable<Transaction> | Iterable<Transaction>): AsyncGenerator<TransactionDetails>
94
+ unconfirmedTransactions<T = unknown>(limit?: number): Promise<{ transactions: T[]; size: number }>
95
+ }
96
+
97
+ function normalizeTransaction(details: TransactionDetails): NormalizedTransaction
98
+ ```
99
+
100
+ Response fields keep the API's `snake_case`. Amounts are decimal strings, except in the exchange responses.
101
+
102
+ The API publishes no schemas. Every type was derived from real responses and is loose where a value has not been seen.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,111 @@
1
+ export type FiatCurrency = 'USD' | 'EUR' | 'RUB' | 'GBP' | 'UAH' | 'KZT' | 'BYN' | 'KRW' | 'ILS' | 'IDR' | 'AED' | 'GEL' | 'TWD' | 'TRY' | 'HKD' | 'INR' | 'CAD' | 'AMD' | 'AUD' | 'PLN' | 'COP' | 'BRL' | 'CHF' | 'MXN' | 'ARS' | 'SGD' | 'SAR' | 'MDL' | 'JPY' | 'RON' | 'SEK' | 'AZN' | 'RSD' | 'NOK' | 'MYR' | 'BGN' | 'EGP' | 'GTQ' | 'CLP' | 'NZD' | 'BDT' | 'KGS' | 'THB' | 'UZS' | 'UYU' | 'VND' | 'UGX' | 'KES' | 'ZAR' | 'VES' | 'ETB' | 'NGN' | 'LKR' | 'PHP' | 'PKR' | 'TJS' | 'CNY' | 'MZN' | 'SDG' | 'ZMW';
2
+ export type PriceAtTime = {
3
+ rate: string;
4
+ fiat_amount: string;
5
+ currency_from: string;
6
+ currency_to: string;
7
+ amount_from: string;
8
+ time: string;
9
+ };
10
+ export type AccountAddress = {
11
+ /** Null until the account `is_created`. */
12
+ address: string | null;
13
+ network: string;
14
+ network_code: string;
15
+ wallet_to_replace: boolean;
16
+ };
17
+ export type AccountAction = {
18
+ is_enabled: boolean;
19
+ is_popular: boolean;
20
+ scheduled_at: unknown;
21
+ };
22
+ export type AccountActions = {
23
+ deposit: AccountAction;
24
+ withdraw_inner: AccountAction;
25
+ withdraw_outer: AccountAction;
26
+ exchange: AccountAction;
27
+ p2p: AccountAction;
28
+ purchase: AccountAction;
29
+ };
30
+ export type Account = {
31
+ currency: string;
32
+ account_type: string;
33
+ /** For example `onchain`, `stock` or `etf`. */
34
+ category: string;
35
+ description: string | null;
36
+ market_cap: string | null;
37
+ rate: string;
38
+ absolute_rate_change: string;
39
+ percent_rate_change: string;
40
+ balance_hold: string;
41
+ available_balance: string;
42
+ available_balance_usd_amount: string;
43
+ available_balance_fiat_amount: string;
44
+ available_balance_fiat_currency: string;
45
+ addresses: AccountAddress[];
46
+ actions: AccountActions;
47
+ is_created: boolean;
48
+ is_always_on_main: boolean;
49
+ is_new_currency: boolean;
50
+ has_transactions: boolean;
51
+ };
52
+ export type AccountsResponse = {
53
+ available_balance_total_fiat_amount: string;
54
+ available_balance_total_fiat_currency: string;
55
+ accounts: Account[];
56
+ };
57
+ export type Coin = {
58
+ currency: string;
59
+ fiat_currency: string;
60
+ networks: string[];
61
+ category: string;
62
+ rate: string;
63
+ rate_change_24h_percent: string;
64
+ volume24h: string | null;
65
+ };
66
+ export type TradingHoursPeriod = {
67
+ mode: string;
68
+ start_at: string;
69
+ end_at: string;
70
+ };
71
+ export type TradingHoursState = TradingHoursPeriod & {
72
+ next_mode: string;
73
+ schedule: TradingHoursPeriod[];
74
+ };
75
+ export type CoinDetails = {
76
+ currency: string;
77
+ fiat_currency: string;
78
+ category: string;
79
+ description: string;
80
+ networks: string[];
81
+ rate: string;
82
+ rate_change_24h_percent: string;
83
+ rate_change_24h_absolute: string;
84
+ volume24h: string | null;
85
+ volume24h_change_24h_percent: string | null;
86
+ market_cap: string | null;
87
+ market_cap_change_24h_percent: string | null;
88
+ total_supply: string | null;
89
+ circulating_supply: string | null;
90
+ chart_preview_rate_24h: {
91
+ timestamp: number;
92
+ value: string;
93
+ }[];
94
+ trading_hours_state: TradingHoursState | null;
95
+ mira_summary: {
96
+ summary: string;
97
+ bot_url: string;
98
+ updated_at: string;
99
+ } | null;
100
+ };
101
+ export type ChartPoint = {
102
+ rate: string;
103
+ timestamp: number;
104
+ };
105
+ export type Charts = {
106
+ day: ChartPoint[];
107
+ week: ChartPoint[];
108
+ month: ChartPoint[];
109
+ year: ChartPoint[];
110
+ all: ChartPoint[];
111
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ export type PnlPosition = {
2
+ currency: string;
3
+ fiat_currency: string;
4
+ /** Cost basis, not current value; add `absolute_all_time` for that. */
5
+ current_value_in_fiat: string;
6
+ absolute_24h: string;
7
+ percentage_24h: string;
8
+ absolute_all_time: string;
9
+ percentage_all_time: string;
10
+ account_type: string;
11
+ earn_campaign_name: string | null;
12
+ };
13
+ export type PnlTotals = {
14
+ fiat_currency: string;
15
+ /** Cost basis, not current value; add `absolute_all_time` for that. */
16
+ current_value_in_fiat: string;
17
+ absolute_24h: string;
18
+ percentage_24h: string;
19
+ absolute_all_time: string;
20
+ percentage_all_time: string;
21
+ };
22
+ export type PnlResponse = {
23
+ positions: PnlPosition[];
24
+ total: PnlTotals;
25
+ };
26
+ export type PortfolioFlows = {
27
+ total: string;
28
+ crypto_transfer: string;
29
+ transfer: string;
30
+ p2p: string;
31
+ on_ramp: string;
32
+ off_ramp: string;
33
+ earn: string;
34
+ gift: string;
35
+ wpay: string;
36
+ trading_agent: string;
37
+ other: string;
38
+ };
39
+ /** Fiat amounts over one period. */
40
+ export type PortfolioPeriod = {
41
+ date_from: string;
42
+ date_to: string;
43
+ open_balance_fiat: string;
44
+ close_amount_fiat: string;
45
+ total_pnl: string;
46
+ total_pnl_percent: string | null;
47
+ realized_pnl_details: {
48
+ total: string;
49
+ total_percent: string | null;
50
+ spot: string;
51
+ perps: string;
52
+ earns: string;
53
+ };
54
+ unrealized_pnl_details: {
55
+ total: string;
56
+ total_percent: string | null;
57
+ };
58
+ deposits_details: PortfolioFlows;
59
+ withdrawals_details: PortfolioFlows;
60
+ };
61
+ export type PortfolioHistory = {
62
+ fiat_currency: string;
63
+ chart: {
64
+ data_points: PortfolioPeriod[];
65
+ };
66
+ total_period_details: PortfolioPeriod;
67
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,113 @@
1
+ export type EarnAccount = {
2
+ account_id: number;
3
+ /** Units staked in this campaign, absent from `accounts()`. */
4
+ balance: string;
5
+ paid_out_amount: string;
6
+ currency: string;
7
+ created_at: string;
8
+ max_withdraw_amount: unknown;
9
+ pending_withdraw_deadline_at: unknown;
10
+ };
11
+ export type EarnAccessPolicy = {
12
+ access_to_deposit: boolean;
13
+ access_to_withdrawal: boolean;
14
+ access_to_full_withdrawal: boolean;
15
+ deposit_restriction_reason: unknown;
16
+ };
17
+ export type EarnBonus = {
18
+ account_id: number | null;
19
+ name: string;
20
+ status: string;
21
+ apy: string;
22
+ combined_apy: string;
23
+ max_amount: string;
24
+ paid_out_amount: string;
25
+ duration: number | null;
26
+ end_at: string | null;
27
+ };
28
+ export type EarnInterestRate = {
29
+ rate: string;
30
+ interest_type: string;
31
+ combined_rate: string;
32
+ effective_rate: string;
33
+ bonus_rate: string | null;
34
+ };
35
+ export type EarnPayoutPool = {
36
+ total: string;
37
+ available: string;
38
+ };
39
+ export type EarnAmount = {
40
+ amount: string;
41
+ currency: string;
42
+ };
43
+ export type EarnCampaign = {
44
+ campaign_id: number;
45
+ /** Matches `PnlPosition.earn_campaign_name`. */
46
+ name: string;
47
+ status: string;
48
+ /** Null unless the campaign was joined. */
49
+ account: EarnAccount | null;
50
+ apy_type: string;
51
+ current_apy: string;
52
+ effective_apy: string | null;
53
+ interest_rate: EarnInterestRate;
54
+ bonus: EarnBonus | null;
55
+ access_policy: EarnAccessPolicy;
56
+ payout_pool: EarnPayoutPool | null;
57
+ total_earned: EarnAmount | null;
58
+ payout_type: string;
59
+ payout_account_type: string;
60
+ payout_interval: number;
61
+ payout_account: unknown;
62
+ input_currency: string;
63
+ deposit_currency: string;
64
+ payout_currency: string;
65
+ deposit_withdraw_currency: string;
66
+ payout_withdraw_currency: string;
67
+ min_amount: string;
68
+ max_amount: string | null;
69
+ min_deposit_amount: string;
70
+ min_withdraw_amount: string | null;
71
+ last_24hours_paid_out_amount: string | null;
72
+ start_at: string;
73
+ end_at: string | null;
74
+ terms_and_conditions: boolean;
75
+ is_deposit_limit_exceeded: boolean;
76
+ };
77
+ export type EarnCampaignsResponse = {
78
+ campaigns: EarnCampaign[];
79
+ required_kyc_level: string;
80
+ ab_tests: Record<string, unknown>;
81
+ tooltip_campaign_id: number | null;
82
+ total_participants: number;
83
+ };
84
+ export type EarnTransaction = {
85
+ id: number;
86
+ transaction_type: string;
87
+ direction: string;
88
+ status: string;
89
+ source: string;
90
+ amount: string;
91
+ currency: string;
92
+ date: string;
93
+ transfer_id: string;
94
+ idempotency_key: string;
95
+ deadline_at: unknown;
96
+ pending_reason: unknown;
97
+ };
98
+ export type EarnTransactionsPage = {
99
+ transactions: EarnTransaction[];
100
+ deposit_acc_next_from: number | null;
101
+ payout_acc_next_from: number | null;
102
+ transfer_request_next_before_datetime: string | null;
103
+ };
104
+ export type EarnDepegInfo = {
105
+ depeg_state: string;
106
+ depeg: number;
107
+ currency: string;
108
+ reference_currency: string;
109
+ };
110
+ export type EarnRate = {
111
+ rate: string;
112
+ depeg_info: EarnDepegInfo | null;
113
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { FiatCurrency } from "./accounts.js";
2
+ /** Each exchangeable currency mapped to its counterparts. Pairs appear in both directions. */
3
+ export type AvailableExchanges = Record<string, string[]>;
4
+ /**
5
+ * Amounts are numbers, not decimal strings. `min_amount` and `max_amount` bound `from_amount`;
6
+ * `max_amount` is the balance when selling and the platform limit when buying.
7
+ */
8
+ export type ExchangeQuote = {
9
+ from_amount: number;
10
+ from_currency: string;
11
+ to_amount: number;
12
+ to_currency: string;
13
+ rate: number;
14
+ min_amount: number;
15
+ max_amount: number;
16
+ };
17
+ export type ExchangeRequest = {
18
+ from_currency: string;
19
+ to_currency: string;
20
+ from_amount: number;
21
+ local_currency: FiatCurrency;
22
+ /** Set when `from_amount` is the whole balance. */
23
+ max_amount?: boolean;
24
+ };
25
+ /** `ttl` is in seconds. */
26
+ export type Exchange = {
27
+ uid: string;
28
+ transfer_id: string;
29
+ ttl: number;
30
+ from_amount: number;
31
+ from_balance_after: number;
32
+ from_currency: string;
33
+ to_amount: number;
34
+ to_balance_after: number;
35
+ to_currency: string;
36
+ from_fiat_amount: number;
37
+ from_fiat_currency: string;
38
+ to_fiat_amount: number;
39
+ to_fiat_currency: string;
40
+ rate: number;
41
+ fee_amount: number;
42
+ fee_currency: string;
43
+ fee_fiat_amount: number;
44
+ fee_fiat_currency: string;
45
+ new_rates: boolean;
46
+ non_promo_fee_amount: unknown;
47
+ non_promo_fee_currency: unknown;
48
+ promo_fee: unknown;
49
+ max_amount: boolean;
50
+ price_deviation_percent: string;
51
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ export type LimitOrder = {
2
+ id: string;
3
+ client_order_id: string;
4
+ base_currency: string;
5
+ side: string;
6
+ /** `LIMIT`, or `TP` and `SL` for take-profit and stop-loss orders. */
7
+ type: string;
8
+ price_limit: string;
9
+ quantity: string;
10
+ multiplier: string;
11
+ spv_price_limit: string;
12
+ spv_quantity: string;
13
+ fee: string;
14
+ fee_currency: string;
15
+ total: string;
16
+ /** For example `OPEN`, `CANCELLING`, `CANCELLED` or `FILLED`. */
17
+ status: string;
18
+ created_at: string;
19
+ updated_at: string;
20
+ /** Only on filled orders. */
21
+ executed_at?: string;
22
+ /** Only on filled orders. */
23
+ execution?: {
24
+ filled_quantity: string;
25
+ filled_quote_amount: string;
26
+ price: string;
27
+ };
28
+ };
29
+ export type LimitOrderQuote = {
30
+ base_currency: string;
31
+ side: string;
32
+ price_limit: string;
33
+ quantity: string;
34
+ multiplier: string;
35
+ spv_price_limit: string;
36
+ spv_quantity: string;
37
+ notional: string;
38
+ fee: string;
39
+ fee_currency: string;
40
+ total: string;
41
+ fee_discount: {
42
+ kind: string;
43
+ regular_amount: string;
44
+ reason: string;
45
+ };
46
+ };
47
+ export type LimitOrderBounds = {
48
+ base_currency: string;
49
+ price: string;
50
+ max_price_limit: string;
51
+ take_profit_min_price: string;
52
+ stop_loss_max_price: string;
53
+ price_decimals: number;
54
+ updated_at: string;
55
+ };
56
+ export type LimitOrderToken = {
57
+ code: string;
58
+ /** Smallest order, in USDT. */
59
+ min_limit: string;
60
+ max_limit: string;
61
+ quantity_decimals: number;
62
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ export type TransactionGateway = 'card' | 'purchase' | 'internal' | 'btc' | 'manual' | 'crypto_exchange' | 'tg_transfer' | 'binance' | 'top_up' | 'withdraw_onchain' | 'fireblocks' | 'single_use' | 'multi_use' | 'part_multi_use' | 'p2p_order' | 'p2p_order_inactive_offer' | 'p2p_offer' | 'p2p_refund' | 'p2p_security_deposit' | 'p2p_security_deposit_seizure' | 'wpay_payment' | 'wpay_payout' | 'payouts' | 'bonus_any' | 'onboarding_incentive' | 'giveaway' | 'earn' | 'earn_reward' | 'earn_v2' | 'earn_v2_reward' | 'earn_v2_bonus' | 'earn_v2_crypto_exchange' | 'forced_transfer' | 'tg_transfer_onchain' | 'onramp' | 'offramp' | 'xstocks_dividends' | 'perps' | 'perps_pnl' | 'perps_fee' | 'perps_liquidation_fee' | 'perps_fundings' | 'perps_open_hold_shortfall' | 'perps_margin' | 'perps_bbook_insurance' | 'perps_bbook_reserve' | 'perps_resize_position' | 'unconfirmed_deposit' | 'loyalty_incentive' | 'compensation' | 'tg_stars_purchase' | 'ipo' | 'ipo_allocation' | 'loyalty_payout' | 'crypto_card_cashback' | 'affiliate_payout' | 'limit_swap' | 'payment_link' | 'bot_banking';
2
+ export type Transaction = {
3
+ id: number;
4
+ type: string;
5
+ created_at: string;
6
+ amount: string;
7
+ currency: string;
8
+ status: string;
9
+ gateway: string;
10
+ is_blocked: boolean;
11
+ username: string | null;
12
+ tg_id: number | null;
13
+ input_addresses: string | null;
14
+ recipient_wallet_address: string | null;
15
+ photo_url: string | null;
16
+ pair_transaction_currency: string | null;
17
+ network: string | null;
18
+ block_reason: string | null;
19
+ crypto_tx_id: string | null;
20
+ gateway_details: unknown;
21
+ };
22
+ export type TransactionsPage = {
23
+ transactions: Transaction[];
24
+ limit: number;
25
+ size: number;
26
+ next_cursor: string | null;
27
+ };
28
+ export type TransactionDetails = {
29
+ id: number;
30
+ type: string;
31
+ created_at: string;
32
+ status: string;
33
+ gateway: string;
34
+ crypto_amount: string;
35
+ crypto_currency: string;
36
+ /** Null when the transaction was never priced. */
37
+ local_fiat_amount: string | null;
38
+ local_fiat_amount_now: string;
39
+ local_currency: string;
40
+ fee_amount: string | null;
41
+ fee_currency: string | null;
42
+ exchange_rate: string | null;
43
+ pair_transaction_amount: string | null;
44
+ pair_transaction_currency: string | null;
45
+ username: string | null;
46
+ tg_id: number | null;
47
+ transfer_id: string | null;
48
+ tg_transfer_status: string | null;
49
+ payment_link_id: string | null;
50
+ mention: string | null;
51
+ comment: string | null;
52
+ input_addresses: string | null;
53
+ recipient_wallet_address: string | null;
54
+ network: string | null;
55
+ crypto_tx_id: string | null;
56
+ /** Block explorer url for `crypto_tx_id`. */
57
+ transaction_link: string | null;
58
+ photo_url: string | null;
59
+ gateway_details: unknown;
60
+ is_blocked: boolean;
61
+ is_cancellable: boolean;
62
+ block_reason: string | null;
63
+ };
64
+ export type TransactionLeg = {
65
+ currency: string;
66
+ amount: string;
67
+ };
68
+ export type NormalizedTransaction = {
69
+ id: number;
70
+ createdAt: string;
71
+ gateway: string;
72
+ status: string;
73
+ received: TransactionLeg | null;
74
+ sent: TransactionLeg | null;
75
+ /** Already deducted from the legs; reporting only. */
76
+ fee: TransactionLeg | null;
77
+ };
78
+ /**
79
+ * Splits a `transactionDetails()` record into what entered the wallet, what left it, and the fee.
80
+ * A `deposit` of USDT paired with BTC is a sale of BTC. Listing rows lack `pair_transaction_amount`,
81
+ * so they cannot be normalised.
82
+ *
83
+ * @param details - A `transactionDetails()` record.
84
+ */
85
+ export declare const normalizeTransaction: (details: TransactionDetails) => NormalizedTransaction;
@@ -0,0 +1,32 @@
1
+ // source/api/transactions.ts
2
+ /**
3
+ * Splits a `transactionDetails()` record into what entered the wallet, what left it, and the fee.
4
+ * A `deposit` of USDT paired with BTC is a sale of BTC. Listing rows lack `pair_transaction_amount`,
5
+ * so they cannot be normalised.
6
+ *
7
+ * @param details - A `transactionDetails()` record.
8
+ */
9
+ const normalizeTransaction = (details) => {
10
+ const main = {
11
+ currency: details.crypto_currency,
12
+ amount: details.crypto_amount
13
+ };
14
+ const pair = details.pair_transaction_currency !== null && details.pair_transaction_amount !== null && details.pair_transaction_currency !== details.crypto_currency ? {
15
+ currency: details.pair_transaction_currency,
16
+ amount: details.pair_transaction_amount
17
+ } : null;
18
+ const inbound = details.type === "deposit";
19
+ return {
20
+ id: details.id,
21
+ createdAt: details.created_at,
22
+ gateway: details.gateway,
23
+ status: details.status,
24
+ received: inbound ? main : pair,
25
+ sent: inbound ? pair : main,
26
+ fee: details.fee_currency !== null && details.fee_amount !== null && Number(details.fee_amount) > 0 ? {
27
+ currency: details.fee_currency,
28
+ amount: details.fee_amount
29
+ } : null
30
+ };
31
+ };
32
+ export { normalizeTransaction };
@@ -0,0 +1 @@
1
+ export {}