To initiate a card request, the user connects their wallet via a Web3 interface (e.g., MetaMask, TonConnect, WalletConnect). The system prompts them to sign a nonce-based message:
I am the owner of this wallet. Requesting Xuna card at [timestamp]. Nonce: abc123
This signature, along with the public address, is submitted to the backend or smart contract, which verifies authenticity without storing the user’s private key or metadata. Optionally, possession of a specific NFT can be checked via ownerOf() methods on-chain.
Wallet Verification Process
Connect Wallet
User connects Web3 wallet
Sign Message
Cryptographic proof of ownership
Verify Signature
Backend validates signature
Check NFT (Optional)
Verify membership tier
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract XunaVerification {
// Verify a signature is valid for a given address and message
function verifySignature(
address _signer,
string memory _message,
bytes memory _signature
) public pure returns (bool) {
bytes32 messageHash = keccak256(abi.encodePacked(_message));
bytes32 ethSignedMessageHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
);
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(ethSignedMessageHash, v, r, s) == _signer;
}
// Split signature into r, s, v components
function splitSignature(bytes memory sig)
internal
pure
returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
if (v < 27) {
v += 27;
}
return (r, s, v);
}
}