Skip to content

Commit 5436516

Browse files
committed
Add accounts[] to GET /users/{id}/coins response
Mirror the UserCoinWithAccounts shape from the singular endpoint so clients can prime the per-mint cache from one plural call instead of issuing N singular requests (one per coin row) on wallet page load. The accounts[] entries carry account, owner, balance, balance_usd, and is_in_app_wallet, matching GET /users/{id}/coins/{mint}. Singular endpoint behavior is unchanged.
1 parent 47852ac commit 5436516

3 files changed

Lines changed: 69 additions & 15 deletions

File tree

api/swagger/swagger-v1.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16452,6 +16452,7 @@ components:
1645216452
- balance
1645316453
- has_discord
1645416454
- balance_usd
16455+
- accounts
1645516456
type: object
1645616457
properties:
1645716458
mint:
@@ -16492,6 +16493,11 @@ components:
1649216493
type: number
1649316494
description: The balance of the coin in the user's account in USD
1649416495
example: 1.23
16496+
accounts:
16497+
type: array
16498+
description: The per-account balances that sum to the total balance, ordered by balance DESC
16499+
items:
16500+
$ref: "#/components/schemas/user_coin_account"
1649516501
track_feed_item:
1649616502
required:
1649716503
- item

api/v1_users_coins.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,37 @@ func (app *ApiServer) v1UsersCoins(c *fiber.Ctx) error {
7373
artist_coins.logo_uri,
7474
artist_coins.banner_image_url,
7575
COALESCE(balances_by_mint.balance, 0) AS balance,
76-
(COALESCE(balances_by_mint.balance, 0) * COALESCE(coin_prices.price, 0)) / POWER(10, artist_coins.decimals) AS balance_usd
76+
(COALESCE(balances_by_mint.balance, 0) * COALESCE(coin_prices.price, 0)) / POWER(10, artist_coins.decimals) AS balance_usd,
77+
COALESCE(
78+
JSON_AGG(
79+
JSON_BUILD_OBJECT(
80+
'account', balances.account,
81+
'owner', balances.owner,
82+
'balance', balances.balance,
83+
'balance_usd', (balances.balance * COALESCE(coin_prices.price, 0)) / POWER(10, artist_coins.decimals),
84+
'is_in_app_wallet', balances.is_in_app_wallet
85+
)
86+
ORDER BY balances.balance DESC
87+
) FILTER (WHERE balances.account IS NOT NULL),
88+
'[]'::json
89+
) AS accounts
7790
FROM artist_coins
7891
LEFT JOIN balances_by_mint ON balances_by_mint.mint = artist_coins.mint
7992
LEFT JOIN artist_coin_prices coin_prices ON coin_prices.mint = artist_coins.mint
93+
LEFT JOIN balances ON balances.mint = artist_coins.mint
8094
WHERE artist_coins.user_id = @user_id -- Show owned coins
81-
OR balance > 0 -- Show coins with positive balance
82-
OR ticker = 'AUDIO' -- Always show AUDIO
95+
OR balances_by_mint.balance > 0 -- Show coins with positive balance
96+
OR artist_coins.ticker = 'AUDIO' -- Always show AUDIO
97+
GROUP BY
98+
artist_coins.ticker,
99+
artist_coins.mint,
100+
artist_coins.decimals,
101+
artist_coins.has_discord,
102+
artist_coins.user_id,
103+
artist_coins.logo_uri,
104+
artist_coins.banner_image_url,
105+
balances_by_mint.balance,
106+
coin_prices.price
83107
ORDER BY
84108
-- Always show user's owned coins first, regardless of balance
85109
(artist_coins.user_id = @user_id) DESC,
@@ -102,7 +126,7 @@ func (app *ApiServer) v1UsersCoins(c *fiber.Ctx) error {
102126
return err
103127
}
104128

105-
userCoins, err := pgx.CollectRows(rows, pgx.RowToStructByName[UserCoin])
129+
userCoins, err := pgx.CollectRows(rows, pgx.RowToStructByName[UserCoinAccounts])
106130
if err != nil {
107131
return err
108132
}

api/v1_users_coins_test.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,17 +131,39 @@ func TestUserCoins(t *testing.T) {
131131
assert.Equal(t, 200, status)
132132

133133
jsonAssert(t, body, map[string]any{
134-
"data.#": 2,
135-
"data.0.ticker": "AUDIO",
136-
"data.0.mint": "9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM",
137-
"data.0.decimals": 8,
138-
"data.0.owner_id": trashid.MustEncodeHashID(1),
139-
"data.0.balance": 1800000000, // 18 AUDIO
140-
"data.0.balance_usd": 180.0, // Assuming $10 per AUDIO
141-
"data.1.ticker": "USDC",
142-
"data.1.mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
143-
"data.1.balance": 7000000, // 7 USDC
144-
"data.1.balance_usd": 7.0,
134+
"data.#": 2,
135+
"data.0.ticker": "AUDIO",
136+
"data.0.mint": "9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM",
137+
"data.0.decimals": 8,
138+
"data.0.owner_id": trashid.MustEncodeHashID(1),
139+
"data.0.balance": 1800000000, // 18 AUDIO
140+
"data.0.balance_usd": 180.0, // Assuming $10 per AUDIO
141+
"data.0.accounts.#": 3,
142+
"data.0.accounts.0.account": "associated",
143+
"data.0.accounts.0.owner": "owner_wallet",
144+
"data.0.accounts.0.balance": 1000000000, // 10 AUDIO
145+
"data.0.accounts.0.balance_usd": 100.0,
146+
"data.0.accounts.0.is_in_app_wallet": false,
147+
"data.0.accounts.1.account": "associated3",
148+
"data.0.accounts.1.owner": "owner_wallet3",
149+
"data.0.accounts.1.balance": 500000000, // 5 AUDIO
150+
"data.0.accounts.1.balance_usd": 50.0,
151+
"data.0.accounts.1.is_in_app_wallet": false,
152+
"data.0.accounts.2.account": "claimable",
153+
"data.0.accounts.2.owner": "claimable_tokens_pda",
154+
"data.0.accounts.2.balance": 300000000, // 3 AUDIO
155+
"data.0.accounts.2.balance_usd": 30.0,
156+
"data.0.accounts.2.is_in_app_wallet": true,
157+
"data.1.ticker": "USDC",
158+
"data.1.mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
159+
"data.1.balance": 7000000, // 7 USDC
160+
"data.1.balance_usd": 7.0,
161+
"data.1.accounts.#": 1,
162+
"data.1.accounts.0.account": "associated2",
163+
"data.1.accounts.0.owner": "owner_wallet2",
164+
"data.1.accounts.0.balance": 7000000,
165+
"data.1.accounts.0.balance_usd": 7.0,
166+
"data.1.accounts.0.is_in_app_wallet": false,
145167
})
146168
}
147169
}
@@ -344,10 +366,12 @@ func TestUserCoinsZeroBalanceOwned(t *testing.T) {
344366
"data.0.owner_id": trashid.MustEncodeHashID(2),
345367
"data.0.balance": 0, // Zero balance
346368
"data.0.balance_usd": 0.0,
369+
"data.0.accounts.#": 0, // No token accounts for owned coin
347370
"data.1.ticker": "AUDIO", // Higher balance but not owned
348371
"data.1.mint": "9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM",
349372
"data.1.balance": 5000000000, // 50 AUDIO
350373
"data.1.balance_usd": 50.0,
374+
"data.1.accounts.#": 1,
351375
})
352376
}
353377

0 commit comments

Comments
 (0)