solana-age-verify-sdk 2.0.0-beta.5 → 2.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/security.js +2 -1
- package/dist/verify.js +108 -62
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Solana Age Verify is a client-side biometric SDK that estimates user age and per
|
|
|
7
7
|
- **Privacy First**: No facial images are ever stored or transmitted. All AI inference happens in the user's browser (client-side).
|
|
8
8
|
- **On-Chain**: Verification results are immutable and publicly verifiable via the Solana blockchain (MemoSq4gqABAXib96qFbncnscymPme7yS4AtGf4Vb7).
|
|
9
9
|
- **Biometric**: Uses geometric face landmarks and texture analysis for liveness.
|
|
10
|
-
- **Sybil Resistance**: A
|
|
10
|
+
- **Sybil Resistance**: A minimal protocol fee (0.001 SOL) is required for each successful verification to prevent spam and fund the decentralized registry.
|
|
11
11
|
|
|
12
12
|
## Security Architecture
|
|
13
13
|
|
|
@@ -183,7 +183,7 @@ interface VerifyResult {
|
|
|
183
183
|
### "Protocol fee payment failed"
|
|
184
184
|
- User must approve the protocol fee transaction.
|
|
185
185
|
- Ensure the wallet has sufficient SOL (typically 0.001 - 0.01 SOL depending on app configuration).
|
|
186
|
-
- The default SDK fee is 0.001 SOL.
|
|
186
|
+
- The default SDK fee is **0.001 SOL** (approx. $0.15).
|
|
187
187
|
|
|
188
188
|
### "SafeToAutoRun" errors
|
|
189
189
|
- This is an internal AI error, disregard.
|
package/dist/security.js
CHANGED
|
@@ -18,7 +18,8 @@ export function getPlatformPublicKey() {
|
|
|
18
18
|
return _cachedPlatformPubKey;
|
|
19
19
|
// In a production build, this would be swapped or populated from env
|
|
20
20
|
// For now, we use the VITE environment variable if available (Vite/Vercel)
|
|
21
|
-
|
|
21
|
+
// We check VITE_TREASURY_ADDRESS first as per configuration, then fallback to legacy VITE_PLATFORM_PUBLIC_KEY
|
|
22
|
+
const envKey = import.meta.env?.VITE_TREASURY_ADDRESS || import.meta.env?.VITE_PLATFORM_PUBLIC_KEY;
|
|
22
23
|
if (envKey) {
|
|
23
24
|
_cachedPlatformPubKey = new PublicKey(envKey);
|
|
24
25
|
return _cachedPlatformPubKey;
|
package/dist/verify.js
CHANGED
|
@@ -664,82 +664,128 @@ export async function verifyHost18Plus(options) {
|
|
|
664
664
|
}
|
|
665
665
|
try {
|
|
666
666
|
const fromPubkey = options.wallet.publicKey;
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
667
|
+
// Debug checks exposed to UI via error message
|
|
668
|
+
try {
|
|
669
|
+
new PublicKey(fromPubkey);
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
throw new Error(`Invalid Wallet Public Key: ${e.message}`);
|
|
673
|
+
}
|
|
674
|
+
try {
|
|
675
|
+
new PublicKey(platformPubKey);
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
throw new Error(`Invalid Treasury Public Key (Check VITE_TREASURY_ADDRESS): ${e.message}`);
|
|
679
|
+
}
|
|
680
|
+
// Step 1: FaceHash
|
|
681
|
+
try {
|
|
682
|
+
if (embedding.length > 0) {
|
|
683
|
+
facehash = await computeFaceHash(options.walletPubkeyBase58, salt, embedding);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
throw new Error(`FaceHash Computation Failed: ${e.message}`);
|
|
670
688
|
}
|
|
671
689
|
const transaction = new Transaction();
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
|
|
690
|
+
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 50000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 }));
|
|
691
|
+
// Step 2: Protocol Fee Instruction
|
|
692
|
+
try {
|
|
693
|
+
transaction.add(SystemProgram.transfer({
|
|
694
|
+
fromPubkey,
|
|
695
|
+
toPubkey: platformPubKey,
|
|
696
|
+
lamports: protocolFeeSol * LAMPORTS_PER_SOL,
|
|
697
|
+
}));
|
|
698
|
+
}
|
|
699
|
+
catch (e) {
|
|
700
|
+
throw new Error(`Fee Transfer Instruction Failed: ${e.message}`);
|
|
701
|
+
}
|
|
682
702
|
const statusStr = isOver18 ? 'OVER18' : (isFinalStrike ? 'FINAL_FAILURE' : 'UNDER18');
|
|
683
703
|
const memoText = `Solana-Age-Registry | ${statusStr} | HASH: ${facehash} | ${verifiedAt}`;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
704
|
+
// Step 3: Memo Instruction
|
|
705
|
+
try {
|
|
706
|
+
transaction.add(new TransactionInstruction({
|
|
707
|
+
keys: [
|
|
708
|
+
{ pubkey: fromPubkey, isSigner: true, isWritable: false },
|
|
709
|
+
{ pubkey: platformPubKey, isSigner: true, isWritable: false }
|
|
710
|
+
],
|
|
711
|
+
programId: getMemoProgramId(),
|
|
712
|
+
data: new TextEncoder().encode(memoText),
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
catch (e) {
|
|
716
|
+
throw new Error(`Memo Instruction Failed: ${e.message}`);
|
|
717
|
+
}
|
|
692
718
|
const { blockhash } = await options.connection.getLatestBlockhash();
|
|
693
719
|
transaction.recentBlockhash = blockhash;
|
|
694
720
|
transaction.feePayer = fromPubkey;
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
requireAllSignatures: false,
|
|
703
|
-
verifySignatures: false
|
|
704
|
-
}).toString('base64')
|
|
705
|
-
})
|
|
706
|
-
});
|
|
707
|
-
if (!signResponse.ok) {
|
|
708
|
-
const signError = await signResponse.json();
|
|
709
|
-
throw new Error(signError.message || 'Platform signing failed');
|
|
721
|
+
// Step 4: Serialization
|
|
722
|
+
let serializedTxBase64;
|
|
723
|
+
try {
|
|
724
|
+
serializedTxBase64 = transaction.serialize({
|
|
725
|
+
requireAllSignatures: false,
|
|
726
|
+
verifySignatures: false
|
|
727
|
+
}).toString('base64');
|
|
710
728
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
729
|
+
catch (e) {
|
|
730
|
+
throw new Error(`Transaction Serialization Failed: ${e.message}`);
|
|
731
|
+
}
|
|
732
|
+
// Step 5: Server Signing
|
|
733
|
+
let platformSignedTx;
|
|
734
|
+
try {
|
|
735
|
+
const signResponse = await fetch('/api/sign-verification', {
|
|
736
|
+
method: 'POST',
|
|
737
|
+
headers: { 'Content-Type': 'application/json' },
|
|
738
|
+
body: JSON.stringify({ serializedTx: serializedTxBase64 })
|
|
739
|
+
});
|
|
740
|
+
if (!signResponse.ok) {
|
|
741
|
+
const signError = await signResponse.json();
|
|
742
|
+
throw new Error(`Server API Error: ${signError.message || signError.error}`);
|
|
743
|
+
}
|
|
744
|
+
const { transaction: txBase64 } = await signResponse.json();
|
|
745
|
+
const txData = atob(txBase64);
|
|
746
|
+
const txUint8 = new Uint8Array(txData.length);
|
|
747
|
+
for (let i = 0; i < txData.length; i++) {
|
|
748
|
+
txUint8[i] = txData.charCodeAt(i);
|
|
749
|
+
}
|
|
750
|
+
platformSignedTx = Transaction.from(txUint8);
|
|
751
|
+
}
|
|
752
|
+
catch (e) {
|
|
753
|
+
throw new Error(`Server Signing Sequence Failed: ${e.message}`);
|
|
754
|
+
}
|
|
755
|
+
// Step 6: User Signing
|
|
756
|
+
let fullSignedTx;
|
|
757
|
+
try {
|
|
758
|
+
fullSignedTx = await options.wallet.signTransaction(platformSignedTx);
|
|
759
|
+
}
|
|
760
|
+
catch (e) {
|
|
761
|
+
throw new Error(`User Signing Failed: ${e.message}`);
|
|
762
|
+
}
|
|
763
|
+
// Step 7: Broadcast
|
|
764
|
+
try {
|
|
765
|
+
protocolFeeTxId = await options.connection.sendRawTransaction(fullSignedTx.serialize(), {
|
|
766
|
+
skipPreflight: false,
|
|
767
|
+
preflightCommitment: 'confirmed'
|
|
768
|
+
});
|
|
769
|
+
const latestBlockhash = await options.connection.getLatestBlockhash('confirmed');
|
|
770
|
+
await options.connection.confirmTransaction({
|
|
771
|
+
signature: protocolFeeTxId,
|
|
772
|
+
blockhash: latestBlockhash.blockhash,
|
|
773
|
+
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
|
|
774
|
+
}, 'confirmed');
|
|
775
|
+
}
|
|
776
|
+
catch (e) {
|
|
777
|
+
throw new Error(`Transaction Broadcast Failed: ${e.message}`);
|
|
716
778
|
}
|
|
717
|
-
const platformSignedTx = Transaction.from(platformSignedTxUint8);
|
|
718
|
-
// 4. USER SIGNATURE
|
|
719
|
-
// Now the user signs the transaction that already contains the platform's signature
|
|
720
|
-
const fullSignedTx = await options.wallet.signTransaction(platformSignedTx);
|
|
721
|
-
// 5. BROADCAST
|
|
722
|
-
// Use 'confirmed' commitment for better balance of speed and reliability on Mainnet
|
|
723
|
-
protocolFeeTxId = await options.connection.sendRawTransaction(fullSignedTx.serialize(), {
|
|
724
|
-
skipPreflight: false,
|
|
725
|
-
preflightCommitment: 'confirmed'
|
|
726
|
-
});
|
|
727
|
-
const latestBlockhash = await options.connection.getLatestBlockhash('confirmed');
|
|
728
|
-
await options.connection.confirmTransaction({
|
|
729
|
-
signature: protocolFeeTxId,
|
|
730
|
-
blockhash: latestBlockhash.blockhash,
|
|
731
|
-
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
|
|
732
|
-
}, 'confirmed');
|
|
733
779
|
protocolFeePaid = true;
|
|
734
|
-
console.log('✓ On-chain proof recorded
|
|
780
|
+
console.log('✓ On-chain proof recorded');
|
|
735
781
|
}
|
|
736
782
|
catch (e) {
|
|
737
|
-
console.error('Protocol fee payment failed
|
|
738
|
-
// User rejected or tx failed - clear the facehash since it wasn't recorded
|
|
783
|
+
console.error('Protocol fee payment failed:', e);
|
|
739
784
|
facehash = '';
|
|
740
|
-
// For Community Version, we fail the verification if payment is rejected
|
|
741
785
|
isOver18 = false;
|
|
742
|
-
|
|
786
|
+
// Expose the EXACT error message to the user UI
|
|
787
|
+
failureReason = `Protocol Fee Error: ${e.message}`;
|
|
788
|
+
console.error('[Verify Debug Stack]', e.stack);
|
|
743
789
|
}
|
|
744
790
|
}
|
|
745
791
|
else if (isOver18 && (!options.wallet || !options.connection)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-age-verify-sdk",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Solana Age Verify is a premium, client-side SDK for privacy-preserving age verification and liveness detection. It generates a deterministic Face Hash linked to a wallet without storing facial data.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"dist",
|
|
11
11
|
"public"
|
|
12
12
|
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/TrenchChef/solana-age-verify-sdk.git"
|
|
16
|
+
},
|
|
13
17
|
"homepage": "https://talkchain.live",
|
|
14
18
|
"keywords": [
|
|
15
19
|
"solana",
|