import test from 'node:test'; import assert from 'node:assert/strict'; import { ComputeBudgetProgram, Keypair, PublicKey, SystemProgram, Transaction } from '@solana/web3.js'; import { openWallet, walletChoices } from '../lib/browser-wallets.mjs'; import { audienceBytes, buildTransaction, createGrantIx, DEVNET_PROGRAM_ID, sendPrepared } from '../lib/registry.mjs'; test('an independently named Wallet Standard wallet signs the reviewed legacy transaction and exact sign-in message',async()=>{ const signer=Keypair.generate(); const account={address:signer.publicKey.toBase58(),chains:['solana:devnet'],features:['solana:signTransaction','solana:signMessage']}; const standard={name:'Independent wallet',accounts:[account],chains:account.chains,features:{ 'standard:connect':{connect:async()=>({accounts:[account]})}, 'solana:signTransaction':{supportedTransactionVersions:['legacy'],signTransaction:async input=>{ assert.equal(input.account,account);assert.equal(input.chain,'solana:devnet'); const tx=Transaction.from(input.transaction);tx.sign(signer);return [{signedTransaction:tx.serialize()}]; }}, 'solana:signMessage':{signMessage:async input=>[{signedMessage:input.message,signature:new Uint8Array(64)}]}, }}; const registry={get:()=>[standard]}; const choice=walletChoices(registry,'signTransaction',{}).find(option=>option.standard===standard); const wallet=await openWallet(choice,'signTransaction'); const tx=new Transaction({recentBlockhash:Keypair.generate().publicKey.toBase58(),feePayer:signer.publicKey}) .add(SystemProgram.transfer({fromPubkey:signer.publicKey,toPubkey:Keypair.generate().publicKey,lamports:1})); assert.ok((await wallet.signTransaction(tx)).verifySignatures()); const message=new TextEncoder().encode('one-time sign-in'); assert.equal((await wallet.signMessage(message)).length,64); standard.features['solana:signMessage'].signMessage=async()=>[{signedMessage:new Uint8Array(1),signature:new Uint8Array(64)}]; await assert.rejects(wallet.signMessage(message),/message changed/); standard.accounts=[];assert.equal(wallet.address,undefined); await assert.rejects(wallet.signTransaction(tx),/changed/); }); test('the wallet signs the grant and fee that were simulated and reviewed',async()=>{ const vault=Keypair.generate(); const delegate=Keypair.generate().publicKey; const latest={blockhash:Keypair.generate().publicKey.toBase58(),lastValidBlockHeight:100}; const grant={owner:vault.publicKey,delegate,scope:'wallet',expiresAt:'2030-01-01T00:00:00Z'}; const ix=createGrantIx(new PublicKey(DEVNET_PROGRAM_ID),grant,await audienceBytes()); let quoted, simulated, sent=0; const connection={ getLatestBlockhash:async()=>latest, getFeeForMessage:async msg=>{quoted=Buffer.from(msg.serialize());return {value:5001};}, simulateTransaction:async tx=>{simulated=Buffer.from(tx.message.serialize());return {value:{err:null}};}, getBlockHeight:async()=>99, sendRawTransaction:async wire=>{ const tx=Transaction.from(wire); assert.deepEqual(tx.serializeMessage(),quoted); assert.ok(tx.verifySignatures()); sent++; return 'test-signature'; }, confirmTransaction:async()=>({value:{err:null}}), }; const prepare=()=>buildTransaction({connection},vault.publicKey,ix); const prepared=await prepare(); assert.deepEqual(quoted,simulated); assert.equal(prepared.feeLamports,5001); const roundTrip=tx=>Transaction.from(tx.serialize({requireAllSignatures:false})); const signWithAutomaticFee=async tx=>{ const returned=roundTrip(tx); if (!returned.instructions.some(i=>i.programId.equals(ComputeBudgetProgram.programId))) { returned.add(ComputeBudgetProgram.setComputeUnitPrice({microLamports:1000})); } returned.sign(vault); return returned; }; assert.equal((await sendPrepared({connection},prepared,signWithAutomaticFee)).finalized,true); for (const change of [ tx=>tx.add(ComputeBudgetProgram.setComputeUnitPrice({microLamports:1000})), tx=>tx.add(SystemProgram.transfer({fromPubkey:vault.publicKey,toPubkey:delegate,lamports:1})), tx=>{tx.recentBlockhash=Keypair.generate().publicKey.toBase58();}, ]) { await assert.rejects(sendPrepared({connection},await prepare(),async tx=>{ const changed=roundTrip(tx);change(changed);changed.sign(vault);return changed; }),/changed/i); } await assert.rejects(sendPrepared({connection},await prepare(),async tx=>roundTrip(tx)),/signature/i); assert.equal(sent,1); });