Как раздать NFT Solana через Airdrop Minting API от Crossmint

Обзор

Доставка NFT еще никогда не была такой простой. Независимо от того, являетесь ли вы компанией Web2, желающей начать работу с NFT, или у вас есть сообщество Web3, которое вы хотели бы вознаградить с помощью NFT, Mint API Crossmint позволяет вам создавать NFT для ваших пользователей с помощью одного запроса POST. NFT API Crossmint доступен непосредственно через QuickNode Marketplace, поэтому начать работу еще проще. В этом руководстве вы создадите простой скрипт, который позволит вам создавать и доставлять NFT напрямую списку клиентов (через адрес кошелька или адрес электронной почты 👀).

Что вы будете делать

В этом руководстве мы:

  • Создайте конечную точку QuickNode с надстройкой Crossmint API.
  • Создать метаданные NFT
  • Airdrop NFT в объединенный список кошельков и адресов электронной почты Solana с использованием API Crossmint (с использованием TypeScript)
  • Убедитесь, что раздача прошла успешно
  • Записывать данные об аирдропе в файл .json

Что вам понадобится

Чтобы следовать этому руководству, вам потребуется следующее:

  • Базовые знания языков программирования JavaScript/TypeScript
  • Установлен Nodejs (версия 16.15 и выше)
  • установлен npm или yarn (мы будем использовать yarn для инициализации нашего проекта и установки необходимых пакетов. Не стесняйтесь использовать npm вместо этого, если это ваш предпочтительный менеджер пакетов)
  • Опыт работы с TypeScript и установленный ts-node

Настройте свой проект

Создайте новый каталог проекта в своем терминале со следующим:

mkdir crossmint-drop
cd crossmint-drop

Создайте файл для своего приложения app.ts:

echo > app.ts

Инициализируйте свой проект с флагом «да», чтобы использовать значения по умолчанию для вашего нового пакета:

yarn init --yes
#or
npm init --yes

Создайте tsconfig.json с включенным импортом .json:

tsc -init --resolveJsonModule true --target es2020

Создайте папку results, которую мы будем использовать для сохранения вывода наших аирдропов:

mkdir results

Установите зависимость Solana Web3

Хотя это и не требуется для этого упражнения, мы будем использовать библиотеку Solana-Web3 для проверки адресов кошельков перед вызовом API Crossmint. Мы также будем использовать Axios для выполнения наших HTTP-запросов к Crossmint. В терминале введите:

yarn add @solana/web3.js axios
#or
npm install @solana/web3.js axios

Нам понадобится несколько компонентов из этих библиотек и fs для записи наших результатов в файл. Откройте папку crossmint-drop в выбранной IDE (мы будем использовать VSCode) и добавьте следующие операторы импорта в app.ts в строке 1:

import { PublicKey } from '@solana/web3.js';
import axios from 'axios';
import fs from 'fs';

Подключитесь к кластеру Solana с помощью конечной точки QuickNode

Чтобы получить быстрый доступ к Crossmint API, который позволяет создавать и отправлять NFT своим пользователям с помощью одной строки кода, даже если у них нет криптокошелька, вам необходимо создать конечную точку QuickNode с надстройкой Crossmint. на. Зарегистрируйте бесплатный аккаунт здесь. Создайте новую конечную точку Solana Devnet и обязательно настройте ее с помощью Crossmint NFT Mint API:

Скопируйте ссылку поставщика HTTP:

Внутри app.ts под вашими операторами импорта объявите свой RPC (это понадобится нам при запуске нашего скрипта):

const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';

Ваше окружение должно выглядеть так.

У вас все настроено. Давайте создадим наше приложение!

Объявить типы

Мы будем использовать TypeScript для нашего сценария, поэтому мы определим несколько типов и интерфейсов, чтобы наш код оставался чистым и точным. Если вы разработчик JavaScript или предпочитаете не использовать TypeScript, это нормально.

Создайте раздел в своем коде // Types и добавьте следующий код вверху вашего приложения под вашим импортом:

// TYPES

type Email = string;
type StringPubKey = string;
type Destination = Email | StringPubKey;

interface NftMetadata {
    name: string,
    image: string,
    description: string
    attributes: {trait_type: string, value: string}[],
    properties: {
        files: {uri: string, type: string}[],
        category: string
    }
}

interface MintNftProps {
    destination: Destination,
    qnEndpoint: string,
    collectionId: string,
    nftInfo: NftMetadata
}

interface FetchMintProps {
    qnEndpoint: string,
    collectionId: string,
    crossmintId: string
}

interface MintResult {
    destination: string, 
    crossmintId: string, 
    mint: string
}

Немного о каждом объявлении:

  • Во-первых, мы объявляем наш Destination как Email или StringPubKey (это будет то место, куда мы хотим, чтобы NFT наших пользователей направлялись).
  • Затем мы определяем наши NftMetadata, которые принимают основную информацию о нашем NFT, включая массив признаков.
  • MintNftProps определяет входные данные, которые мы будем использовать для нашей функции монетного двора. Нам понадобится пункт назначения, конечная точка QuickNode, идентификатор коллекции и метаданные создаваемого NFT.
  • FetchNftProps определяет входные данные, которые мы будем использовать для проверки состояния нашего монетного двора.
  • MintResult описывает, как мы будем регистрировать наши результаты (включая пункт назначения, уникальный идентификатор от CrossMint и адрес монетного двора — уникальный открытый ключ в блокчейне Solana).

Определить ключевые константы

Давайте определим несколько ключевых констант для нашего проекта. Под вашими типами добавьте:

// CONSTANTS

const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const COLLECTION_ID = 'default-solana';
const DROP_LIST: Destination[] = [
    '[email protected]',
    'CTrLzkrcnqgqSTmzJ146ZTRkLAvwcjnxGSZBvqC5BH3w',
    '[email protected]',
    'DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H'
];
const DELAY = 1000; 
  • Сначала мы переместили QUICKNODE_RPC вместе с остальными константами.
  • Затем мы устанавливаем COLLECTION_ID в default-solana. Это значение по умолчанию для монетных дворов Crossmint NFT на Solana. Если вы планируете запустить коллекцию NFT, возможно, имеет смысл создать собственную коллекцию. Сделать это можно с помощью метода cm_createCollection (docs). Мы будем использовать значение по умолчанию для этого примера.
  • Список адресов электронной почты и адресов кошелька Solana, на которые мы хотим отправить NFT (мы настроим наше приложение, чтобы иметь возможность обрабатывать оба). Mint API от Crossmint создает кошельки для новых пользователей, позволяя вам отправлять NFT на их адреса электронной почты и легко добавлять новых пользователей.
  • Мы добавим простую задержку между каждым запросом монетного двора в Crossmint, чтобы предотвратить превышение каких-либо ограничений скорости. Мы рекомендуем ознакомиться с лучшими практиками Crossmint, если вы планируете запустить какой-либо крупный выпуск NFT. Мы установим DELAY на 1 секунду (1000 мс).

Давайте добавим еще одну служебную константу, функцию wait. Мы будем использовать это для создания задержки между вызовами API:

async function wait(ms: number):Promise<void> {
    return new Promise<void>((resolve) => {
      setTimeout(() => {
        resolve();
      }, ms);
    });
}

Хорошо! Давайте перейдем к API Crossmint.

Создать функцию монетного двора

Давайте создадим наш HTTP-запрос к Crossmint, чтобы отчеканить наш NFT. Функция будет делать три вещи:

  1. Подтвердите наш пункт назначения как действительный адрес электронной почты или открытый ключ Solana.
  2. Собрать почтовый запрос
  3. Отправить почтовый запрос и обработать ответ

В app.ts добавьте функцию requestCrossMintNft:

const requestCrossMintNft = async ({ destination, qnEndpoint, collectionId, nftInfo }: MintNftProps) => {
    // Regular expression to validate an email address
    const emailRegex: RegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

    // Validate & define recipient (as either email address or Solana wallet else throw an error)
    let recipient: string;
    if (emailRegex.test(destination)) {
        recipient = `email:${destination}:sol`;
    } else if (new PublicKey(destination)) {
        recipient = `solana:${destination}`;
    }
    else {
        throw new Error('Invalid destination address (must be a valid email address or a Solana Wallet address).');
    }

    // Assemble POST Request
    const metadata = {
        "name": nftInfo.name,
        "image": nftInfo.image,
        "description": nftInfo.description
    };
    const data = {
        jsonrpc: "2.0",
        id: 1,
        method: "cm_mintNFT",
        params: [collectionId, recipient, metadata], // https://docs.crossmint.com/docs/cm-mintnft
    };
    const config = {
        headers: {
            "Content-Type": "application/json",
        },
    };

    // Send Post Request
    return new Promise<string>(async (resolve, reject) => {
        try {
          let response =  await axios.post(qnEndpoint, data, config);
          resolve(response.data.result.id as string);
        } catch (error) {
          reject("Error sending request to CrossMint. Check your inputs.");
        }
      });
}

Вот что мы делаем:

  1. Во-первых, мы используем регулярное выражение JavaScript (RegExp) и класс Solana PublicKey, чтобы проверить, что предоставленный адрес является допустимым пунктом назначения. Не стесняйтесь изменять это в зависимости от вашего списка рассылки (например, если вы отправляете только на адреса электронной почты, вам не нужна проверка Solana PublicKey и наоборот). Если недействительно, мы выдаем ошибку.

ОБЫЧНЫЕ ВЫРАЖЕНИЯ

Если вы никогда раньше не использовали регулярное выражение, шаблон emailRegex проверяет, что входная строка начинается с одного или нескольких символов из набора a-z, A-Z, 0-9, .!#$%&'*+/=?^_{|}~-, за которым следует символ @, а затем один или несколько символов из набора a-z. , A-Z, 0-9 и дефис -, максимум 61 символ и возможность добавления точки ., за которой следуют дополнительные символы, максимум 61 символ, из набора a-z, A-Z, 0-9 и дефис -.

  1. Затем мы собираем наш POST-запрос на основе документации Crossmint. Обратите внимание, что наш metadata скомпилирован с использованием нашего параметра nftInfo. Это означает, что мы можем создавать собственные атрибуты для каждого NFT, который мы чеканим!

2. Наконец, мы возвращаем обещание POST метода cm_mintNFT. Если получен успешный ответ, обещание возвращает идентификатор Crossmint (уникальный идентификатор транзакции).

Создать функцию Fetch Mint

После того, как мы отправили запрос на чеканку в Crossmint, нам понадобится способ убедиться, что наша чеканка прошла успешно (до сих пор все, что мы знаем, это то, что Crossmint успешно получил наш запрос, а не то, что Солана подтвердила чеканку NFT). Для этого мы можем выполнить проверку состояния с помощью Crossmint, используя идентификатор, возвращенный на предыдущем шаге.

Создайте новую функцию fetchMintAddress:

const fetchMintAddress = async ({ collectionId, qnEndpoint, crossmintId }: FetchMintProps) => {

    // Assemble POST Request
    const data = {
        jsonrpc: "2.0",
        id: 1,
        method: "cm_getNFTMintStatus",
        params: [collectionId,crossmintId], //https://docs.crossmint.com/docs/cm-getnftmintstatus
    };
    const config = {
        headers: {
            "Content-Type": "application/json",
        },
    };

    // Send POST Request
    return new Promise<string>(async (resolve, _reject) => {
        try {
          let response =  await axios.post(qnEndpoint, data, config);
          resolve(response.data.result.onChain.mintHash as string);
        } catch (error) {
          //reject("Error fetching mint address.");
        }
      });
}

Структурно это очень похоже на наш предыдущий шаг:

  1. Мы собираем запрос POST (на этот раз вызывая cm_getNFTMintStatus и передавая crossmintId, возвращенный с предыдущего шага).
  2. Отправьте запрос POST и, в случае успеха, верните уникальный хэш монетного двора в сети отчеканенного NFT.

Вместе вы можете запустить эти две функции, чтобы создать один NFT, но мы хотим немного повеселиться — давайте раздадим кучу 👇

Создать функцию массовой раздачи

У нас есть все инструменты для чеканки и подтверждения NFT с помощью Crossmint API — нам нужен способ массового сброса с некоторой возможностью настройки каждого NFT. Нам нужно создать функцию dropNfts, которая будет:

  1. Используйте .map, чтобы создать обещание для каждого Destination в нашем dropList.
  2. Определите наши метаданные на основе какой-либо уникальной характеристики (в этом случае мы будем использовать индекс i, чтобы дать уникальное имя и характеристику каждому NFT. Не стесняйтесь обновлять метаданные так, чтобы они соответствовали вашему стилю!
  3. Создайте обещание, что:
  • Просит Crossmint отчеканить NFT, используя requestCrossMintNft
  • Ожидает 1 минуту, пока произойдет чеканка, позвонив. Примечание. Это простой метод для нашей демонстрации. В зависимости от ваших потребностей вы можете настроить некоторую логику запроса/повторения).
  • Проверяет успешность монетного двора с помощью fetchMintAddress
  • Возвращает информацию о сброшенном NFT
  1. Выполнить все обещания, используя Promise.allSettled()
  2. Создайте журнал наших результатов в каталоге results

Эта следующая функция немного длинная, но мы уже сделали всю тяжелую работу в наших предыдущих шагах! В app.ts создайте функцию dropNfts, которая принимает список Destination, вашу конечную точку QuickNode и идентификатор вашей коллекции:

const dropNfts = async (dropList: Destination[], qnEndpoint: string, collectionId: string) => {
    console.log('Generating promises...');
    let promises = dropList.map((drop, i) => {
        // 1-Define Custom Metadata
        const nftNumber = (i+1).toString();
        const nftInfo = {
            name: `Demo Airdrop # ${nftNumber}`,
            image: 'https://arweave.net/UTFFfaVA3HoFcxwoMHEcvBLq19HrW6FuzpyygXqxduk',
            description: 'Demo airdrop NFT using Crossmint Mint API via the Quicknode add-on',
            attributes: [
                {
                    trait_type: "background",
                    value: "blue"
                },
                {
                    trait_type: "type",
                    value: "pixel"
                },
                {
                    trait_type: "id",
                    value: nftNumber
                }
            ],
            properties: {
                files: [
                    {
                        "uri": "https://arweave.net/UTFFfaVA3HoFcxwoMHEcvBLq19HrW6FuzpyygXqxduk",
                        "type": "image/png"
                    }
                ],
                category: "image"
            }
        };
        // 2-Create Promise
        return new Promise< MintResult >(async (resolve, reject)=>{
            setTimeout(async ()=>{
                try {
                    let crossmintId = await requestCrossMintNft({
                      destination: drop,
                      qnEndpoint,
                      collectionId,
                      nftInfo
                    });
                    if (!crossmintId) throw new Error('No CrossMint ID received.');
                    await wait(60000); // wait 1 min
                    let mint = await fetchMintAddress({
                        collectionId,
                        qnEndpoint,
                        crossmintId
                    });
                    resolve({
                        destination: drop,
                        crossmintId: crossmintId, 
                        mint: mint ?? ''
                    });
                } catch (error) {
                    reject('Unknown Error sending request to CrossMint.');
                }
            },i * DELAY);
        })
    });
    // 3-Execute Promises
    console.log('Executing promises...(this will take 1min +)');
    let results = await Promise.allSettled(promises);
    // 4-Save Results
    console.log('Writing results to ./results/results.json');
    let data = JSON.stringify(results);
    fs.writeFileSync('./results/results.json',data);
}

Эпично! Давайте зажжем его.

Бросьте свои NFT 🪂

В конце app.ts вызовите функцию dropNfts:

dropNfts(DROP_LIST, QUICKNODE_RPC, COLLECTION_ID);

Внутри вашего терминала введите следующее:

ts-node app

Примерно через минуту вы должны увидеть новый сгенерированный results.json, который содержит массив результатов вашего монетного двора:

[
    {
        "status": "fulfilled",
        "value": {
            "destination": "[email protected]",
            "crossmintId": "60f33f72-a8d8-41ce-b28a-bc899aa7b929",
            "mint": "AbJjT4j9MYQTya9aZ9qmCd36dspwoMnhfsEpZL91sFwG"
        }
    },
    {
        "status": "fulfilled",
        "value": {
            "destination": "DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H",
            "crossmintId": "0c4836cb-26dc-48a2-a55f-5d3ca40699da",
            "mint": "5RRDSrq6ME5Yz9qXcKRvSQeGD1F2AEnURnjfCs7BTEvN"
        }
    },
    // etc.
]

Хорошая работа!

Подпишитесь на нашу рассылку новостей, чтобы получать больше статей и руководств по Ethereum. Если у вас есть какие-либо отзывы, не стесняйтесь обращаться к нам через Twitter. Вы всегда можете пообщаться с нами на нашем сервере сообщества Discord, где представлены одни из самых крутых разработчиков, которых вы когда-либо встречали :)