/ contracts / dtub.testnet / deploy-to-ipfs-hash.js
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  });