Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/update-prices.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Update Price History

on:
schedule:
- cron: '0 6 * * *'
push:
branches: [main, feat/fiat-price-overlay]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
update-prices:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm install

- name: Fetch new prices
run: npm run fetch-prices

- name: Commit and push changes
run: |
git config --local user.email "action@github.com"
git config --local user.name "github-actions[bot]"
git status
if ! git diff --quiet src/data/algo-price-history.json; then
echo "Changes detected, committing..."
git add src/data/algo-price-history.json
git commit -m "chore: update algo price history [skip ci]"
git push origin HEAD:${{ github.ref_name }}
else
echo "No new price data found."
fi
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"prettier:check": "prettier --check .",
"test:watch": "vitest",
"test": "vitest --run",
"ci": "npm run lint && npm run prettier:fix && npm run type:check && npm run build && vitest --run"
"ci": "npm run lint && npm run prettier:fix && npm run type:check && npm run build && vitest --run",
"fetch-prices": "node scripts/fetch-algo-price-history.js"
},
"dependencies": {
"@algorandfoundation/algokit-utils": "^9.1.2",
Expand Down
109 changes: 109 additions & 0 deletions scripts/fetch-algo-price-history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const SYMBOL = 'ALGOUSDT';
const INTERVAL = '1d';
const START_DATE = '2019-09-20';
const OUTPUT_FILE = path.join(__dirname, '../src/data/algo-price-history.json');
const MEXC_API = 'https://api.mexc.com/api/v3/klines';

async function fetchKlines(startTime) {
const url = `${MEXC_API}?symbol=${SYMBOL}&interval=${INTERVAL}&startTime=${startTime}&limit=1000`;
console.log(`Fetching: ${url}`);
const response = await fetch(url);
if (!response.ok) {
const text = await response.text();
throw new Error(`Failed to fetch: ${response.status} ${response.statusText} - ${text}`);
}
return response.json();
}

function formatDate(timestamp) {
const date = new Date(timestamp);
return date.toISOString().split('T')[0];
}

async function main() {
let existingData = {};
if (fs.existsSync(OUTPUT_FILE)) {
try {
existingData = JSON.parse(fs.readFileSync(OUTPUT_FILE, 'utf8'));
console.log(`Loaded ${Object.keys(existingData).length} existing entries.`);
} catch (e) {
console.error('Error reading existing file, starting fresh.');
}
}

// Ensure src/data exists
const dataDir = path.dirname(OUTPUT_FILE);
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}

const today = new Date().setHours(0, 0, 0, 0);
let currentStartTime = new Date(START_DATE).getTime();

// If we have existing data, we can start from the last date + 1 day
const dates = Object.keys(existingData).sort();
if (dates.length > 0) {
const lastDate = dates[dates.length - 1];
const lastTime = new Date(lastDate).getTime();
currentStartTime = lastTime + 24 * 60 * 60 * 1000;

// If lastTime is already today or later, we don't need to fetch
if (currentStartTime >= today) {
console.log('Already up to date.');
return;
}

console.log(`Starting/Resuming from ${formatDate(currentStartTime)}`);
} else {
console.log(`Starting from scratch: ${START_DATE}`);
}

let newDataCount = 0;
while (currentStartTime < today) {
console.log(`Fetching from ${formatDate(currentStartTime)}...`);
const klines = await fetchKlines(currentStartTime);

if (klines.length === 0) break;

for (const candle of klines) {
const openTime = candle[0];
const closePrice = parseFloat(candle[4]);
const dateStr = formatDate(openTime);

if (openTime < today) {
existingData[dateStr] = closePrice;
newDataCount++;
}

currentStartTime = openTime + 24 * 60 * 60 * 1000;
}

// Small delay to avoid aggressive rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
}

if (newDataCount > 0) {
// Sort keys before writing
const sortedData = {};
Object.keys(existingData).sort().forEach(key => {
sortedData[key] = existingData[key];
});

fs.writeFileSync(OUTPUT_FILE, JSON.stringify(sortedData, null, 2));
console.log(`Done! Added ${newDataCount} new entries. Total: ${Object.keys(sortedData).length}`);
} else {
console.log('Already up to date.');
}
}

main().catch(err => {
console.error(err);
process.exit(1);
});
11 changes: 9 additions & 2 deletions src/components/address/address-breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useTheme } from "@/components/theme-provider";
import { MinimalBlock } from "@/lib/block-types";
import { RefreshButton } from "./refresh-button";
import { useNFDReverseMultiple } from "@/hooks/queries/useNFD";
import { useSearch } from "@tanstack/react-router";

const AddressBreadcrumb = ({
resolvedAddresses,
Expand All @@ -39,6 +40,8 @@ const AddressBreadcrumb = ({
hasError: boolean;
}) => {
const { theme } = useTheme();
const search = useSearch({ from: "/$addresses" });
const isBalanceHidden = search.hideBalance;

// Fetch NFD names for all addresses
const addresses = resolvedAddresses.map((addr) => addr.address);
Expand Down Expand Up @@ -105,7 +108,9 @@ const AddressBreadcrumb = ({
)}
{!loading &&
resolvedAddresses.length === 1 &&
getDisplayName(resolvedAddresses[0].address)}
(isBalanceHidden
? "*****"
: getDisplayName(resolvedAddresses[0].address))}
{!loading &&
resolvedAddresses.length > 1 &&
`Multiple addresses (${resolvedAddresses.length})`}
Expand All @@ -123,7 +128,9 @@ const AddressBreadcrumb = ({
)}
{!loading &&
resolvedAddresses.length === 1 &&
getDisplayName(resolvedAddresses[0].address, true)}
(isBalanceHidden
? "*****"
: getDisplayName(resolvedAddresses[0].address, true))}
{!loading && resolvedAddresses.length > 1 && "Multiple addresses"}
</a>

Expand Down
22 changes: 16 additions & 6 deletions src/components/address/address-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CopyButton from "@/components/copy-to-clipboard";
import { ResolvedAddress } from "@/components/heatmap/types";
import { useAccount } from "@/hooks/queries/useAccounts";
import AccountStatus from "./stats/status/status";
import { useSearch } from "@tanstack/react-router";

export default function AddressFilters({
showFilters,
Expand Down Expand Up @@ -109,6 +110,8 @@ function AddressCheckbox({
onChange: (checked: boolean) => void;
}) {
const { data: account } = useAccount(address);
const search = useSearch({ from: "/$addresses" });
const isBalanceHidden = search.hideBalance;

return (
<div className="flex gap-2">
Expand All @@ -133,14 +136,21 @@ function AddressCheckbox({
htmlFor={`address-${address.address}`}
className="flex items-center gap-2 font-medium text-nowrap text-gray-900 dark:text-gray-100"
>
{address.nfd ? address.nfd : displayAlgoAddress(address.address)}
<CopyButton
address={address.nfd ? address.nfd : address.address}
small
/>
{isBalanceHidden
? "*****"
: address.nfd
? address.nfd
: displayAlgoAddress(address.address)}
{!isBalanceHidden && (
<CopyButton
address={address.nfd ? address.nfd : address.address}
small
/>
)}
</label>
<p className="hidden items-center gap-2 text-gray-500 sm:flex dark:text-gray-400">
{address.address} <CopyButton address={address.address} small />
{isBalanceHidden ? "*****" : address.address}
{!isBalanceHidden && <CopyButton address={address.address} small />}
</p>
{account && <AccountStatus address={address} />}
</div>
Expand Down
36 changes: 29 additions & 7 deletions src/components/address/address-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const AccountStatus = lazy(() => import("./stats/status/status"));
const CumulativeRewardsChart = lazy(
() => import("@/components/address/charts/cumulative-rewards-chart"),
);
const PriceImpactSection = lazy(
() => import("@/components/address/price-impact-section"),
);
const CumulativeBlocksChart = lazy(
() => import("@/components/address/charts/cumulative-blocks-chart"),
);
Expand Down Expand Up @@ -110,6 +113,7 @@ const ChartFallback = () => (
export default function AddressView({ addresses }: { addresses: string }) {
const navigate = useNavigate();
const search = useSearch({ from: "/$addresses" });
const isBalanceHidden = search.hideBalance;
const [showFilters, setShowFilters] = useState(false);
const [showAddAddress, setShowAddAddress] = useState(false);

Expand Down Expand Up @@ -218,6 +222,7 @@ export default function AddressView({ addresses }: { addresses: string }) {
nfdName={addr.nfd}
timeExpires={addr.timeExpires}
expired={addr.expired}
hideSensitive={isBalanceHidden}
/>
),
)}
Expand Down Expand Up @@ -248,13 +253,21 @@ export default function AddressView({ addresses }: { addresses: string }) {
{resolvedAddresses.length === 1 && resolvedAddresses[0] && (
<div>
<div className={"flex flex-wrap items-center gap-2"}>
<h2 className="block text-xl/7 text-gray-700 sm:hidden sm:truncate sm:text-lg sm:tracking-tight">
{displayAlgoAddress(resolvedAddresses[0]?.address)}
</h2>
<h2 className="hidden text-xl/7 text-gray-700 sm:block sm:truncate sm:text-lg sm:tracking-tight">
{resolvedAddresses[0]?.address}
</h2>
<CopyButton address={resolvedAddresses[0].address} />
{isBalanceHidden ? (
<h2 className="text-xl/7 text-gray-700 sm:truncate sm:text-lg sm:tracking-tight">
*****
</h2>
) : (
<>
<h2 className="block text-xl/7 text-gray-700 sm:hidden sm:truncate sm:text-lg sm:tracking-tight">
{displayAlgoAddress(resolvedAddresses[0]?.address)}
</h2>
<h2 className="hidden text-xl/7 text-gray-700 sm:block sm:truncate sm:text-lg sm:tracking-tight">
{resolvedAddresses[0]?.address}
</h2>
<CopyButton address={resolvedAddresses[0].address} />
</>
)}
</div>
<AccountStatus address={resolvedAddresses[0]} />
</div>
Expand Down Expand Up @@ -288,6 +301,15 @@ export default function AddressView({ addresses }: { addresses: string }) {
</Suspense>
</ErrorBoundary>

<ErrorBoundary>
<Suspense fallback={<ChartFallback />}>
<PriceImpactSection
blocks={deferredBlocks}
hideBalance={search.hideBalance}
/>
</Suspense>
</ErrorBoundary>

<ErrorBoundary>
<Suspense fallback={<ChartFallback />}>
<CumulativeBlocksChart blocks={deferredBlocks} />
Expand Down
Loading