Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 218 lines 7.1 kB view raw
1/** 2 * Keeps v4 Test Helper 3 * 4 * Shared utilities for testing the Keeps FA2 v4 contract. 5 * Provides Tezos client setup, credential management, and common helpers. 6 */ 7 8import fs from 'fs'; 9import path from 'path'; 10import dotenv from 'dotenv'; 11import { TezosToolkit } from '@taquito/taquito'; 12import { InMemorySigner } from '@taquito/signer'; 13import { fileURLToPath } from 'url'; 14 15const __filename = fileURLToPath(import.meta.url); 16const __dirname = path.dirname(__filename); 17 18function envBool(value, fallback = false) { 19 if (value === undefined) return fallback; 20 return String(value).toLowerCase() === 'true'; 21} 22 23function envNat(value, fallback) { 24 if (value === undefined || value === '') return fallback; 25 const parsed = Number.parseInt(String(value), 10); 26 return Number.isNaN(parsed) ? fallback : parsed; 27} 28 29const MAINNET_CONTRACT_DEFAULT = 'KT1QdGZP8jzqaxXDia3U7DYEqFYhfqGRHido'; // v5 RC 30const MAINNET_ADMIN_DEFAULT = 'tz1dfoQDuxjwSgxdqJnisyKUxDHweade4Gzt'; 31const MAINNET_RPC_DEFAULT = 'https://mainnet.api.tez.ie'; 32const GHOSTNET_RPC_DEFAULT = 'https://rpc.ghostnet.teztnets.com'; 33 34// Contract addresses (override for v6 audits with env vars) 35export const CONTRACTS = { 36 mainnet: 37 process.env.KEEPS_AUDIT_CONTRACT || 38 process.env.KEEPS_CONTRACT || 39 process.env.TEZOS_KEEPS_CONTRACT || 40 MAINNET_CONTRACT_DEFAULT, 41 mainnet_v4: 'KT1ER1GyoeRNhkv6E57yKbBbEKi5ynKbaH3W', // v4 staging (legacy) 42 ghostnet: process.env.KEEPS_GHOSTNET_CONTRACT || null // To be deployed for testing 43}; 44 45// RPC endpoints (override per network if needed) 46export const RPCS = { 47 mainnet: process.env.KEEPS_MAINNET_RPC || MAINNET_RPC_DEFAULT, 48 ghostnet: process.env.KEEPS_GHOSTNET_RPC || GHOSTNET_RPC_DEFAULT 49}; 50 51// Expected storage values (override for deployed v6 contract checks) 52export const EXPECTED_STORAGE = { 53 mainnet: { 54 administrator: 55 process.env.KEEPS_EXPECTED_ADMIN || 56 process.env.KEEPS_ADMIN || 57 MAINNET_ADMIN_DEFAULT, 58 default_royalty_bps: envNat(process.env.KEEPS_EXPECTED_ROYALTY_BPS, 1000), // 10% 59 paused: envBool(process.env.KEEPS_EXPECTED_PAUSED, false) 60 } 61}; 62 63/** 64 * Load credentials from staging/.env file 65 * @param {string} wallet - Wallet name (default: 'staging') 66 * @returns {Promise<{address: string, secretKey: string}>} 67 */ 68export async function loadCredentials(wallet = 'staging') { 69 const envPath = path.join( 70 __dirname, 71 '../../tezos/staging/.env' 72 ); 73 74 if (!fs.existsSync(envPath)) { 75 throw new Error(`Credentials not found: ${envPath}`); 76 } 77 78 const envContent = fs.readFileSync(envPath, 'utf8'); 79 const config = {}; 80 81 // Parse .env file manually 82 envContent.split('\n').forEach(line => { 83 const trimmed = line.trim(); 84 if (!trimmed || trimmed.startsWith('#')) return; 85 86 const [key, ...valueParts] = trimmed.split('='); 87 if (key && valueParts.length > 0) { 88 config[key.trim()] = valueParts.join('=').trim().replace(/^["']|["']$/g, ''); 89 } 90 }); 91 92 const address = config.STAGING_ADDRESS || config.ADDRESS; 93 const secretKey = config.STAGING_KEY || config.KEY || config.SECRET_KEY; 94 95 if (!address || !secretKey) { 96 throw new Error('Could not load address or secret key from credentials file'); 97 } 98 99 return { address, secretKey }; 100} 101 102/** 103 * Create Tezos client (read-only or with signer) 104 * @param {string} network - Network name ('mainnet' or 'ghostnet') 105 * @param {boolean} withSigner - Whether to add signer for write operations 106 * @returns {Promise<TezosToolkit>} 107 */ 108export async function createTezosClient(network = 'mainnet', withSigner = false) { 109 const rpc = RPCS[network]; 110 if (!rpc) { 111 throw new Error(`Unknown network: ${network}`); 112 } 113 114 const tezos = new TezosToolkit(rpc); 115 116 if (withSigner) { 117 const creds = await loadCredentials(); 118 const signer = new InMemorySigner(creds.secretKey); 119 tezos.setProvider({ signer }); 120 console.log(`✅ Tezos client created for ${network} with signer (${creds.address})`); 121 } else { 122 console.log(`✅ Tezos client created for ${network} (read-only)`); 123 } 124 125 return tezos; 126} 127 128/** 129 * Get contract instance 130 * @param {string} network - Network name ('mainnet' or 'ghostnet') 131 * @param {boolean} withSigner - Whether to add signer for write operations 132 * @returns {Promise<{tezos: TezosToolkit, contract: any, address: string}>} 133 */ 134export async function getContract(network = 'mainnet', withSigner = false) { 135 const tezos = await createTezosClient(network, withSigner); 136 const address = CONTRACTS[network]; 137 138 if (!address) { 139 throw new Error(`No contract address for network: ${network}`); 140 } 141 142 console.log(`📋 Loading contract at ${address}...`); 143 const contract = await tezos.contract.at(address); 144 console.log(`✅ Contract loaded successfully`); 145 146 return { tezos, contract, address }; 147} 148 149/** 150 * Convert string to hex bytes (for contract parameters) 151 * @param {string} str - String to convert 152 * @returns {string} Hex string 153 */ 154export function stringToBytes(str) { 155 return Buffer.from(str, 'utf8').toString('hex'); 156} 157 158/** 159 * Convert hex bytes to string 160 * @param {string} bytes - Hex string 161 * @returns {string} UTF-8 string 162 */ 163export function bytesToString(bytes) { 164 return Buffer.from(bytes, 'hex').toString('utf8'); 165} 166 167/** 168 * Wait for operation confirmation 169 * @param {any} operation - Taquito operation 170 * @param {number} confirmations - Number of confirmations to wait for 171 * @returns {Promise<any>} 172 */ 173export async function waitForConfirmation(operation, confirmations = 1) { 174 console.log(`⏳ Waiting for ${confirmations} confirmation(s)...`); 175 await operation.confirmation(confirmations); 176 console.log(`✅ Operation confirmed: ${operation.hash}`); 177 return operation; 178} 179 180/** 181 * Create valid keep parameters for testing 182 * @param {object} overrides - Override default parameters 183 * @returns {object} Keep parameters 184 */ 185export function createKeepParams(overrides = {}) { 186 const defaults = { 187 name: stringToBytes("Test Keep"), 188 description: stringToBytes("Test description"), 189 artifactUri: stringToBytes("ipfs://QmTest123"), 190 displayUri: stringToBytes("ipfs://QmTest123"), 191 thumbnailUri: stringToBytes("ipfs://QmTestThumb"), 192 decimals: stringToBytes("0"), 193 symbol: stringToBytes("KEEP"), 194 isBooleanAmount: stringToBytes("true"), 195 shouldPreferSymbol: stringToBytes("false"), 196 formats: stringToBytes('[{"uri":"ipfs://QmTest123","mimeType":"text/html"}]'), 197 tags: stringToBytes('["test"]'), 198 attributes: stringToBytes('[]'), 199 creators: stringToBytes('[{"address":"tz1test","share":"100"}]'), 200 royalties: stringToBytes('[{"address":"tz1test","share":"100"}]'), 201 rights: stringToBytes("© 2025 Test"), 202 content_type: stringToBytes("text/html"), 203 content_hash: stringToBytes(`test-hash-${Date.now()}`), 204 metadata_uri: stringToBytes("ipfs://QmTestMeta"), 205 owner: "tz1dfoQDuxjwSgxdqJnisyKUxDHweade4Gzt" 206 }; 207 208 return { ...defaults, ...overrides }; 209} 210 211/** 212 * Sleep for specified milliseconds 213 * @param {number} ms - Milliseconds to sleep 214 * @returns {Promise<void>} 215 */ 216export function sleep(ms) { 217 return new Promise(resolve => setTimeout(resolve, ms)); 218}