diff --git a/.eslintrc.js b/.eslintrc.js index e41fbf7..f67f037 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,5 +44,6 @@ module.exports = { 'no-async-promise-executor': 'off', 'no-restricted-properties': 'off', 'import/no-cycle': 'off', + 'prefer-destructuring': ["error", { "object": true, "array": false }], }, }; diff --git a/package.json b/package.json index 2a40aa2..871f671 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@ethersproject/solidity": "^5.0.5", "@ethersproject/strings": "^5.0.5", "@ethersproject/wallet": "^5.0.7", + "@imtbl/core-sdk": "^1.0.0", "@imtbl/imlogging": "^1.0.33", "@imtbl/imx-sdk": "^1.28.0", "@typescript-eslint/eslint-plugin": "^4.29.2", @@ -87,4 +88,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/src/admin/get-project.ts b/src/admin/get-project.ts index 137d0ce..f86585b 100644 --- a/src/admin/get-project.ts +++ b/src/admin/get-project.ts @@ -5,7 +5,7 @@ import { ImmutableXClient } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; import { parse } from 'ts-command-line-args'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); diff --git a/src/admin/get-projects.ts b/src/admin/get-projects.ts index 15ce0dc..aac3cab 100644 --- a/src/admin/get-projects.ts +++ b/src/admin/get-projects.ts @@ -4,7 +4,7 @@ import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { ImmutableXClient } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); diff --git a/src/admin/get-withdrawal-balance.ts b/src/admin/get-withdrawal-balance.ts index 6bfd748..639c2ba 100644 --- a/src/admin/get-withdrawal-balance.ts +++ b/src/admin/get-withdrawal-balance.ts @@ -10,7 +10,7 @@ import { } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); diff --git a/src/admin/update-collection.ts b/src/admin/update-collection.ts index 447555a..d80e510 100644 --- a/src/admin/update-collection.ts +++ b/src/admin/update-collection.ts @@ -4,7 +4,7 @@ import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { ImmutableXClient, UpdateCollectionParams } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); diff --git a/src/admin/update-metadata-by-name.ts b/src/admin/update-metadata-by-name.ts index 6a3e757..a41992a 100644 --- a/src/admin/update-metadata-by-name.ts +++ b/src/admin/update-metadata-by-name.ts @@ -8,7 +8,7 @@ import { import { requireEnvironmentVariable } from 'libs/utils'; import { parse } from 'ts-command-line-args'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); diff --git a/src/bulk-mint.ts b/src/bulk-mint.ts index a97dcb8..172a11c 100644 --- a/src/bulk-mint.ts +++ b/src/bulk-mint.ts @@ -1,10 +1,10 @@ import { AlchemyProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; -import { ImmutableXClient, ImmutableMethodParams } from '@imtbl/imx-sdk'; +import { ImmutableMethodParams, ImmutableXClient } from '@imtbl/imx-sdk'; import { parse } from 'ts-command-line-args'; -import env from './config/client'; +import { env } from './config/client'; import { loggerConfig } from './config/logging'; interface BulkMintScriptArgs { diff --git a/src/config/client.ts b/src/config/client.ts index 27d8fd2..1ecf902 100644 --- a/src/config/client.ts +++ b/src/config/client.ts @@ -1,6 +1,10 @@ +import { AlchemyProvider } from '@ethersproject/providers'; +import { Wallet } from '@ethersproject/wallet'; +import { Config, ImmutableX } from '@imtbl/core-sdk'; + import { getEnv } from '../libs/utils'; -export default { +export const env = { alchemyApiKey: getEnv('ALCHEMY_API_KEY'), ethNetwork: getEnv('ETH_NETWORK'), client: { @@ -19,4 +23,32 @@ export default { ownerAccountPrivateKey: getEnv('OWNER_ACCOUNT_PRIVATE_KEY'), collectionContractAddress: getEnv('COLLECTION_CONTRACT_ADDRESS'), collectionProjectId: getEnv('COLLECTION_PROJECT_ID'), + projectName: getEnv('PROJECT_NAME'), + companyName: getEnv('COMPANY_NAME'), + contactEmail: getEnv('CONTACT_EMAIL'), }; + +function ensureNetworkSet() { + if (env.ethNetwork !== ('goerli' || 'mainnet')) { + throw new Error("Set ETH_NETWORK to 'goerli' or 'mainnet'"); + } +} + +export function createIMXClient() { + ensureNetworkSet(); + + if (env.ethNetwork === 'mainnet') { + return new ImmutableX(Config.PRODUCTION); + } + return new ImmutableX(Config.SANDBOX); +} + +export function getEthWalletAndSigner() { + ensureNetworkSet(); + + const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); + const wallet = new Wallet(env.ownerAccountPrivateKey); + const ethSigner = wallet.connect(provider); + + return { wallet, ethSigner }; +} diff --git a/src/onboarding/1-user-registration.ts b/src/onboarding/1-user-registration.ts index ca0c986..127a717 100644 --- a/src/onboarding/1-user-registration.ts +++ b/src/onboarding/1-user-registration.ts @@ -1,52 +1,104 @@ -import { AlchemyProvider } from '@ethersproject/providers'; -import { Wallet } from '@ethersproject/wallet'; +import { + createStarkSigner, + generateLegacyStarkPrivateKey, + generateStarkPrivateKey, +} from '@imtbl/core-sdk'; import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; -import { ImmutableXClient } from '@imtbl/imx-sdk'; -import { requireEnvironmentVariable } from 'libs/utils'; +import { parse } from 'ts-command-line-args'; -import env from '../config/client'; +import { createIMXClient, getEthWalletAndSigner } from '../config/client'; import { loggerConfig } from '../config/logging'; -const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); const log: ImLogger = new WinstonLogger(loggerConfig); const component = '[IMX-USER-REGISTRATION]'; -(async (): Promise => { - const privateKey = requireEnvironmentVariable('OWNER_ACCOUNT_PRIVATE_KEY'); +interface StarkKeyType { + starkKeyType: string; +} + +// Initialize ImmutableX client +const client = createIMXClient(); - const user = await ImmutableXClient.build({ - ...env.client, - signer: new Wallet(privateKey).connect(provider), +(async (): Promise => { + // Get user input for type of Stark key to generate + const { starkKeyType } = parse({ + starkKeyType: { + type: String, + alias: 's', + description: "Stark key type: 'random' (default: 'deterministic')", + }, }); - log.info(component, 'Registering user...'); + // Check that value entered is exactly 'random' + if (starkKeyType !== 'random') { + const text = + "Enter 'random' or do not use '-s' flag.\n\n" + + 'To generate a non-deterministic Stark key (more secure, recommended for ' + + 'collection owners): `npm run onboarding:user-registration -- -s random`\n' + + '***NOTE*** You must persist and store this key securely as it cannot be ' + + 'regenerated for you.\n\n' + + 'To generate a deterministic Stark key: `npm run onboarding:user-registration`\n'; + + console.log(text); + return; + } + + // Create Ethereum signer + const { ethSigner } = getEthWalletAndSigner(); + + // Check if user already exists + let starkPublicKey; + let starkPrivateKey; - let existingUser; - let newUser; try { - // Fetching existing user - existingUser = await user.getUser({ - user: user.address, - }); + const existingUser = await client.getUser(ethSigner.address); + starkPublicKey = existingUser.accounts[0]; + + const message = + `This user is already registered.\nEthereum (L1) public key: ${ethSigner.address}` + + `\nStark (L2) public key: ${starkPublicKey}`; + + console.log(message); + return; } catch { + // If user doesn't exist, register user try { - // If user doesnt exist, create user - newUser = await user.registerImx({ - etherKey: user.address, - starkPublicKey: user.starkPublicKey, - }); + log.info(component, 'Registering user...'); + + // Generate Stark private key + if (starkKeyType === 'random') { + starkPrivateKey = generateStarkPrivateKey(); + } else { + starkPrivateKey = await generateLegacyStarkPrivateKey(ethSigner); + } + + // Create Stark signer + const starkSigner = createStarkSigner(starkPrivateKey); + + // Register user + await client.registerOffchain({ ethSigner, starkSigner }); + + // Get registered user Stark key + const registeredUser = await client.getUser(ethSigner.address); + starkPublicKey = registeredUser.accounts[0]; } catch (error) { throw new Error(JSON.stringify(error, null, 2)); } } - if (existingUser) { - log.info(component, 'User already exists', user.address); - } else { - log.info(component, 'User has been created', user.address); + // Return details about the user created + console.log('User has been registered.'); + console.log(`Ethereum (L1) public key: ${ethSigner.address}`); + console.log(`Stark (L2) public key: ${starkPublicKey}`); + + if (starkKeyType === 'random') { + const message = + `Stark (L2) private key: ${starkPrivateKey}\n` + + '***NOTE*** You must persist and store this key securely as it cannot be ' + + 'regenerated for you.'; + console.log(message); } - console.log(JSON.stringify({ newUser, existingUser }, null, 2)); })().catch(e => { log.error(component, e); process.exit(1); diff --git a/src/onboarding/2-create-project.ts b/src/onboarding/2-create-project.ts index 2dd97f1..a6f916d 100644 --- a/src/onboarding/2-create-project.ts +++ b/src/onboarding/2-create-project.ts @@ -1,47 +1,37 @@ -import { AlchemyProvider } from '@ethersproject/providers'; -import { Wallet } from '@ethersproject/wallet'; import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; -import { CreateProjectParams, ImmutableXClient } from '@imtbl/imx-sdk'; -import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { createIMXClient, env, getEthWalletAndSigner } from '../config/client'; import { loggerConfig } from '../config/logging'; -const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); const log: ImLogger = new WinstonLogger(loggerConfig); const component = '[IMX-CREATE-PROJECT]'; +// Initialize ImmutableX client +const client = createIMXClient(); + (async (): Promise => { - const privateKey = requireEnvironmentVariable('OWNER_ACCOUNT_PRIVATE_KEY'); + // Create Ethereum signer + const { ethSigner } = getEthWalletAndSigner(); - const signer = new Wallet(privateKey).connect(provider); + log.info(component, 'Creating project...'); - const user = await ImmutableXClient.build({ - ...env.client, - signer, - enableDebug: true, - }); + // Create project + try { + const createProjectResponse = await client.createProject(ethSigner, { + name: env.projectName, + company_name: env.companyName, + contact_email: env.contactEmail, + }); - log.info(component, 'Creating project...'); + const projectId = createProjectResponse.id.toString(); - /** - * Edit your values here - */ - const params: CreateProjectParams = { - name: 'ENTER_PROJECT_NAME_HERE-2', - company_name: 'ENTER_COMPANY_NAME_HERE', - contact_email: 'contactemail@example.com', - }; + const getProjectResponse = await client.getProject(ethSigner, projectId); - let project; - try { - project = await user.createProject(params); + log.info(component, `Created project with ID: ${getProjectResponse.id}`); } catch (error) { throw new Error(JSON.stringify(error, null, 2)); } - - log.info(component, `Created project with ID: ${project.id}`); })().catch(e => { log.error(component, e); process.exit(1); diff --git a/src/onboarding/3-create-collection.ts b/src/onboarding/3-create-collection.ts index a340d5d..5992e91 100644 --- a/src/onboarding/3-create-collection.ts +++ b/src/onboarding/3-create-collection.ts @@ -1,53 +1,40 @@ -import { AlchemyProvider } from '@ethersproject/providers'; -import { Wallet } from '@ethersproject/wallet'; +import { CreateCollectionRequest } from '@imtbl/core-sdk'; import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; -import { CreateCollectionParams, ImmutableXClient } from '@imtbl/imx-sdk'; -import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { createIMXClient, env, getEthWalletAndSigner } from '../config/client'; import { loggerConfig } from '../config/logging'; -const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); const log: ImLogger = new WinstonLogger(loggerConfig); const component = '[IMX-CREATE-COLLECTION]'; -(async (): Promise => { - const privateKey = requireEnvironmentVariable('OWNER_ACCOUNT_PRIVATE_KEY'); - const collectionContractAddress = requireEnvironmentVariable( - 'COLLECTION_CONTRACT_ADDRESS', - ); - const projectId = requireEnvironmentVariable('COLLECTION_PROJECT_ID'); - - const wallet = new Wallet(privateKey); - const signer = wallet.connect(provider); - const ownerPublicKey = wallet.publicKey; +// Initialize ImmutableX client +const client = createIMXClient(); - const user = await ImmutableXClient.build({ - ...env.client, - signer, - enableDebug: true, - }); +(async (): Promise => { + // Get Ethereum wallet and signer + const { wallet, ethSigner } = getEthWalletAndSigner(); - log.info(component, 'Creating collection...', collectionContractAddress); + log.info(component, 'Creating collection...', env.collectionContractAddress); /** * Edit your values here */ - const params: CreateCollectionParams = { + const params: CreateCollectionRequest = { name: 'ENTER_COLLECTION_NAME', // description: 'ENTER_COLLECTION_DESCRIPTION (OPTIONAL)', - contract_address: collectionContractAddress, - owner_public_key: ownerPublicKey, + contract_address: env.collectionContractAddress, + owner_public_key: wallet.publicKey, // icon_url: '', // metadata_api_url: '', // collection_image_url: '', - project_id: parseInt(projectId, 10), + project_id: parseInt(env.collectionProjectId, 10), }; let collection; + try { - collection = await user.createCollection(params); + collection = await client.createCollection(ethSigner, params); } catch (error) { throw new Error(JSON.stringify(error, null, 2)); } diff --git a/src/onboarding/4-add-metadata-schema.ts b/src/onboarding/4-add-metadata-schema.ts index d1f6e68..fc4f597 100644 --- a/src/onboarding/4-add-metadata-schema.ts +++ b/src/onboarding/4-add-metadata-schema.ts @@ -1,65 +1,54 @@ -import { AlchemyProvider } from '@ethersproject/providers'; -import { Wallet } from '@ethersproject/wallet'; -import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { - AddMetadataSchemaToCollectionParams, - ImmutableXClient, - MetadataTypes, -} from '@imtbl/imx-sdk'; -import { requireEnvironmentVariable } from 'libs/utils'; + AddMetadataSchemaToCollectionRequest, + MetadataSchemaRequestTypeEnum, +} from '@imtbl/core-sdk'; +import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; -import env from '../config/client'; +import { createIMXClient, env, getEthWalletAndSigner } from '../config/client'; import { loggerConfig } from '../config/logging'; -const provider = new AlchemyProvider(env.ethNetwork, env.alchemyApiKey); const log: ImLogger = new WinstonLogger(loggerConfig); const component = '[IMX-ADD-COLLECTION-METADATA-SCHEMA]'; -(async (): Promise => { - const privateKey = requireEnvironmentVariable('OWNER_ACCOUNT_PRIVATE_KEY'); - const collectionContractAddress = requireEnvironmentVariable( - 'COLLECTION_CONTRACT_ADDRESS', - ); - - const wallet = new Wallet(privateKey); - const signer = wallet.connect(provider); +// Initialize ImmutableX client +const client = createIMXClient(); - const user = await ImmutableXClient.build({ - ...env.client, - signer, - enableDebug: true, - }); +(async (): Promise => { + // Get Ethereum wallet and signer + const { ethSigner } = getEthWalletAndSigner(); log.info( component, 'Adding metadata schema to collection', - collectionContractAddress, + env.collectionContractAddress, ); /** * Edit your values here */ - const params: AddMetadataSchemaToCollectionParams = { + const request: AddMetadataSchemaToCollectionRequest = { + contract_address: env.collectionContractAddress, metadata: [ { name: 'EXAMPLE_BOOLEAN', - type: MetadataTypes.Boolean, - filterable: true, + type: MetadataSchemaRequestTypeEnum.Boolean, // Optional + filterable: true, // Optional }, // ..add rest of schema here ], }; - const collection = await user.addMetadataSchemaToCollection( - collectionContractAddress, - params, + const collection = await client.addMetadataSchemaToCollection( + ethSigner, + env.collectionContractAddress, + request, ); log.info( component, 'Added metadata schema to collection', - collectionContractAddress, + env.collectionContractAddress, ); console.log(JSON.stringify(collection, null, 2)); })().catch(e => { diff --git a/src/public/get-collection.ts b/src/public/get-collection.ts index e731169..2f14264 100644 --- a/src/public/get-collection.ts +++ b/src/public/get-collection.ts @@ -2,7 +2,7 @@ import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { ImmutableMethodParams, ImmutableXClient } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const log: ImLogger = new WinstonLogger(loggerConfig); diff --git a/src/public/get-collections.ts b/src/public/get-collections.ts index 4032406..9b1f025 100644 --- a/src/public/get-collections.ts +++ b/src/public/get-collections.ts @@ -1,7 +1,7 @@ import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { ImmutableMethodParams, ImmutableXClient } from '@imtbl/imx-sdk'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const log: ImLogger = new WinstonLogger(loggerConfig); diff --git a/src/public/get-metadata-schema.ts b/src/public/get-metadata-schema.ts index 79e592a..5ff087f 100644 --- a/src/public/get-metadata-schema.ts +++ b/src/public/get-metadata-schema.ts @@ -2,7 +2,7 @@ import { ImLogger, WinstonLogger } from '@imtbl/imlogging'; import { GetMetadataSchemaParams, ImmutableXClient } from '@imtbl/imx-sdk'; import { requireEnvironmentVariable } from 'libs/utils'; -import env from '../config/client'; +import { env } from '../config/client'; import { loggerConfig } from '../config/logging'; const log: ImLogger = new WinstonLogger(loggerConfig);