deploy-to-ipfs-hash.js
1 import { base58btc } from 'multiformats/bases/base58'; 2 import { CID } from 'multiformats/cid'; 3 import { Account, connect, KeyPair, keyStores, utils } from 'near-api-js'; 4 5 const DEBUG = process.env.DEBUG === 'true'; 6 7 function convertToV0IfNeeded(ipfsHash) { 8 if (ipfsHash.startsWith('Qm')) { 9 return ipfsHash; 10 } 11 12 if (ipfsHash.startsWith('bafy')) { 13 try { 14 console.log('Converting CIDv1 to CIDv0...'); 15 const cid = CID.parse(ipfsHash), 16 cidv0 = cid.toV0().toString(base58btc.encoder); 17 console.log(`Converted CID: ${cidv0}`); 18 return cidv0; 19 } catch (error) { 20 console.error('Error converting CID:', error.message); 21 console.error('Using original CID instead'); 22 return ipfsHash; 23 } 24 } 25 26 return ipfsHash; 27 } 28 29 async function main() { 30 if (process.argv.length !== 4) { 31 console.error('Usage: node deploy-to-ipfs-hash.js <account-id> <ipfs-hash>'); 32 process.exit(1); 33 } 34 35 const accountId = process.argv[2]; 36 let ipfsHash = process.argv[3]; 37 38 if (!/^(Qm[\dA-Za-z]{44}|bafy[\dA-Za-z]{55})$/.test(ipfsHash)) { 39 console.error('Invalid IPFS hash format. Expected CIDv0 (Qm...) or CIDv1 (bafy...)'); 40 process.exit(1); 41 } 42 43 ipfsHash = convertToV0IfNeeded(ipfsHash); 44 45 const NEAR_SIGNER_ACCOUNT = process.env.NEAR_SIGNER_ACCOUNT || accountId, 46 { NEAR_SIGNER_KEY } = process.env; 47 48 if (!NEAR_SIGNER_KEY) { 49 console.error('NEAR_SIGNER_KEY environment variable must be set'); 50 console.error( 51 'Example: export NEAR_SIGNER_KEY="ed25519:2JKZSWEyFGDtmXxk...(your private key)"', 52 ); 53 process.exit(1); 54 } 55 56 if (!NEAR_SIGNER_KEY.startsWith('ed25519:')) { 57 console.error('NEAR_SIGNER_KEY must start with "ed25519:"'); 58 console.error("Make sure you're using the full private key format from your credentials"); 59 process.exit(1); 60 } 61 62 const network = 63 process.env.NEAR_ENV || 64 process.env.NODE_ENV || 65 (accountId.endsWith('.near') ? 'mainnet' : 'testnet'); 66 67 console.log(`Deploying to account: ${accountId}`); 68 console.log(`Using IPFS hash: ${ipfsHash}`); 69 console.log(`Network: ${network}`); 70 71 const config = { 72 explorerUrl: 73 network === 'mainnet' ? 'https://explorer.near.org' : 'https://explorer.testnet.near.org', 74 helperUrl: 75 network === 'mainnet' 76 ? 'https://helper.mainnet.near.org' 77 : 'https://helper.testnet.near.org', 78 networkId: network, 79 nodeUrl: 80 network === 'mainnet' ? 'https://rpc.mainnet.near.org' : 'https://rpc.testnet.near.org', 81 walletUrl: 82 network === 'mainnet' ? 'https://wallet.near.org' : 'https://wallet.testnet.near.org', 83 }, 84 keyStore = new keyStores.InMemoryKeyStore(); 85 86 try { 87 const keyPair = KeyPair.fromString(NEAR_SIGNER_KEY); 88 console.log('Successfully created key pair'); 89 90 await keyStore.setKey(config.networkId, NEAR_SIGNER_ACCOUNT, keyPair); 91 console.log(`Key set for account ${NEAR_SIGNER_ACCOUNT} on network ${config.networkId}`); 92 } catch (error) { 93 console.error('Error setting up key pair:'); 94 console.error(error.message); 95 console.error('\nMake sure your NEAR_SIGNER_KEY is in the correct format:'); 96 console.error('- It should start with "ed25519:"'); 97 console.error('- It should be followed by the base58-encoded private key'); 98 console.error('- Example: ed25519:2JKZSWEyFGDtmXxk...'); 99 console.error('\nYou can find your key in ~/.near-credentials/testnet/your-account.json'); 100 process.exit(1); 101 } 102 103 const TEST_MODE = process.argv.includes('--test'); 104 if (TEST_MODE) { 105 console.log('Running in TEST MODE - will not make any transactions'); 106 } 107 108 console.log('Connecting to NEAR...'); 109 let near; 110 try { 111 near = await connect({ 112 ...config, 113 keyStore, 114 }); 115 console.log('Successfully connected to NEAR'); 116 117 if (DEBUG) { 118 console.log('Connection details:', { 119 networkId: near.connection.networkId, 120 nodeUrl: near.connection.provider.connection.url, 121 signer: near.connection.signer.toString(), 122 }); 123 } 124 } catch (error) { 125 console.error('Error connecting to NEAR:'); 126 console.error(error); 127 process.exit(1); 128 } 129 130 console.log('Creating account object...'); 131 let account; 132 try { 133 account = new Account(near.connection, NEAR_SIGNER_ACCOUNT); 134 console.log('Account object created successfully'); 135 136 try { 137 const accountState = await account.state(); 138 console.log( 139 `Account ${NEAR_SIGNER_ACCOUNT} exists with ${utils.format.formatNearAmount(accountState.amount)} NEAR`, 140 ); 141 } catch (stateError) { 142 console.warn(`Warning: Could not get account state: ${stateError.message}`); 143 } 144 } catch (error) { 145 console.error('Error creating account object:'); 146 console.error(error); 147 process.exit(1); 148 } 149 150 const url = `ipfs://${ipfsHash}`; 151 console.log(`Setting static URL to: ${url}`); 152 153 if (TEST_MODE) { 154 console.log('TEST MODE: Would call web4_setStaticUrl with:', { url }); 155 console.log('TEST MODE: Transaction skipped'); 156 157 const websiteUrl = 158 accountId.match(/\.(near|testnet)$/) && `https://${accountId.replace(/^web4./, '')}.page`; 159 if (websiteUrl) { 160 console.log('\nAfter deployment, the website would be available at:', websiteUrl); 161 } 162 163 console.log( 164 '\nTest completed successfully. Run without --test to perform the actual deployment.', 165 ); 166 process.exit(0); 167 } 168 169 try { 170 console.log(`Calling web4_setStaticUrl on ${accountId}...`); 171 172 if (DEBUG) { 173 console.log('Skipping contract state check'); 174 } 175 176 const result = await account.functionCall({ 177 args: { url }, 178 attachedDeposit: '0', 179 contractId: accountId, 180 gas: '100000000000000', 181 methodName: 'web4_setStaticUrl', 182 }); 183 184 if (DEBUG) { 185 console.log('Function call result:', result); 186 } 187 188 console.log( 189 'Updated in transaction:', 190 `${config.explorerUrl}/transactions/${result.transaction.hash}`, 191 ); 192 193 const websiteUrl = 194 accountId.match(/\.(near|testnet)$/) && `https://${accountId.replace(/^web4./, '')}.page`; 195 if (websiteUrl) { 196 console.log('\nVisit your website at:', websiteUrl); 197 } else { 198 console.log("\nYou'll need to run your own Web4 gateway to access", accountId); 199 } 200 } catch (error) { 201 console.error('Error during function call:'); 202 if (DEBUG) { 203 console.error(error); 204 } else { 205 console.error(error.message); 206 } 207 208 if (error.message.includes('Cannot find contract code for account')) { 209 console.error(`\nAccount ${accountId} doesn't have a contract deployed yet.`); 210 console.error( 211 'Please deploy a contract first using web4-deploy with the --deploy-contract option.', 212 ); 213 process.exit(1); 214 } 215 216 if (error.message.includes('Contract method is not found')) { 217 console.error( 218 `\nAccount ${accountId} doesn't have web4_setStaticUrl method in its contract.`, 219 ); 220 console.error( 221 'Please add web4_setStaticUrl method to your contract and try deploying again.', 222 ); 223 process.exit(1); 224 } 225 226 if (error.message.includes('Can not sign transactions for account')) { 227 console.error(`\nCan not sign transactions for account ${accountId} on network ${network}.`); 228 console.error('Check your NEAR_SIGNER_KEY and NEAR_SIGNER_ACCOUNT environment variables.'); 229 process.exit(1); 230 } 231 232 if (error.message.includes('Cannot read properties of undefined')) { 233 console.error( 234 '\nThis error often occurs when there is an issue with the NEAR connection or account setup.', 235 ); 236 console.error('Try running with DEBUG=true for more information:'); 237 console.error('DEBUG=true node deploy-to-ipfs-hash.js dtub.testnet QmYourIPFSHash'); 238 console.error('\nAlso try running in test mode to verify the connection:'); 239 console.error('node deploy-to-ipfs-hash.js dtub.testnet QmYourIPFSHash --test'); 240 } 241 242 process.exit(1); 243 } 244 } 245 246 main().catch(error => { 247 console.error('Unhandled error:', error); 248 process.exit(1); 249 });