Skip to content
Merged
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
Binary file added assets/images/webp/giddy-receive.webp
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 assets/images/webp/giddy-send.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 62 additions & 80 deletions assets/js/webapp/swapUtils.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,100 @@
// assets/js/webapp/swapUtils.js

// Helper to handle cancellations silently
const handleCancel = (err) => {
const msg = err?.message || "";
if (msg.includes("rejected") || msg.includes("cancelled") || msg.includes("user declined")) {
console.log("[Ultra] Swap cancelled");
throw new Error("USER_REJECTED");
}
return false;
};

// ==================== ULTRA SWAP (Primary) ====================
async function performUltraSwap(inputMint, outputMint, rawAmount, provider, connectedWallet) {
try {
console.log(`[Ultra] Requesting quote: ${rawAmount} from ${inputMint} to ${outputMint}`);

const ultraRes = await fetch(
`https://lite-api.jup.ag/ultra/v1/order?inputMint=${inputMint}&outputMint=${outputMint}&amount=${rawAmount}&taker=${connectedWallet}`
);

if (!ultraRes.ok) {
const errText = await ultraRes.text().catch(() => '');
throw new Error(`Ultra HTTP ${ultraRes.status}: ${errText}`);
}

if (!ultraRes.ok) throw new Error(`Ultra HTTP ${ultraRes.status}`);
const quote = await ultraRes.json();

if (quote.error) throw new Error(`Ultra Quote Error: ${JSON.stringify(quote.error)}`);
if (!quote.transaction) throw new Error("Ultra returned no transaction");

console.log(`[Ultra] Success | Router: ${quote.router || 'unknown'}`);


return await executeUltraTransaction(quote, provider);
} catch (error) {
// Suppress logging if the user simply cancelled the swap
if (error.message !== "USER_REJECTED") {
console.error("[Ultra] Failed:", error.message);
}
if (!handleCancel(error)) console.error("[Ultra] Failed:", error.message);
throw error;
}
}

// ==================== SMART SIGNING (Injected + Browser SDK) ====================
// ==================== SMART SIGNING ====================
async function executeUltraTransaction(quote, provider) {
try {
const txBuffer = Buffer.from(quote.transaction, 'base64');
const vtx = solanaWeb3.VersionedTransaction.deserialize(txBuffer);

const connectionMethod = window.connectionMethod || localStorage.getItem('connection_method');
let signedTx;

// 1. PHANTOM BROWSER SDK (Google / Apple)
// ==================== GOOGLE / APPLE (Embedded Wallet) ====================
if (connectionMethod === 'google' || connectionMethod === 'apple') {
console.log("[Ultra] Signing with Phantom Browser SDK (solana namespace)...");
console.log("[Ultra] Embedded wallet detected → Using v1 proxy + SDK signing");
console.log(`[Ultra] Connected wallet: ${window.connectedWallet}`);

const proxyRes = await fetch('https://giddy-key-swaps-production.up.railway.app/api/swap/v1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inputMint: quote.inputMint || "So11111111111111111111111111111111111111112",
outputMint: quote.outputMint,
rawAmount: quote.inAmount,
connectedWallet: window.connectedWallet
})
});

const data = await proxyRes.json();
if (!data.success) throw new Error(data.error || "v1 proxy failed");

const tx = solanaWeb3.VersionedTransaction.deserialize(Buffer.from(data.swapTransaction, "base64"));

const sdk = await window.getPhantomSDK?.();
if (!sdk?.solana) throw new Error("Phantom SDK solana provider not available");

try {
signedTx = await sdk.solana.signTransaction(vtx);
} catch (err) {
// Check for user rejection
if (err.message && (err.message.includes("rejected") || err.message.includes("cancelled"))) {
throw new Error("USER_REJECTED");
}
throw err;
}
}
// 2. INJECTED PHANTOM (Extension / In-App)
else {
console.log("[Ultra] Signing with Injected Phantom Provider...");
try {
signedTx = await provider.signTransaction(vtx);
} catch (err) {
if (err.message && (err.message.includes("rejected") || err.message.includes("cancelled"))) {
throw new Error("USER_REJECTED");
}
throw err;
}
if (!sdk?.solana) throw new Error("Phantom SDK not available");

console.log("[Ultra] Requesting SDK signAndSendTransaction...");
// Removed presignTransaction hook to prevent 403 Forbidden on /prepare
const result = await sdk.solana.signAndSendTransaction(tx);

console.log("[Ultra] SDK Success, TXID:", result.signature);
return { success: true, txid: result.signature, router: "v1-proxy" };
}

// ==================== INJECTED PHANTOM ====================
console.log("[Ultra] Signing with Injected Phantom...");
if (!provider || typeof provider.signTransaction !== 'function') {
throw new Error("No valid injected provider");
}

// Serialize and Execute
const signedBytes = signedTx.serialize();
const signedTxBase64 = btoa(String.fromCharCode(...new Uint8Array(signedBytes)));
const vtx = solanaWeb3.VersionedTransaction.deserialize(Buffer.from(quote.transaction, 'base64'));
const signed = await provider.signTransaction(vtx);
const signedTxBase64 = btoa(String.fromCharCode(...new Uint8Array(signed.serialize())));

const executeRes = await fetch('https://lite-api.jup.ag/ultra/v1/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
signedTransaction: signedTxBase64,
requestId: quote.requestId
})
body: JSON.stringify({ signedTransaction: signedTxBase64, requestId: quote.requestId })
});

if (!executeRes.ok) {
const errText = await executeRes.text().catch(() => '');
throw new Error(`Ultra Execute Failed: ${executeRes.status} ${errText}`);
}

if (!executeRes.ok) throw new Error(`Ultra Execute Failed: ${executeRes.status}`);
const result = await executeRes.json();
return {
success: true,
txid: result.signature,
router: quote.router || 'OKX/DFlow'
};

return { success: true, txid: result.signature, router: quote.router || 'OKX/DFlow' };
} catch (error) {
if (error.message !== "USER_REJECTED") {
console.error("[Ultra] Execute failed:", error);
}
if (!handleCancel(error)) console.error("[Ultra] Execute failed:", error);
throw error;
}
}

// ==================== V1 FALLBACK ====================
async function performV1Swap(inputMint, outputMint, rawAmount, provider, connectedWallet) {
try {
const proxyUrl = getApiUrl('/api/swap/v1');
const proxyRes = await fetch(proxyUrl, {
const proxyRes = await fetch('https://giddy-key-swaps-production.up.railway.app/api/swap/v1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inputMint, outputMint, rawAmount, connectedWallet })
Expand All @@ -116,19 +103,11 @@ async function performV1Swap(inputMint, outputMint, rawAmount, provider, connect
const data = await proxyRes.json();
if (!data.success) throw new Error(data.error || "v1 proxy failed");

const tx = solanaWeb3.VersionedTransaction.deserialize(
Buffer.from(data.swapTransaction, "base64")
);

const tx = solanaWeb3.VersionedTransaction.deserialize(Buffer.from(data.swapTransaction, "base64"));
const result = await provider.signAndSendTransaction(tx);

return {
success: true,
txid: result.signature,
version: "v1"
};
return { success: true, txid: result.signature, version: "v1" };
} catch (error) {
console.error("[V1 Proxy] Failed:", error.message);
if (!handleCancel(error)) console.error("[V1 Proxy] Failed:", error.message);
throw error;
}
}
Expand All @@ -137,4 +116,7 @@ async function performV1Swap(inputMint, outputMint, rawAmount, provider, connect
window.performUltraSwap = performUltraSwap;
window.performV1Swap = performV1Swap;

console.log("✅ swapUtils.js loaded (Robust SDK, Provider support + Silent Cancel)");
// Dispatch ready event
window.dispatchEvent(new CustomEvent('swap-engine-ready'));

console.log("✅ swapUtils.js loaded and READY");
Loading
Loading