Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ next-env.d.ts
# The manifest is the batch's evidence, but it is reproducible from the script
# and the cutoff, and it is recorded in D1 once the batch is frozen.
dist/

test-results/

playwright-report/
7 changes: 7 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
void initOpenNextCloudflareForDev();

const nextConfig: NextConfig = {
async headers() {
return [
{ source: "/.well-known/:path*", headers: [{ key: "Access-Control-Allow-Origin", value: "*" }] },
{ source: "/.well-known/wallet-app.json", headers: [{ key: "Content-Type", value: "application/json; charset=utf-8" }] },
{ source: "/wallet-app/:path*", headers: [{ key: "Access-Control-Allow-Origin", value: "*" }] },
];
},
reactCompiler: true,
/**
* How long one page may take to prerender, in seconds.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@noble/hashes": "^1.8.0",
"@scure/base": "^2.2.0",
"@scure/btc-signer": "^2.2.0",
"@xcp/wallet-sdk": "github:XCP/wallet-sdk#5a056ce83dc3a999f9384a7cb625094471d19c91",
"@xcp/wallet-sdk": "github:XCP/wallet-sdk#089ca36869ab72440292c3064143e7dc0ea8e75c",
"babel-plugin-react-compiler": "^1.0.0",
"bignumber.js": "^9.3.1",
"next": "16.3.4",
Expand Down
35 changes: 35 additions & 0 deletions apps/web/public/.well-known/wallet-app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "xcp.fun",
"short_name": "xcp.fun",
"description": "Discover Counterparty token launches and community projects on Bitcoin.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#09090b",
"categories": [
"finance",
"counterparty"
],
"icons": [
{
"src": "/wallet-app/icon.png",
"sizes": "1024x1024",
"type": "image/png",
"purpose": "any"
}
],
"wallet": {
"version": "1",
"subtitle": "Counterparty launches",
"heroImageUrl": "/wallet-app/banner.png",
"splashImageUrl": "/wallet-app/splash.png",
"chains": {
"optional": [
"bip122:000000000019d6689c085ae165831e93"
]
},
"capabilities": {
"optional": ["provider.bitcoin"]
}
}
}
9 changes: 9 additions & 0 deletions apps/web/public/_headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

/.well-known/*
Access-Control-Allow-Origin: *

/.well-known/wallet-app.json
Content-Type: application/json; charset=utf-8

/wallet-app/*
Access-Control-Allow-Origin: *
Binary file added apps/web/public/wallet-app/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/wallet-app/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/wallet-app/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 21 additions & 10 deletions apps/web/src/app/[lang]/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,13 @@ export default function CreatePage() {
const num = useNumbers();
const t = useT();
const usd = useFiat();
const { address, status: walletStatus, signPsbt, signCommitAndReveal, broadcastTransaction, session } = useWallet();
const { address, status: walletStatus, signPsbt, signCommitAndReveal, broadcastTransaction, session, wallet: selectedWallet } = useWallet();
const compose = useCompose();
const isTaproot = address?.startsWith("bc1p") ?? false;
const { data: canInscribe = false } = useSWR(
walletStatus === "connected" && isTaproot ? ["inscription-support", selectedWallet, address] : null,
async () => (await session.getWallet()?.features())?.marketplaceBundles?.includes("commit-and-reveal") ?? false,
);
const [inscribe, setInscribe] = useState(false);
const [inscribeStep, setInscribeStep] = useState<InscribeStep | null>(null);
const [inscribeTxid, setInscribeTxid] = useState<string | null>(null);
Expand Down Expand Up @@ -347,6 +351,7 @@ export default function CreatePage() {
(nameCheck === "available" || nameCheck === "owned") &&
image !== null &&
!imageTooBigToInscribe &&
(!inscribe || canInscribe) &&
isValidX(xProfile) &&
isValidTelegram(telegram) &&
scheduleValid &&
Expand All @@ -363,6 +368,9 @@ export default function CreatePage() {
setSubmitting(true);
setUploadError(null);
try {
if (inscribe && !(await session.getWallet()?.features())?.marketplaceBundles?.includes("commit-and-reveal")) {
throw new Error(t("Use a wallet that supports image inscriptions, or launch with a hosted image."));
}
// 1. Host image + metadata JSON (write-once) so the on-chain
// description URL resolves from the first block.
const form = new FormData();
Expand Down Expand Up @@ -403,12 +411,6 @@ export default function CreatePage() {
: baseFeeRate;

if (inscribe && isTaproot && address) {
// 3a. Commit/reveal inscription: the image is inscribed and the
// inscription output burned; the description is the JSON URL,
// as on a hosted launch. A wallet that signs a commit and its
// reveal in one approval gets both before either is broadcast.
const features = await session.getWallet()?.features().catch(() => null);
const bundles = features?.marketplaceBundles ?? [];
const { revealTxid } = await inscribeLaunch({
asset: name,
lpAsset: generateLpAssetName(),
Expand All @@ -420,7 +422,7 @@ export default function CreatePage() {
description: upload.json_url,
address,
signPsbt,
signCommitAndReveal: bundles.includes("commit-and-reveal") ? signCommitAndReveal : undefined,
signCommitAndReveal,
broadcast: broadcastTransaction,
onStep: setInscribeStep,
});
Expand Down Expand Up @@ -472,7 +474,7 @@ export default function CreatePage() {

const finishLaunch = async () => {
const record = pendingReveal;
if (!record || submitting || walletStatus !== "connected") return;
if (!record || submitting || walletStatus !== "connected" || (!record.revealRawTx && !canInscribe)) return;
setSubmitting(true);
setResuming(true);
setUploadError(null);
Expand Down Expand Up @@ -605,7 +607,10 @@ export default function CreatePage() {
{ asset: pendingReveal.asset },
)}
</p>
<CTA className="mt-3" size="md" onClick={finishLaunch} disabled={submitting}>
{!pendingReveal.revealRawTx && !canInscribe && (
<p className="mt-2">{t("Connect a wallet that supports this inscription reveal to finish the launch.")}</p>
)}
<CTA className="mt-3" size="md" onClick={finishLaunch} disabled={submitting || (!pendingReveal.revealRawTx && !canInscribe)}>
{resuming && inscribeStep && inscribeStep !== "done"
? t(INSCRIBE_STEP_LABELS[inscribeStep])
: t("Finish launching {asset}", { asset: pendingReveal.asset })}
Expand Down Expand Up @@ -738,6 +743,7 @@ export default function CreatePage() {
<input
type="checkbox"
checked={inscribe}
disabled={!canInscribe && !inscribe}
onChange={(e) => setInscribe(e.target.checked)}
className="mt-0.5"
/>
Expand All @@ -748,6 +754,11 @@ export default function CreatePage() {
"The image itself becomes the permanent on-chain description (commit + reveal, two signatures, higher fees scale with image size; the inscription is burned so it belongs to the asset forever). Max 385 KB. Taproot wallets only.",
)}
</span>
{!canInscribe && (
<span className="mt-1 block text-xs text-amber-700 dark:text-amber-300">
{t("Use a wallet that supports image inscriptions, or launch with a hosted image.")}
</span>
)}
{imageTooBigToInscribe && (
<span className="mt-1 block text-xs text-red-600 dark:text-red-400">
{t(
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/components/header-wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useT } from "@/lib/i18n/client";
*/
export function HeaderWallet() {
const t = useT();
const { status, address, proofStatus } = useWallet();
const { status, address, proofStatus, connecting, connectError } = useWallet();
const menu = useWalletMenu();
const { accounts, switchAccount, disconnect } = menu;
/**
Expand Down Expand Up @@ -70,17 +70,20 @@ export function HeaderWallet() {
// — and that read is synchronous from localStorage, so it lands within a
// frame of hydration rather than after a round trip.
return (
<div className="flex shrink-0 justify-end">
<div className="relative flex shrink-0 justify-end">
{!connected ? (
<>
<button
type="button"
onClick={onClick}
disabled={connecting}
aria-busy={connecting}
className="rounded-md bg-purple-600 px-3 py-1.5 text-white hover:bg-purple-500"
>
{t("Connect")}
</button>
{installPrompt}
{connectError && <p role="alert" className="absolute right-0 top-full z-50 mt-2 w-64 rounded-lg border border-red-200 bg-white p-3 text-sm text-red-700 dark:border-red-900 dark:bg-gray-900 dark:text-red-400">{connectError}</p>}
</>
) : (
<P.Root>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function SiteHeader() {
<div className={`flex shrink-0 items-center gap-2 text-sm font-medium text-gray-600 dark:text-gray-400 min-[360px]:gap-4 ${layout === "tight-nav" ? "nav:gap-3 min-[1340px]:gap-4" : ""}`}>
{/* Telegram uses its icon on phones and in translated headers.
Status chips take turns here to leave room for the menu. */}
<span className="flex items-center gap-1 min-[360px]:gap-2 nav:hidden">
<span className={`flex items-center gap-1 min-[360px]:gap-2 nav:hidden ${process.env.NEXT_PUBLIC_CASATOOKAN_ENABLED === "true" ? "max-sm:hidden" : ""}`}>
{queued ? <MempoolChip className="max-[360px]:gap-1 max-[360px]:px-2" /> : <RewardsChip />}
<TelegramChip />
</span>
Expand All @@ -148,7 +148,7 @@ export function SiteHeader() {
<div className="hidden nav:block min-[1340px]:hidden">
<LanguageSwitch compact />
</div>
<div className="hidden nav:block">
<div className={process.env.NEXT_PUBLIC_CASATOOKAN_ENABLED === "true" ? "" : "hidden nav:block"}>
<HeaderWallet />
</div>
<MobileMenu />
Expand Down
9 changes: 8 additions & 1 deletion apps/web/src/lib/wallet/wallet-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
WalletProvider as SdkWalletProvider,
useWallet as useSdkWallet,
} from "@xcp/wallet-sdk/react";
import { CASATOOKAN_WALLET, discoverWallets, KNOWN_WALLETS } from "@xcp/wallet-sdk/web";
import "@/lib/wallet/sdk-config";

export type { ProofStatus };
Expand All @@ -28,9 +29,15 @@ const STATUS: Record<WalletReadyState, XcpWalletStatus> = {
reload_required: "disconnected",
};

const wallets = discoverWallets(
process.env.NEXT_PUBLIC_CASATOOKAN_ENABLED === "true"
? [...KNOWN_WALLETS, CASATOOKAN_WALLET]
: KNOWN_WALLETS,
);

export function WalletProvider({ children }: { children: ReactNode }) {
// Connect is login here: a wallet that proves nothing at connect (Horizon) is asked to sign the proof.
return <SdkWalletProvider proofOnConnect>{children}</SdkWalletProvider>;
return <SdkWalletProvider wallets={wallets} proofOnConnect>{children}</SdkWalletProvider>;
}

export function useWallet() {
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/locales/_source.json
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,9 @@
"components/connect-button.tsx",
"components/header-wallet.tsx"
],
"Connect a wallet that supports this inscription reveal to finish the launch.": [
"app/[lang]/create/page.tsx"
],
"Connect a wallet to chat.": [
"components/chat-panel.tsx"
],
Expand Down Expand Up @@ -3866,6 +3869,9 @@
"Upload token image": [
"app/[lang]/create/page.tsx"
],
"Use a wallet that supports image inscriptions, or launch with a hosted image.": [
"app/[lang]/create/page.tsx"
],
"Use at most {max} lines.": [
"components/chat-panel.tsx"
],
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "Sin vencimiento",
"Burned": "Quemado",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "Incluye los tokens enviados a la dirección de quema y los tokens destruidos explícitamente.",
"Destroyed": "Destruidos"
"Destroyed": "Destruidos",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "Usa una billetera compatible con inscripciones de imágenes o lanza con una imagen alojada.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "Conecta una billetera compatible con la revelación de esta inscripción para finalizar el lanzamiento."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "Sans expiration",
"Burned": "Brûlés",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "Comprend les tokens envoyés à l’adresse de destruction et les tokens explicitement détruits.",
"Destroyed": "Détruits"
"Destroyed": "Détruits",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "Utilisez un portefeuille compatible avec les inscriptions d’images, ou lancez avec une image hébergée.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "Connectez un portefeuille compatible avec la révélation de cette inscription pour terminer le lancement."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "有効期限なし",
"Burned": "バーン済み",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "バーンアドレスに送信されたトークンと、明示的に破棄されたトークンを含みます。",
"Destroyed": "破棄済み"
"Destroyed": "破棄済み",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "画像のインスクリプションに対応したウォレットを使うか、ホストされた画像でローンチしてください。",
"Connect a wallet that supports this inscription reveal to finish the launch.": "このインスクリプションのリビールに対応したウォレットを接続して、ローンチを完了してください。"
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "만료 없음",
"Burned": "소각됨",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "소각 주소로 전송된 토큰과 명시적으로 파기된 토큰을 포함합니다.",
"Destroyed": "파기됨"
"Destroyed": "파기됨",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "이미지 인스크립션을 지원하는 지갑을 사용하거나 호스팅된 이미지로 출시하세요.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "출시를 완료하려면 이 인스크립션의 리빌을 지원하는 지갑을 연결하세요."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "Sem expiração",
"Burned": "Queimados",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "Inclui tokens enviados ao endereço de queima e tokens explicitamente destruídos.",
"Destroyed": "Destruídos"
"Destroyed": "Destruídos",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "Use uma carteira compatível com inscrições de imagens ou lance com uma imagem hospedada.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "Conecte uma carteira compatível com a revelação desta inscrição para concluir o lançamento."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "Бессрочно",
"Burned": "Сожжено",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "Включает токены, отправленные на адрес сжигания, и токены, уничтоженные явно.",
"Destroyed": "Уничтожено"
"Destroyed": "Уничтожено",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "Используйте кошелёк с поддержкой записи изображений в блокчейн или запустите токен с изображением на сервере.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "Подключите кошелёк с поддержкой раскрытия этой записи, чтобы завершить запуск."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "Безстроково",
"Burned": "Спалено",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "Включає токени, надіслані на адресу спалювання, і токени, знищені явно.",
"Destroyed": "Знищено"
"Destroyed": "Знищено",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "Використовуйте гаманець із підтримкою запису зображень у блокчейн або запустіть токен із зображенням на сервері.",
"Connect a wallet that supports this inscription reveal to finish the launch.": "Підключіть гаманець із підтримкою розкриття цього запису, щоб завершити запуск."
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/zh-hk.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "永不過期",
"Burned": "已銷毀",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "包括傳送到銷毀地址的代幣及透過銷毀交易銷毀的代幣。",
"Destroyed": "已銷毀"
"Destroyed": "已銷毀",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "請使用支援圖片銘文的錢包,或使用託管圖片發行。",
"Connect a wallet that supports this inscription reveal to finish the launch.": "請連接支援此銘文揭示交易的錢包以完成發行。"
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "永不過期",
"Burned": "已銷毀",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "包括傳送到銷毀地址的代幣和透過銷毀交易銷毀的代幣。",
"Destroyed": "已銷毀"
"Destroyed": "已銷毀",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "請使用支援圖片銘文的錢包,或使用託管圖片發行。",
"Connect a wallet that supports this inscription reveal to finish the launch.": "請連接支援此銘文揭示交易的錢包以完成發行。"
}
4 changes: 3 additions & 1 deletion apps/web/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,5 +1298,7 @@
"No expiry": "永不过期",
"Burned": "已销毁",
"Includes tokens sent to the burn address and explicitly destroyed tokens.": "包括发送到销毁地址的代币和通过销毁交易销毁的代币。",
"Destroyed": "已销毁"
"Destroyed": "已销毁",
"Use a wallet that supports image inscriptions, or launch with a hosted image.": "请使用支持图片铭文的钱包,或使用托管图片发行。",
"Connect a wallet that supports this inscription reveal to finish the launch.": "请连接支持此铭文揭示交易的钱包以完成发行。"
}
Loading
Loading