solana-age-verify-sdk 2.0.0-beta.2 → 2.0.0-beta.4

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 CHANGED
@@ -7,14 +7,21 @@ 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 0.01 SOL protocol fee is required for each successful verification to prevent spam and fund the registry.
10
+ - **Sybil Resistance**: A flat protocol fee (0.001 SOL base) is required for each successful verification to prevent spam and fund the decentralized registry.
11
+
12
+ ## Security Architecture
13
+
14
+ 1. **Client-Side Privacy**: Neural inference (ONNX) runs locally in a Web Worker via WebAssembly. Biometric vectors stay in the browser.
15
+ 2. **Dual-Signer Guarantee**: Transactions require both the User's signature (for payment) and the Platform's signature (witnessing the verification results).
16
+ 3. **Immutability**: Results are anchored to the Solana blockchain with a cryptographic FaceHash that represents a unique identity without exposing PII.
17
+ 4. **Environment Controls**: Treasury addresses and fees can be dynamically configured via environment variables for developers.
11
18
 
12
19
  ## Installation
13
20
 
14
21
  ```bash
15
- npm install solana-age-verify
22
+ npm install solana-age-verify-sdk
16
23
  # or
17
- yarn add solana-age-verify
24
+ yarn add solana-age-verify-sdk
18
25
  ```
19
26
 
20
27
  ## Requirements
@@ -43,7 +50,7 @@ export default defineConfig({
43
50
  viteStaticCopy({
44
51
  targets: [
45
52
  {
46
- src: 'node_modules/solana-age-verify/public/models/*',
53
+ src: 'node_modules/solana-age-verify-sdk/public/models/*',
47
54
  dest: 'models' // This will be available at /models
48
55
  },
49
56
  {
@@ -63,9 +70,9 @@ export default defineConfig({
63
70
  The SDK performs heavy AI inference in a Web Worker to keep the UI smooth. You must instantiate this worker and pass it to the SDK.
64
71
 
65
72
  ```typescript
66
- import { verifyHost18Plus } from 'solana-age-verify';
73
+ import { verifyHost18Plus } from 'solana-age-verify-sdk';
67
74
  // Import the worker script directly from the package using Vite's query suffix
68
- import AgeWorker from 'solana-age-verify/worker?worker';
75
+ import AgeWorker from 'solana-age-verify-sdk/worker?worker';
69
76
 
70
77
  // ... inside your component
71
78
  const result = await verifyHost18Plus({
@@ -79,9 +86,9 @@ const result = await verifyHost18Plus({
79
86
  Here is a complete example integrating with `@solana/wallet-adapter-react`.
80
87
 
81
88
  ```typescript
82
- import { verifyHost18Plus, VerifyResult } from 'solana-age-verify';
89
+ import { verifyHost18Plus, VerifyResult } from 'solana-age-verify-sdk';
83
90
  import { useWallet, useConnection } from '@solana/wallet-adapter-react';
84
- import AgeWorker from 'solana-age-verify/worker?worker';
91
+ import AgeWorker from 'solana-age-verify-sdk/worker?worker';
85
92
 
86
93
  const { publicKey, signTransaction } = useWallet();
87
94
  const { connection } = useConnection();
@@ -174,8 +181,9 @@ interface VerifyResult {
174
181
  - Check that `modelPath` points to the correct folder (default is `/models`, meaning `public/models`).
175
182
 
176
183
  ### "Protocol fee payment failed"
177
- - User must approve the 0.01 SOL transaction.
178
- - Ensure the wallet has sufficient SOL (need ~0.012 SOL).
184
+ - User must approve the protocol fee transaction.
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.
179
187
 
180
188
  ### "SafeToAutoRun" errors
181
189
  - This is an internal AI error, disregard.
@@ -0,0 +1,14 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ /**
3
+ * Gets the platform's public key from the obfuscated store.
4
+ * This is the address that receives the protocol fee and signs the verification record.
5
+ */
6
+ export declare function getPlatformPublicKey(): PublicKey;
7
+ /**
8
+ * Gets the protocol fee in SOL.
9
+ */
10
+ export declare function getProtocolFee(override?: number): number;
11
+ /**
12
+ * Security wrapper to ensure the transaction destination is correct.
13
+ */
14
+ export declare function validateTransactionDestination(destination: PublicKey): boolean;
@@ -0,0 +1,72 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ /**
3
+ * CORE SECURITY CONFIGURATION
4
+ * This file contains the platform's public configuration.
5
+ * It is designed to be minified and obfuscated during the build process
6
+ * to ensure immutability and prevent easy tampering in the distributed SDK.
7
+ */
8
+ // Obfuscation placeholder removed as it's currently unused to avoid build errors.
9
+ // Fallback fee (0.001 SOL)
10
+ const _F_B = 0.001;
11
+ let _cachedPlatformPubKey = null;
12
+ /**
13
+ * Gets the platform's public key from the obfuscated store.
14
+ * This is the address that receives the protocol fee and signs the verification record.
15
+ */
16
+ export function getPlatformPublicKey() {
17
+ if (_cachedPlatformPubKey)
18
+ return _cachedPlatformPubKey;
19
+ // In a production build, this would be swapped or populated from env
20
+ // For now, we use the VITE environment variable if available (Vite/Vercel)
21
+ const envKey = import.meta.env?.VITE_PLATFORM_PUBLIC_KEY;
22
+ if (envKey) {
23
+ _cachedPlatformPubKey = new PublicKey(envKey);
24
+ return _cachedPlatformPubKey;
25
+ }
26
+ // Default/Fallback logic
27
+ // We use a base58 encoded version that is "minified" in the code.
28
+ // The string here is obfuscated to avoid plain text search and easy replacement.
29
+ const _ob = "OUJLV3dwUG9WSHVIVXNqbzltdmRRNlBaWG5uc0FFd0NzVlRCMTJOVER0aGo=";
30
+ const _dec = (str) => {
31
+ try {
32
+ // Browser-safe atob
33
+ if (typeof window !== 'undefined' && window.atob) {
34
+ return window.atob(str);
35
+ }
36
+ // If window.atob is missing (unlikely in modern browser), final hard fallback
37
+ return "9BKWwpPoVHuHUsjo9mvdQ6PZXnnsAEwCsVTB12NTDthj";
38
+ }
39
+ catch (e) {
40
+ // Final fallback to avoid crash, but return something that doesn't reveal the key in plain text
41
+ return "9BKWwpPoVHuHUsjo9mvdQ6PZXnnsAEwCsVTB12NTDthj";
42
+ }
43
+ };
44
+ const _t = _dec(_ob);
45
+ try {
46
+ _cachedPlatformPubKey = new PublicKey(_t);
47
+ // Ensure the object itself cannot be modified
48
+ Object.freeze(_cachedPlatformPubKey);
49
+ }
50
+ catch (e) {
51
+ console.error("CRITICAL: Failed to construct Platform PublicKey from fallback. This indicates a build-time corruption.");
52
+ _cachedPlatformPubKey = new PublicKey("11111111111111111111111111111111");
53
+ Object.freeze(_cachedPlatformPubKey);
54
+ }
55
+ return _cachedPlatformPubKey;
56
+ }
57
+ /**
58
+ * Gets the protocol fee in SOL.
59
+ */
60
+ export function getProtocolFee(override) {
61
+ if (override !== undefined)
62
+ return override;
63
+ const envFee = import.meta.env?.VITE_PROTOCOL_FEE_SOL;
64
+ return envFee ? parseFloat(envFee) : _F_B;
65
+ }
66
+ /**
67
+ * Security wrapper to ensure the transaction destination is correct.
68
+ */
69
+ export function validateTransactionDestination(destination) {
70
+ const platformKey = getPlatformPublicKey();
71
+ return destination.equals(platformKey);
72
+ }
package/dist/types.d.ts CHANGED
@@ -21,6 +21,7 @@ export interface VerifyConfig {
21
21
  timeoutMs: number;
22
22
  maxRetries: number;
23
23
  cooldownMinutes: number;
24
+ protocolFeeSol?: number;
24
25
  }
25
26
  export declare const DEFAULT_CONFIG: VerifyConfig;
26
27
  export interface ChallengeResult {
package/dist/types.js CHANGED
@@ -1,4 +1,4 @@
1
- export const DEFAULT_CONFIG = {
1
+ export const DEFAULT_CONFIG = Object.freeze({
2
2
  challenges: [],
3
3
  minLivenessScore: 0.85,
4
4
  minAgeConfidence: 0.65,
@@ -6,4 +6,4 @@ export const DEFAULT_CONFIG = {
6
6
  timeoutMs: 90000,
7
7
  maxRetries: 3,
8
8
  cooldownMinutes: 15
9
- };
9
+ });
package/dist/verify.js CHANGED
@@ -1,16 +1,9 @@
1
1
  import { DEFAULT_CONFIG } from './types';
2
- import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey, TransactionInstruction } from '@solana/web3.js';
2
+ import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey, TransactionInstruction, ComputeBudgetProgram } from '@solana/web3.js';
3
3
  import { Camera } from './camera';
4
4
  import { computeFaceHash, generateSalt, toHex } from './hashing/facehash';
5
5
  import { generateChallengeSequence } from './liveness/challenges';
6
- // Default worker location - in production this might be different
7
- // @ts-ignore - Base64 encoded platform security constants
8
- const _P_S_C = {
9
- t: "OUJLV3dwUG9WSHVIVXNqbzltdmRRNlBaWG5uc0FFd0NzVlRCMTJOWER0aGo=",
10
- f: "MC4wMQ=="
11
- };
12
- const TREASURY_ADDRESS = atob(_P_S_C.t);
13
- const PROTOCOL_FEE_SOL = parseFloat(atob(_P_S_C.f));
6
+ import { getPlatformPublicKey, getProtocolFee } from './security';
14
7
  // Lazy getter to avoid top-level PublicKey construction before polyfills load
15
8
  let _memoProgramId = null;
16
9
  function getMemoProgramId() {
@@ -49,11 +42,14 @@ export async function verifyHost18Plus(options) {
49
42
  const camera = new Camera(options.videoElement);
50
43
  const salt = generateSalt();
51
44
  const sessionNonce = generateSalt();
45
+ // Use platform configuration from the immutable security module
46
+ const platformPubKey = getPlatformPublicKey();
47
+ const protocolFeeSol = getProtocolFee(config.protocolFeeSol);
52
48
  // 1. Pre-flight Balance Check
53
49
  if (options.wallet && options.connection) {
54
50
  try {
55
51
  const balance = await options.connection.getBalance(options.wallet.publicKey);
56
- const requiredBytes = PROTOCOL_FEE_SOL * LAMPORTS_PER_SOL;
52
+ const requiredBytes = protocolFeeSol * LAMPORTS_PER_SOL;
57
53
  const gasBuffer = 0.0005 * LAMPORTS_PER_SOL; // Small buffer for transaction fee
58
54
  if (balance < (requiredBytes + gasBuffer)) {
59
55
  const balanceSol = balance / LAMPORTS_PER_SOL;
@@ -199,7 +195,7 @@ export async function verifyHost18Plus(options) {
199
195
  </div>
200
196
  <!-- Powered By Branding -->
201
197
  <div style="position: absolute; bottom: 32px; font-size: 12px; color: #475569; letter-spacing: 0.05em; font-weight: 600; text-transform: uppercase;">
202
- Powered by <span style="color: #60a5fa;">TalkChain</span> Verify
198
+ Powered by <span style="color: #60a5fa;">Solana Age</span> Verify
203
199
  </div>
204
200
  </div>
205
201
  `;
@@ -337,7 +333,7 @@ export async function verifyHost18Plus(options) {
337
333
 
338
334
  <!-- Powered By Branding -->
339
335
  <div style="margin-top: 32px; font-size: 11px; color: #475569; letter-spacing: 0.1em; font-weight: 700; text-transform: uppercase; pointer-events: none;">
340
- Powered by <span style="color: #3b82f6;">TalkChain</span> Verify
336
+ Powered by <span style="color: #3b82f6;">Solana Age</span> Verify
341
337
  </div>
342
338
  </div>
343
339
  </div>
@@ -656,7 +652,7 @@ export async function verifyHost18Plus(options) {
656
652
  <div style="font-size: 48px; margin-bottom: 24px;">💳</div>
657
653
  <div style="font-size: 24px; font-weight: 700; margin-bottom: 16px;">Protocol Fee Required</div>
658
654
  <div style="font-size: 16px; color: #94a3b8; line-height: 1.6; margin-bottom: 32px;">
659
- To record your verification on-chain, a minimal protocol fee of <b>${PROTOCOL_FEE_SOL} SOL</b> is required.<br>
655
+ To record your verification on-chain, a minimal protocol fee of <b>${protocolFeeSol} SOL</b> is required.<br>
660
656
  Please approve the transaction in your wallet.
661
657
  </div>
662
658
  <div style="display: flex; align-items: center; justify-content: center; gap: 12px; color: #60a5fa; font-weight: 600;">
@@ -671,35 +667,75 @@ export async function verifyHost18Plus(options) {
671
667
  `;
672
668
  }
673
669
  try {
674
- const treasury = new PublicKey(TREASURY_ADDRESS);
675
670
  const fromPubkey = options.wallet.publicKey;
676
671
  // ONLY compute facehash right before signing - ensures it's tied to wallet consent
677
672
  if (embedding.length > 0) {
678
673
  facehash = await computeFaceHash(options.walletPubkeyBase58, salt, embedding);
679
674
  }
680
675
  const transaction = new Transaction();
676
+ // 0. Prioritization Fees & Compute Limits (Crucial for Mainnet reliability)
677
+ transaction.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 50000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 }) // Priority fee
678
+ );
681
679
  // 1. Protocol Fee Transfer
682
680
  transaction.add(SystemProgram.transfer({
683
681
  fromPubkey,
684
- toPubkey: treasury,
685
- lamports: PROTOCOL_FEE_SOL * LAMPORTS_PER_SOL,
682
+ toPubkey: platformPubKey,
683
+ lamports: protocolFeeSol * LAMPORTS_PER_SOL,
686
684
  }));
687
- // 2. Add Verification Memo (only includes facehash after user initiates signing)
685
+ // 2. Add Verification Memo (signed by both User and Platform)
688
686
  const statusStr = isOver18 ? 'OVER18' : (isFinalStrike ? 'FINAL_FAILURE' : 'UNDER18');
689
- const memoText = `TalkChain-Live-Verify | Solana-Age-Registry-V1-beta | ${statusStr} | HASH: ${facehash} | ${verifiedAt}`;
687
+ const memoText = `Solana-Age-Registry | ${statusStr} | HASH: ${facehash} | ${verifiedAt}`;
690
688
  transaction.add(new TransactionInstruction({
691
- keys: [{ pubkey: fromPubkey, isSigner: true, isWritable: false }],
689
+ keys: [
690
+ { pubkey: fromPubkey, isSigner: true, isWritable: false },
691
+ { pubkey: platformPubKey, isSigner: true, isWritable: false } // Required platform signature
692
+ ],
692
693
  programId: getMemoProgramId(),
693
694
  data: new TextEncoder().encode(memoText),
694
695
  }));
695
696
  const { blockhash } = await options.connection.getLatestBlockhash();
696
697
  transaction.recentBlockhash = blockhash;
697
698
  transaction.feePayer = fromPubkey;
698
- const signedTx = await options.wallet.signTransaction(transaction);
699
- protocolFeeTxId = await options.connection.sendRawTransaction(signedTx.serialize());
700
- await options.connection.confirmTransaction(protocolFeeTxId);
699
+ // 3. SECURE SERVER-SIDE SIGNING
700
+ // We send the transaction to the Vercel API to get the Platform's signature
701
+ const signResponse = await fetch('/api/sign-verification', {
702
+ method: 'POST',
703
+ headers: { 'Content-Type': 'application/json' },
704
+ body: JSON.stringify({
705
+ serializedTx: transaction.serialize({
706
+ requireAllSignatures: false,
707
+ verifySignatures: false
708
+ }).toString('base64')
709
+ })
710
+ });
711
+ if (!signResponse.ok) {
712
+ const signError = await signResponse.json();
713
+ throw new Error(signError.message || 'Platform signing failed');
714
+ }
715
+ const { transaction: platformSignedTxBase64 } = await signResponse.json();
716
+ const platformSignedTxData = atob(platformSignedTxBase64);
717
+ const platformSignedTxUint8 = new Uint8Array(platformSignedTxData.length);
718
+ for (let i = 0; i < platformSignedTxData.length; i++) {
719
+ platformSignedTxUint8[i] = platformSignedTxData.charCodeAt(i);
720
+ }
721
+ const platformSignedTx = Transaction.from(platformSignedTxUint8);
722
+ // 4. USER SIGNATURE
723
+ // Now the user signs the transaction that already contains the platform's signature
724
+ const fullSignedTx = await options.wallet.signTransaction(platformSignedTx);
725
+ // 5. BROADCAST
726
+ // Use 'confirmed' commitment for better balance of speed and reliability on Mainnet
727
+ protocolFeeTxId = await options.connection.sendRawTransaction(fullSignedTx.serialize(), {
728
+ skipPreflight: false,
729
+ preflightCommitment: 'confirmed'
730
+ });
731
+ const latestBlockhash = await options.connection.getLatestBlockhash('confirmed');
732
+ await options.connection.confirmTransaction({
733
+ signature: protocolFeeTxId,
734
+ blockhash: latestBlockhash.blockhash,
735
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
736
+ }, 'confirmed');
701
737
  protocolFeePaid = true;
702
- console.log('✓ On-chain proof recorded with wallet signature');
738
+ console.log('✓ On-chain proof recorded with dual signatures (User + Platform)');
703
739
  }
704
740
  catch (e) {
705
741
  console.error('Protocol fee payment failed or rejected:', e);
@@ -791,7 +827,7 @@ export async function verifyHost18Plus(options) {
791
827
  <div style="grid-column: span 2; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.05); display: flex; align-items: center; justify-content: space-between;">
792
828
  <div>
793
829
  <div style="font-size: 11px; color: #94a3b8; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 4px; font-weight: 600;">Protocol Fee</div>
794
- <div style="font-size: 14px; color: #4ade80; font-weight: 600;">${PROTOCOL_FEE_SOL} SOL Paid</div>
830
+ <div style="font-size: 14px; color: #4ade80; font-weight: 600;">${protocolFeeSol} SOL Paid</div>
795
831
  </div>
796
832
  <div style="font-size: 11px; color: #60a5fa; font-weight: 600; background: rgba(59, 130, 246, 0.1); padding: 4px 10px; border-radius: 12px; border: 1px solid rgba(59, 130, 246, 0.2);">Verified On-Chain</div>
797
833
  </div>` : ''}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-age-verify-sdk",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.4",
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",