@munityclubs/coin-club-verify
v0.1.0
Published
Solana Coin Club deployer verification primitives extracted from Munity.
Downloads
37
Maintainers
Readme
@munityclubs/coin-club-verify
Reusable Solana Coin Club deployer-verification primitives extracted from Munity.
The package helps a server prove that a wallet can credibly claim a Solana SPL mint, then lock a community display name to the mint's on-chain symbol.
Install
npm install @munityclubs/coin-club-verify @solana/web3.js tweetnaclWhat It Verifies
- The mint account is an initialized SPL mint.
- If
mintAuthorityis live, that authority is the verifier wallet. - If
mintAuthorityis null, the earliest mint-address transaction's first signer is used as the evidence wallet. - A wallet signature matches the exact challenge message.
- The challenge payload is bound to the expected mint, optional app subject, and TTL.
- The community name starts with
$<ON_CHAIN_SYMBOL>using Metaplex PDA or SPL Token-2022 metadata.
Single-use replay prevention still belongs to the host app: store the encoded challenge payload in an httpOnly cookie, session, or nonce table, then delete it after one verification attempt.
Basic Server Flow
import {
createDeployerChallenge,
encodeChallengePayload,
decodeChallengePayload,
validateDeployerChallenge,
buildChallengeMessage,
verifyCoinClubDeployer,
} from "@munityclubs/coin-club-verify";
// GET /challenge
const challenge = createDeployerChallenge({
origin: "https://example.com",
coinMint,
subject: userId,
});
setHttpOnlyCookie("coin_club_nonce", encodeChallengePayload(challenge.payload));
return { message: challenge.message, coinMint };
// POST /verify
const payload = decodeChallengePayload(readHttpOnlyCookie("coin_club_nonce"));
validateDeployerChallenge({ payload, coinMint, subject: userId });
deleteCookie("coin_club_nonce");
const result = await verifyCoinClubDeployer({
coinMint,
deployerWallet,
challengeMessage: buildChallengeMessage({
origin: "https://example.com",
coinMint,
nonce: payload.nonce,
issuedAt: payload.issuedAt,
}),
signatureBase64,
expectedClubName: "$MUNITY Holders",
});
console.log(result.evidence);Runnable Example
examples/server-verify-flow.js walks through all four server-side steps with
a generated keypair — no real wallet or RPC connection required:
node examples/server-verify-flow.jsStep 5 (on-chain deployer lookup) is commented out; uncomment it and set
SOLANA_RPC_URL when testing against a real mint.
Client Signing (Browser / Wallet Adapter)
The challenge message is plain UTF-8. In a browser, request the signature via the Solana Wallet Standard:
// @solana/wallet-adapter-react
const { signMessage } = useWallet();
const messageBytes = Buffer.from(challenge.message, "utf8");
const rawSignature = await signMessage(messageBytes);
const signatureBase64 = Buffer.from(rawSignature).toString("base64");
// POST { deployerWallet: publicKey.toBase58(), signatureBase64 } to /verifyFor Phantom's legacy solana.signMessage API, replace signMessage(messageBytes)
with window.solana.signMessage(messageBytes, "utf8") — the signature bytes are
the same format.
Important Limits
- This package does not store nonce state or enforce one-claim-per-mint. Your app should enforce uniqueness in its own database.
- Renounced-mint fallback depends on RPC transaction-history availability.
- Symbols are intentionally restricted to ASCII alphanumeric values so visual look-alikes cannot pass the name-lock check.
Development
yarn install
yarn test
yarn exampleLicense
Apache-2.0
