To build an application that uses Streambird API for deploying a ThirdWeb NFT marketplace, developers will need to follow a series of steps. Below is a tutorial that outlines these steps and provides examples of code to help developers get started.

Step 1: Register for a Streambird API Key

To use Streambird API, developers need to register for an API key. To do this, visit the Streambird API website (https://app.streambird.io/) and sign up for an account. After signing up, developers will be able to access the API key on their dashboard, and use the API key to authorize requests.

Step 2: Create or Get a wallet

Create a wallet if you do not have using the following cURL script or use an existing wallet:

  • Replace $API_KEY with your Streambird API Key.
  • Replace $USER_ID with your user ID.
  • Run the following cURL command in your terminal:
curl \
 -X POST https://api.streambird.io/v1/wallets/create \
 -H "Authorization: Bearer $API_KEY" \
 -H "Content-Type: application/json" \
 -d '{"wallet_type":"ETH","user_id":"$USER_ID"}'

Step 3: Install Required Dependencies

To build the application, developers will need to install the following dependencies:

ethers: A JavaScript library that provides a simple interface to interact with Ethereum nodes and smart contracts. node-fetch-commonjs: A light-weight module that brings the Fetch API to Node.js. To install these dependencies, use the following command:

npm install ethers node-fetch-commonjs

Step 4: Import the required dependencies

Add the following lines to the top of your JavaScript file to import the required dependencies:

const ethers = require('ethers');
const fetch = require('node-fetch-commonjs');

Step 5: Set up the provider

Create a new instance of ethers.providers.JsonRpcProvider using the Goerli test network’s RPC URL. You can use Infura as the provider to access the Goerli network.

  • Replace the ALCHEMY_API_KEY with your Alchemy API Key.
const RPC_URL = 'https://eth-goerli.g.alchemy.com/v2/$ALCHEMY_API_KEY';
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);

Step 6: Define the gas price function

Create an async function called getGasPrice that calculates the gas price for the transaction. The function uses the getGasPrice method to retrieve the current gas price, formats the price to ether, multiplies it by the given multiplier, and then converts it back to wei.

async function getGasPrice(provider, mul) {
  const price = await provider.getGasPrice();
  const str = ethers.utils.formatEther(price);
  const eth = str * mul;
  return ethers.utils.parseEther(eth.toFixed(18));
}

Step 7: Define the send transaction function

Create an async function called sendRawTransaction that sends a raw transaction to the Goerli network via the Ethereum JSON-RPC endpoint. This function is used to deploy the Thirdweb marketplace. The function does the following:

  • Defines the contract address and ABI.
  • Sets the implementation, data, and salt for the smart contract.
  • Encodes the function data for the deployProxyByImplementation function.
  • Retrieves the transaction count for the fromAddress account.
  • Defines the gas limit and the chain ID.
  • Defines the transaction object with the to, data, gas price, gas limit, chain ID, and nonce.
  • Calls the fetchSignedTx function to sign the transaction.
  • Sends the signed transaction to the network and waits for the transaction to be confirmed.
async function sendRawTransaction() {
  try {
    const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
    const gasPrice = await getGasPrice(provider, 1.5)

    // Thirdweb's deployer contract
    const contractAddress = '0x5dbc7b840baa9dabcbe9d2492e45d7244b54a2a0' 
    const contractABI = require('./proxy.json')
    const contract = new ethers.Contract(contractAddress, contractABI, provider)
    
    const _implementation = '0x4f247c69184ad61036EC2Bb3213b69F10FbEDe1F'
    // Data for the marketplace deployment 
    const _data = '0x8c8a84e20000000000000000000000007a33615d12a12f58b25c653dc5e44188d44f689800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000007a33615d12a12f58b25c653dc5e44188d44f689800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d5236666a51427071416b554643534e4343363859504b3676426776386d714331656147373970524a636670622f3000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005001a14ca6163143316a7c614e30e6041033ac20000000000000000000000000e041608922d06a4f26c0d4c27d8bcd01daf1f792'
    const _salt = '0x3838313332373400000000000000000000000000000000000000000000000000'

    const contractData = contract.interface.encodeFunctionData('deployProxyByImplementation', [_implementation, _data, _salt])

    let nonceInt = await provider.getTransactionCount(fromAddress);
    let nonceStr = ethers.utils.hexlify(nonceInt)
    
    let gasLimit = ethers.BigNumber.from(729779)
    gasLimit = ethers.utils.hexlify(gasLimit)

    const chainId = 5; // goerli
    const chainIdStr = ethers.utils.hexlify(chainId);
    
    const transaction = {
      to: contractAddress,
      data: contractData,
      gasPrice: gasPrice.toHexString(),
      gas: gasLimit,
      chainId: chainIdStr,
      nonce: nonceStr
    };

    console.log("Transaction Object: ", transaction)
    let signedTransaction = await fetchSignedTx(transaction);
    const txResponse = await provider.sendTransaction(signedTransaction.raw_transaction);
    console.log('Transaction sent:', txResponse.hash);
    await txResponse.wait();
    console.log('Transaction confirmed:', txResponse.hash);
  } catch (error) {
    console.error('Error sending raw transaction:', error);
  }
}

Step 8: Define the fetchSignedTx function

The fetchSignedTx function takes a transaction object as input, signs the transaction using a Streambird Wallet API, and returns the signed transaction object. You can find more information about the WalletSignTransaction endpoint in the Streambird API.

  • Replace $API_KEY with your Streambird API Key.
  • Change the $WALLET_ID variable to match the ID of the wallet from which you want to sign the transaction.
  • Modify the signedTxUrl variable to match the endpoint where you want to send the signed transaction.
  • Adjust the headers object to match the authorization credentials required to access the endpoint.
  • Update the body of the POST request to include any additional data required by the endpoint to successfully sign the transaction.
const fetchSignedTx = async (transaction) => {
  let walletId = '$WALLET_ID'
  const signedTxUrl = `https://api.streambird.io/v1/wallets/${walletId}/sign_transaction`;

  try {
    console.log(JSON.stringify({
      eth_transaction: transaction
    }))

    const response = await fetch(signedTxUrl, {
      method: 'POST',
      body: JSON.stringify({
        eth_transaction: transaction
      }),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $API_KEY'
      },
    });

    const signedTx = await response.json();
    console.log(signedTx)
    return signedTx;
  } catch (error) {
    console.error(error);
  }
};

Conclusion:

In conclusion, deploying a ThirdWeb marketplace using the Streambird API requires a few key steps. First, developers must create an account with Streambird and generate an API key. They must then install the necessary dependencies and configure their application to use the Streambird API. Once this is done, developers can use the API to perform various functions, such as creating wallets, signing transactions, and querying blockchain data. By following the steps outlined in this tutorial, developers can get up and running with the Streambird API and begin building their own decentralized marketplaces.

While this tutorial focused on deploying a ThirdWeb marketplace using the Streambird Wallet API, the same general steps can be used to launch any smart contract. Developers can follow these steps to deploy their own smart contracts and integrate them into their applications. By leveraging the Streambird API, developers can simplify the process of signing transactions and interacting with the Ethereum network, allowing them to focus on building their applications and bringing their ideas to life. With the power of smart contracts and the growing ecosystem of tools and services available to developers, the possibilities are endless.

Source code on Github

Additional notes and materials

You will also need an ABI file for the Thirdweb marketplace called proxy.json.

[{"inputs":[{"internalType":"address","name":"_trustedForwarder","type":"address"},{"internalType":"address","name":"_registry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"implementation","type":"address"},{"indexed":true,"internalType":"bytes32","name":"contractType","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ImplementationAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"implementation","type":"address"},{"indexed":false,"internalType":"bool","name":"isApproved","type":"bool"}],"name":"ImplementationApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"},{"indexed":false,"internalType":"address","name":"proxy","type":"address"},{"indexed":true,"internalType":"address","name":"deployer","type":"address"}],"name":"ProxyDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FACTORY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"}],"name":"addImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approval","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"bool","name":"_toApprove","type":"bool"}],"name":"approveImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"currentVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_type","type":"bytes32"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"deployProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"},{"internalType":"bytes32","name":"_salt","type":"bytes32"}],"name":"deployProxyByImplementation","outputs":[{"internalType":"address","name":"deployedProxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_type","type":"bytes32"},{"internalType":"bytes","name":"_data","type":"bytes"},{"internalType":"bytes32","name":"_salt","type":"bytes32"}],"name":"deployProxyDeterministic","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_type","type":"bytes32"},{"internalType":"uint256","name":"_version","type":"uint256"}],"name":"getImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_type","type":"bytes32"}],"name":"getLatestImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"registry","outputs":[{"internalType":"contract TWRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]