import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { generateKeyPairSync, sign } from 'node:crypto'; import { Keypair, PublicKey, SYSVAR_CLOCK_PUBKEY } from '@solana/web3.js'; import { audienceBytes, checkDeployment, createGrantIx, DEVNET_GENESIS, grantPda, normalizedGrant, readGrant, vaultPda } from '../lib/registry.mjs'; import { connectWallet, disconnectWallet, getWalletSession, onWalletSessionChange, hasSamplePass, listGrants, reviewGrant, revokeAll, revokeGrant, sampleAddresses, submitGrant } from '../lib/wallet.mjs'; import { ALL_APPS, AUDIENCE, LEGACY_MAIL, siteOrigin, appPath } from '../lib/partners.mjs'; import { getMetadataAccountDataSerializer, TokenStandard, Key } from '@metaplex-foundation/mpl-token-metadata'; import { checkDelegation, authenticateChallenge, checkHolderEligibility, findHolderEligibility, issueChallenge } from './partner-verifier.mjs'; const {owner,delegate,asset}=sampleAddresses; const expiry=new Date(Date.now()+3_600_000).toISOString(); const program=Keypair.generate().publicKey; const audience=await audienceBytes(); const grant=normalizedGrant(owner,{delegate,asset,scope:'asset',expiresAt:expiry},Date.now()/1000); test('only a reviewed sample grant can be applied; renewal, direction and revocation match the public API',async t=>{ const observed=[]; t.after(onWalletSessionChange(session=>observed.push(session))); assert.equal(getWalletSession(),null); const session=await connectWallet('sample'); assert.equal(getWalletSession(),session); assert.deepEqual(observed,[session]); assert.equal(await connectWallet('sample'),session); assert.equal((await listGrants(session)).length,0); await assert.rejects(submitGrant(session,{...grant}),/Review/); const result=await submitGrant(session,await reviewGrant(session,grant)); assert.equal(result.signature,null); assert.equal((await listGrants(session))[0].status,'active'); assert.deepEqual(await listGrants(session,{direction:'incoming'}),[]); await revokeGrant(session,result.grant); assert.equal((await listGrants(session))[0].status,'revoked'); await submitGrant(session,await reviewGrant(session,grant)); assert.equal((await listGrants(session)).length,1); await revokeAll(session); assert.equal((await listGrants(session))[0].status,'revoked'); await disconnectWallet();await assert.rejects(listGrants(session),/Reconnect/); assert.equal(getWalletSession(),null); assert.deepEqual(observed,[session,null]); }); test('universal and restricted permissions stay distinct in two independent apps',async()=>{ const session=await connectWallet('sample'); const a='https://gallery.example',b='https://club.example'; for (const audience of [AUDIENCE,LEGACY_MAIL,a]) { await submitGrant(session,await reviewGrant(session,{...grant,audience})); assert.equal(hasSamplePass(a),audience===a); assert.equal(hasSamplePass(b),false); await revokeAll(session); } const review=await reviewGrant(session,{...grant,scope:'collection',asset:sampleAddresses.collection,audience:ALL_APPS}); const {grant:record}=await submitGrant(session,review); assert.ok(hasSamplePass(a) && hasSamplePass(b)); assert.equal(hasSamplePass(b,owner),false); await revokeGrant(session,record); assert.equal(hasSamplePass(a),false); await assert.rejects(reviewGrant(session,{...grant,audience:'https://club.example/path'})); await disconnectWallet(); }); test('app setup accepts independent origins and nested pages',()=>{ assert.equal(siteOrigin('https://NEW.example/'),'https://new.example'); for(const url of ['http://wrong.example','https://name@wrong.example','https://host.example/?q=1',ALL_APPS]) assert.throws(()=>siteOrigin(url)); assert.equal(appPath('/members/entry'),'/members/entry'); assert.equal(appPath('/'),'/'); for(const path of ['//other.example','/path/..','/?']) assert.throws(()=>appPath(path)); }); test('rejects invalid scopes, self delegation, bad expiry and readonly account addresses',()=>{ for(const draft of [{...grant,delegate:owner},{...grant,scope:'all'},{...grant,expiresAt:'1971-01-01'}, {...grant,asset:'invalid'}, {...grant,delegate:grantPda(program,owner,delegate,audience,'asset',asset).toBase58()}]) assert.throws(()=>normalizedGrant(owner,draft,Date.now()/1000)); }); test('client ABI matches the canonical registry grant layout',()=>{ const ix=createGrantIx(program,grant,audience); assert.equal(ix.data.length,106); assert.equal(ix.keys.filter(k=>k.isSigner).length,1); assert.equal(ix.keys[0].pubkey.toBase58(),owner); assert.equal(ix.data[1],1); assert.ok(ix.data.subarray(34,66).equals(audience)); const data=Buffer.alloc(146);data[0]=2; new PublicKey(owner).toBuffer().copy(data,1); ix.data.subarray(2,66).copy(data,33); data[97]=ix.data[1];ix.data.subarray(66,98).copy(data,98); ix.data.subarray(98).copy(data,138); const info={owner:program,data}; assert.equal(readGrant(program,ix.keys[2].pubkey,info).expiresAt,grant.expiresAt); assert.throws(()=>readGrant(program,Keypair.generate().publicKey,info),/canonical/); }); test('issued sign-in cannot be reused or carried between sessions',async()=>{ const {publicKey,privateKey}=generateKeyPairSync('ed25519'); const who=new PublicKey(publicKey.export({format:'der',type:'spki'}).subarray(-32)).toBase58(); const records=new Map(),store={save:(id,x)=>records.set(id,x),get:id=>records.get(id),consume:id=>records.delete(id)}; const sessionId='session-A',challenge=await issueChallenge({delegate:who,sessionId,store}); const signature=sign(null,Buffer.from(challenge.message),privateKey).toString('base64'); const args={id:challenge.id,sessionId,store,signature}; await assert.rejects(authenticateChallenge({...args,sessionId:'session-B'})); await assert.rejects(authenticateChallenge({...args,origin:'https://another.example'})); await assert.rejects(authenticateChallenge({...args,signature:Buffer.alloc(64).toString('base64')})); assert.equal(await authenticateChallenge(args),who); await assert.rejects(authenticateChallenge(args)); }); test('partner uses one finalized view to detect revocation, resale, expiry and outages',async()=>{ const clock=Buffer.alloc(40),state=Buffer.alloc(41),data=Buffer.alloc(146),mint=Buffer.alloc(82),token=Buffer.alloc(165); const vaultKey=new PublicKey(owner),delegateKey=new PublicKey(delegate),mintKey=new PublicKey(asset); clock.writeBigInt64LE(BigInt(Math.floor(Date.now()/1000)),32); state[0]=1;vaultKey.toBuffer().copy(state,1); data[0]=2;vaultKey.toBuffer().copy(data,1);delegateKey.toBuffer().copy(data,33);audience.copy(data,65); data[97]=1;mintKey.toBuffer().copy(data,98);data.writeBigInt64LE(BigInt(Date.parse(grant.expiresAt)/1000),138); mint.writeBigUInt64LE(1n,36);mint[45]=1; mintKey.toBuffer().copy(token);vaultKey.toBuffer().copy(token,32);token.writeBigUInt64LE(1n,64);token[108]=1; const spl=new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const loader=new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111'); const deployed=Buffer.alloc(36);deployed.writeUInt32LE(2); PublicKey.findProgramAddressSync([program.toBuffer()],loader)[0].toBuffer().copy(deployed,4); const binary=readFileSync(new URL('../protocol/build/solmate_registry.so',import.meta.url)); const code=Buffer.concat([Buffer.alloc(45),binary]);code.writeUInt32LE(3); const liveToken={owner:spl,data:token}, discovery={owner:spl,data:Buffer.from(token)}; const tokenKey=Keypair.generate().publicKey; let storedGrant=true; let deployedData=true; const metadataProgram = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'); const collection = Keypair.generate().publicKey.toBase58(); let metadata = {owner:metadataProgram}; const metadataData = {mint:asset,updateAuthority:owner,name:'Pilot',symbol:'TEST',uri:'', sellerFeeBasisPoints:0,creators:null,primarySaleHappened:false,isMutable:true,editionNonce:null, tokenStandard:TokenStandard.NonFungible,collection:{key:collection,verified:true},uses:null,collectionDetails:null,programmableConfig:null}; const encode = overrides => Buffer.from(getMetadataAccountDataSerializer().serialize({...metadataData,...overrides})); const connection={ getGenesisHash:async()=>DEVNET_GENESIS, getAccountInfo:async key=>key.equals(program)?{owner:loader,executable:true,data:deployed}:deployedData?{owner:loader,data:code}:null, getAccountInfoAndContext:async()=>({context:{slot:900},value:metadata.data?metadata:null}), getProgramAccounts:async(_program,options)=>{ assert.equal(options.filters[1].memcmp.bytes,delegate); const hash=data.subarray(65,97); if(!storedGrant || options.filters[2].memcmp.bytes!==new PublicKey(hash).toBase58()) return []; return [{pubkey:recordKey(),account:{owner:program,data}}]; }, getTokenAccountsByOwner:async()=>({context:{slot:900},value:[{pubkey:tokenKey,account:discovery}]}), getMultipleAccountsInfoAndContext:async(keys,opts)=>{ assert.equal(opts.commitment,'finalized'); const rows=keys.map(key=>{ if(key.equals(recordKey())) return storedGrant?{owner:program,data}:null; if(key.equals(vaultPda(program,owner))) return {owner:program,data:state}; if(key.equals(SYSVAR_CLOCK_PUBKEY)) return {owner:new PublicKey('Sysvar1111111111111111111111111111111111111'),data:clock}; if(key.equals(mintKey)) return {owner:spl,data:mint}; if(key.equals(tokenKey)) return liveToken; if(key.equals(PublicKey.findProgramAddressSync([Buffer.from('metadata'),metadataProgram.toBuffer(),mintKey.toBuffer()],metadataProgram)[0])) return metadata.data?metadata:null; return null; }); return {context:{slot:901},value:rows}; } }; function recordKey(){return grantPda(program,owner,delegate,data.subarray(65,97),data[97]?'asset':'wallet',data[97]?new PublicKey(data.subarray(98,130)):null);} const client={connection,program},ask=()=>checkHolderEligibility(client,{authenticatedDelegate:delegate,vault:owner,qualifyingMint:asset}); assert.equal((await ask()).eligible,true); const checkCollection=()=>checkHolderEligibility(client,{authenticatedDelegate:delegate,vault:owner,qualifyingMint:asset,collection}); metadata.data=encode({}); assert.equal((await checkCollection()).eligible,true); const discover=(rule,address,origin)=>findHolderEligibility(client,{authenticatedDelegate:delegate,rule,address,origin}); assert.equal((await discover('collection',collection)).eligible,true); const siteA='https://first.example',siteB='https://second.example'; for(const site of [siteA,siteB]) assert.equal((await discover('mint',asset,site)).eligible,true); for(const site of [siteA,AUDIENCE,LEGACY_MAIL]){ (await audienceBytes(site)).copy(data,65); assert.equal((await discover('mint',asset,site)).eligible,true); assert.equal((await discover('mint',asset,siteB)).eligible,false); } audience.copy(data,65); const walletCheck=origin=>checkDelegation(client,{authenticatedDelegate:delegate,vault:owner,origin}); assert.equal((await walletCheck(siteA)).eligible,false); const balanceCheck=minimum=>checkHolderEligibility(client,{authenticatedDelegate:delegate,vault:owner,qualifyingMint:asset,origin:siteA,rule:'balance',minimum}); assert.equal((await balanceCheck('1')).eligible,true);assert.equal((await balanceCheck('2')).eligible,false); await assert.rejects(balanceCheck('0')); assert.equal((await discover('mint',owner)).eligible,false); data[97]=0;data.fill(0,98,130);assert.equal((await walletCheck(siteA)).eligible,true);assert.equal((await discover('wallet',undefined,siteB)).eligible,true); assert.equal((await discover('collection',collection)).eligible,true); state.writeBigUInt64LE(1n,33);assert.equal((await discover('collection',collection)).eligible,false);state.writeBigUInt64LE(0n,33); data[97]=1;mintKey.toBuffer().copy(data,98);storedGrant=false;assert.equal((await ask()).eligible,false);storedGrant=true; const setCollection=async(origin)=>{(await audienceBytes(origin,'collection')).copy(data,65);new PublicKey(collection).toBuffer().copy(data,98);}; await setCollection(ALL_APPS);assert.equal((await discover('collection',collection,siteB)).eligible,true); assert.equal((await discover('mint',asset,siteA)).eligible,true); assert.equal((await walletCheck(siteB)).eligible,false); await setCollection(siteA);assert.equal((await discover('collection',collection,siteB)).eligible,false); metadata.data=encode({collection:{key:collection,verified:false}});assert.equal((await discover('collection',collection,siteA)).eligible,false); metadata.data=encode({});audience.copy(data,65);mintKey.toBuffer().copy(data,98); for (const overrides of [{mint:owner},{collection:null},{collection:{key:collection,verified:false}}, {collection:{key:owner,verified:true}},{tokenStandard:TokenStandard.FungibleAsset}]) { metadata.data=encode(overrides); assert.equal((await checkCollection()).eligible,false); } metadata.data=encode({});metadata.data[0]=Key.Uninitialized;assert.equal((await checkCollection()).eligible,false); metadata.data=encode({});metadata.owner=program;assert.equal((await checkCollection()).eligible,false); state.writeBigUInt64LE(1n,33);assert.equal((await ask()).eligible,false);state.writeBigUInt64LE(0n,33); token.writeBigUInt64LE(0n,64);assert.equal((await ask()).eligible,false);token.writeBigUInt64LE(1n,64); data.writeBigInt64LE(1n,138);assert.equal((await ask()).eligible,false); clock.writeBigInt64LE(100000n,32);await assert.rejects(ask(),/stale/); code[45]^=1;await assert.rejects(checkDeployment(client),/deployed devnet code has changed/); deployedData=false;await assert.rejects(checkDeployment(client),/program data/); });