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
5 changes: 5 additions & 0 deletions app/public/.well-known/security.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Contact: https://connect.devfridge.cool
Expires: 2027-09-01T00:00:00.000Z
Preferred-Languages: en, it
Policy: https://docs.devfridge.cool/security
Canonical: https://devfridge.cool/.well-known/security.txt
2 changes: 2 additions & 0 deletions app/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
75 changes: 69 additions & 6 deletions app/src/hooks/usePhantom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,62 @@ type PhantomProvider = {
) => Promise<{ signature: string }>;
};

type SimulationResponse = {
error?: { message?: string };
result?: { value?: { err?: unknown; logs?: string[] | null } };
};

function bytesToBase64(bytes: Uint8Array): string {
let binary = "";
const chunkSize = 0x8000;
for (let i = 0; i < bytes.length; i += chunkSize) {
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
}
return btoa(binary);
}

async function simulateBeforeSigning(
connection: Connection,
tx: Transaction | VersionedTransaction
): Promise<void> {
const wire =
tx instanceof Transaction
? tx.serialize({ requireAllSignatures: false, verifySignatures: false })
: tx.serialize();
const response = await fetch(connection.rpcEndpoint, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "devfridge-preflight",
method: "simulateTransaction",
params: [
bytesToBase64(wire),
{
encoding: "base64",
commitment: "confirmed",
sigVerify: false,
replaceRecentBlockhash: false,
},
],
}),
});
if (!response.ok) {
throw new Error(`Transaction simulation request failed (${response.status})`);
}
const body = (await response.json()) as SimulationResponse;
if (body.error) {
throw new Error(body.error.message || "Transaction simulation failed");
}
const simulationError = body.result?.value?.err;
if (simulationError) {
const logs = body.result?.value?.logs?.slice(-3).join(" | ");
throw new Error(
`Transaction would fail on-chain: ${JSON.stringify(simulationError)}${logs ? ` (${logs})` : ""}`
);
}
}

function getProvider(): PhantomProvider | null {
const w = window as Window & {
phantom?: { solana?: PhantomProvider };
Expand Down Expand Up @@ -80,27 +136,34 @@ export function usePhantom(cluster: ClusterName, fallbackEndpoint?: string) {
tx.recentBlockhash = latest.blockhash;
tx.lastValidBlockHeight = latest.lastValidBlockHeight;
}
if (extraSigners.length) tx.partialSign(...extraSigners);
}

// Phantom recommends simulating with sigVerify=false before requesting a
// signature. This catches deterministic failures before the wallet dialog.
await simulateBeforeSigning(connection, tx);

// Always submit on DevFridge's selected cluster RPC. Phantom's
// signAndSendTransaction uses the wallet's network, which is often
// still Mainnet when the site is on Devnet/Testnet.
if (p.signTransaction) {
const signed = await p.signTransaction(tx);
if (signed instanceof Transaction) {
const out = Transaction.from(
signed.serialize({ requireAllSignatures: false, verifySignatures: false })
);
if (extraSigners.length) out.partialSign(...extraSigners);
return connection.sendRawTransaction(out.serialize(), sendOpts);
// Phantom signs first; any local co-signers are added afterwards.
// This ordering follows Phantom's transaction-warning guidance.
if (extraSigners.length) signed.partialSign(...extraSigners);
return connection.sendRawTransaction(signed.serialize(), sendOpts);
}
if (extraSigners.length) signed.sign(extraSigners);
return connection.sendRawTransaction(
(signed as VersionedTransaction).serialize(),
sendOpts
);
}

if (extraSigners.length) {
throw new Error("This transaction requires a wallet that supports signTransaction");
}

const { signature } = await p.signAndSendTransaction(tx);
return signature;
}, [connection, publicKey]);
Expand Down
5 changes: 5 additions & 0 deletions scan/public/.well-known/security.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Contact: https://connect.devfridge.cool
Expires: 2027-09-01T00:00:00.000Z
Preferred-Languages: en, it
Policy: https://docs.devfridge.cool/security
Canonical: https://scan.devfridge.cool/.well-known/security.txt
12 changes: 12 additions & 0 deletions team/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DevFridge — Team</title>
<meta
name="description"
content="Official DevFridge team directory. Team roles are assigned by the admin wallet and independently verified against public Solana Token-2022 timelocks."
/>
<link rel="canonical" href="https://team.devfridge.cool/" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://team.devfridge.cool/" />
<meta property="og:title" content="DevFridge Team — on-chain verified roles" />
<meta
property="og:description"
content="Official team directory backed by publicly verifiable DevFridge timelocks on Solana."
/>
<script>
window.global = window;
window.process = window.process || { env: { NODE_ENV: "production" } };
Expand Down
5 changes: 5 additions & 0 deletions team/public/.well-known/security.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Contact: https://connect.devfridge.cool
Expires: 2027-09-01T00:00:00.000Z
Preferred-Languages: en, it
Policy: https://docs.devfridge.cool/security
Canonical: https://team.devfridge.cool/.well-known/security.txt
2 changes: 2 additions & 0 deletions team/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
Loading