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
101 changes: 101 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"@electric-sql/pglite": "^0.3.15",
"@lifi/intent": "0.0.4",
"@metamask/sdk": "^0.34.0",
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-adapter-phantom": "^0.9.29",
"@solana/wallet-adapter-solflare": "^0.6.33",
"@sveltejs/adapter-cloudflare": "^7.0.3",
"@wagmi/connectors": "^7.2.1",
"@wagmi/core": "^3.4.0",
Expand Down
6 changes: 6 additions & 0 deletions src/hooks.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Buffer } from "buffer";
// Buffer is not available in browser environments by default.
// Polyfill it globally so Solana/Anchor libraries that depend on Node's Buffer
// can run client-side. This hook runs only in the browser (never SSR), which
// is the correct place for browser-only globals.
globalThis.Buffer = Buffer;
59 changes: 59 additions & 0 deletions src/lib/components/SolanaWalletButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { AVAILABLE_WALLETS } from "$lib/utils/solana-wallet.svelte";
import solanaWallet from "$lib/utils/solana-wallet.svelte";

let selectedWalletIndex = $state(0);
let connecting = $state(false);
let error = $state<string | null>(null);

const truncate = (key: string) => `${key.slice(0, 4)}…${key.slice(-4)}`;

async function connect() {
error = null;
connecting = true;
try {
await solanaWallet.connect(AVAILABLE_WALLETS[selectedWalletIndex].adapter);
} catch (e: unknown) {
error = e instanceof Error ? e.message : "Connection failed";
} finally {
connecting = false;
}
}

async function disconnect() {
await solanaWallet.disconnect();
error = null;
}
</script>

{#if solanaWallet.connected && solanaWallet.publicKey}
<div class="flex items-center gap-1">
<span class="rounded bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700">
◎ {truncate(solanaWallet.publicKey)}
</span>
<button class="cursor-pointer text-xs text-gray-400 hover:text-red-500" onclick={disconnect}>
</button>
</div>
{:else}
<div class="flex items-center gap-1">
<select
class="rounded border border-gray-200 bg-white px-1 py-0.5 text-xs text-gray-700"
bind:value={selectedWalletIndex}
>
{#each AVAILABLE_WALLETS as wallet, i (wallet.name)}
<option value={i}>{wallet.name}</option>
{/each}
</select>
<button
class="cursor-pointer rounded border border-gray-200 bg-white px-2 py-0.5 text-xs font-medium text-gray-700 hover:border-sky-300 hover:text-sky-700 disabled:cursor-not-allowed disabled:text-gray-400"
disabled={connecting}
onclick={connect}
>
{connecting ? "Connecting…" : "Connect Solana"}
</button>
</div>
{#if error}
<p class="mt-0.5 text-xs text-red-500">{error}</p>
{/if}
{/if}
15 changes: 15 additions & 0 deletions src/lib/screens/IssueIntent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import { isAddress } from "viem";
import { isValidSolanaAddress, solanaAddressToBytes32 } from "$lib/utils/solana";
import { SOLANA_CHAIN_IDS } from "$lib/config";
import SolanaWalletButton from "$lib/components/SolanaWalletButton.svelte";
import solanaWallet from "$lib/utils/solana-wallet.svelte";

const bigIntSum = (...nums: bigint[]) => nums.reduce((a, b) => a + b, 0n);

Expand Down Expand Up @@ -55,6 +57,13 @@
return resolveEvmRecipient(store.recipient);
});

// Auto-fill solana recipient from connected wallet (only when field is empty).
$effect(() => {
if (solanaWallet.publicKey && store.solanaRecipient.trim() === "") {
store.solanaRecipient = solanaWallet.publicKey;
}
});

// A valid Solana recipient is required to encode a usable cross-chain intent.
const solanaRecipientMissing = $derived(hasSolanaOutput && !outputRecipient);

Expand Down Expand Up @@ -301,6 +310,12 @@

<SectionCard compact>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1.5">
<span class="text-[11px] font-semibold whitespace-nowrap text-gray-500"
>Solana Wallet</span
>
<SolanaWalletButton />
</div>
{#if hasEvmOutput}
<div class="flex min-w-0 items-center gap-1">
<span class="text-[11px] font-semibold whitespace-nowrap text-gray-500"
Expand Down
59 changes: 59 additions & 0 deletions src/lib/utils/solana-wallet.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import { SolflareWalletAdapter } from "@solana/wallet-adapter-solflare";
import type { SignerWalletAdapter } from "@solana/wallet-adapter-base";

export const AVAILABLE_WALLETS = [
{ name: "Phantom", adapter: new PhantomWalletAdapter() },
{ name: "Solflare", adapter: new SolflareWalletAdapter() }
] as const;

class SolanaWalletStore {
#adapter = $state<SignerWalletAdapter | null>(null);
#connected = $state(false);
#publicKey = $state<string | null>(null);

get adapter(): SignerWalletAdapter | null {
return this.#adapter;
}

get connected(): boolean {
return this.#connected;
}

/** Base58-encoded public key of the connected wallet, or null */
get publicKey(): string | null {
return this.#publicKey;
}

async connect(adapter: SignerWalletAdapter): Promise<void> {
if (this.#adapter?.connected) {
this.#adapter.off("connect");
this.#adapter.off("disconnect");
await this.#adapter.disconnect();
}
this.#adapter = adapter;
adapter.on("connect", (pk) => {
this.#connected = true;
this.#publicKey = pk.toBase58();
});
adapter.on("disconnect", () => {
this.#connected = false;
this.#publicKey = null;
});
await adapter.connect();
}

async disconnect(): Promise<void> {
if (this.#adapter) {
this.#adapter.off("connect");
this.#adapter.off("disconnect");
await this.#adapter.disconnect();
}
this.#adapter = null;
this.#connected = false;
this.#publicKey = null;
}
}

const solanaWallet = new SolanaWalletStore();
export default solanaWallet;
Loading