# Creating your first spore from scratch

This step-by-step guide will help you create your first spore on CKB testnet using the SDK of Spore Protocol. To follow along, you should be familiar with basic typescript and know how to install and configure Node.js dev environment.

## **Ingredients**

Make sure you have the following ingredients ready:

#### Tools You Need

* [ ] An IDE/Editor that supports Typescript
* [ ] [Node.js](https://nodejs.dev/en/learn/) development environment
* [ ] A package manager (we will be using [yarn](https://classic.yarnpkg.com/lang/en/docs/getting-started/))

#### **Project Ingredients**

* [ ] A CKB testnet address and its private key
* [ ] CKBytes testnet tokens from the [faucet](https://faucet.nervos.org/)
* [ ] The content you’d like to store on-chain

## **Gather Your Ingredients**

### **1. Create a CKB Address**

#### Method 1

You can use [**this generator tool**](https://ckb.tools/generator) to create a CKB testnet address.

#### Method 2

You can explore your options in the land of [**CKB Wallets**](https://docs.nervos.org/docs/basics/concepts/cryptowallet#ckb-wallets).

#### Method 3

To create an account via ckb-cli, you need use a testnet RPC node, make sure you have git and [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed. Then, in your terminal, run the following commands to install ckb-cli:

```bash
git clone <https://github.com/nervosnetwork/ckb-cli.git>
cd ckb-cli
cargo install --path . -f --locked
```

Create a CKB account

```bash
#For testnet
export API_URL=https://testnet.ckb.dev

ckb-cli account new
```

Get your private key with the follow command by replacing `<lock-arg>` with your lock\_arg ☝ and `<extended-privkey-path>` with where you want the key to be stored on your device. We’ll need it later when constructing the transaction.

```bash
ckb-cli account export --lock-arg <lock-arg> --extended-privkey-path <extended-privkey-path>
```

The output will give you 2 strings, **the top one will be your private key**.

For a more detailed guide on ckb-cli setup and interaction, take a peek at the [**ckb-cli GitHub**](https://github.com/nervosnetwork/ckb-cli).

### **2. Get Some CKBytes**

You'll need to reserve some CKBytes to mint your spore on-chain,

1. Head over to the [**faucet**](https://faucet.nervos.org/)
2. Input your CKB Testnet Address for some testnet tokens

### **3. Select Digital Content**

Choose your digital ingredient – an image, video, text or something else. Check and note down the [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) of your content (e.g., image/png). Just keep it under 500KB for a successful mint.

***

Now that we've created a testnet wallet, filled it with test CKBytes and a file ready to rock on the blockchain, we are ready to do some action.

## Let's craft your first spore by following these steps:

### **Step 1: Set Up the Project**

We'll use **`yarn`** as the package manager.

1. Create a project folder (e.g., **`my-spore-project`**) and navigate into it.

```bash
mkdir my-spore-project && cd my-spore-project
```

2. Initialize your project with yarn:

```bash
yarn init -y
```

3. Install required packages:

```bash
yarn add @types/node --dev
yarn add typescript ts-node
# Install sdk module
yarn add @spore-sdk/core
```

4. Create an **`index.ts`** file in your project's root folder.

```bash
touch index.ts
```

### **Step 2: Mix in the Ingredients**

Copy and edit the following code into your **`index.ts`** file, make sure to add your **private key in** **line 107**, specify the content type and filename:

{% code lineNumbers="true" %}

```typescript
import { SporeConfig, createSpore, updateWitnessArgs, isScriptValueEquals, predefinedSporeConfigs, defaultEmptyWitnessArgs } from '@spore-sdk/core';
import { hd, helpers, HexString, RPC } from '@ckb-lumos/lumos';
import { secp256k1Blake160 } from '@ckb-lumos/common-scripts';
import { Address, Hash, Script } from '@ckb-lumos/base';
import { readFileSync } from 'fs';
import { resolve } from 'path';

interface Wallet {
  lock: Script;
  address: Address;
  signMessage(message: HexString): Hash;
  signTransaction(txSkeleton: helpers.TransactionSkeletonType): helpers.TransactionSkeletonType;
  signAndSendTransaction(txSkeleton: helpers.TransactionSkeletonType): Promise<Hash>;
}

/**
 * Create a Secp256k1Blake160 Sign-all Wallet by a private key and a SporeConfig,
 * providing lock/address, and functions to sign message/transaction and send the transaction on-chain.
 */
function createSecp256k1Wallet(privateKey: HexString, config: SporeConfig): Wallet {
  const Secp256k1Blake160 = config.lumos.SCRIPTS.SECP256K1_BLAKE160!;

  // Generate a lock script from the private key
  const lock: Script = {
    codeHash: Secp256k1Blake160.CODE_HASH,
    hashType: Secp256k1Blake160.HASH_TYPE,
    args: hd.key.privateKeyToBlake160(privateKey),
  };

  // Generate address from the lock script
  const address = helpers.encodeToAddress(lock, {
    config: config.lumos,
  });

  // Sign for a message
  function signMessage(message: HexString): Hash {
    return hd.key.signRecoverable(message, privateKey);
  }

  // Sign prepared signing entries,
  // and then fill signatures into Transaction.witnesses
  function signTransaction(txSkeleton: helpers.TransactionSkeletonType): helpers.TransactionSkeletonType {
    const signingEntries = txSkeleton.get('signingEntries');
    const signatures = new Map<HexString, Hash>();
    const inputs = txSkeleton.get('inputs');

    let witnesses = txSkeleton.get('witnesses');
    for (let i = 0; i < signingEntries.size; i++) {
      const entry = signingEntries.get(i)!;
      if (entry.type === 'witness_args_lock') {
        // Skip if the input's lock does not match to the wallet's lock
        const input = inputs.get(entry.index);
        if (!input || !isScriptValueEquals(input.cellOutput.lock, lock)) {
          continue;
        }

        // Sign message
        if (!signatures.has(entry.message)) {
          const sig = signMessage(entry.message);
          signatures.set(entry.message, sig);
        }

        // Update signature to Transaction.witnesses
        const signature = signatures.get(entry.message)!;
        const witness = witnesses.get(entry.index, defaultEmptyWitnessArgs);
        witnesses = witnesses.set(entry.index, updateWitnessArgs(witness, 'lock', signature));
      }
    }

    return txSkeleton.set('witnesses', witnesses);
  }

  // Sign the transaction and send it via RPC
  async function signAndSendTransaction(txSkeleton: helpers.TransactionSkeletonType): Promise<Hash> {
    // 1. Sign transaction
    txSkeleton = secp256k1Blake160.prepareSigningEntries(txSkeleton, { config: config.lumos });
    txSkeleton = signTransaction(txSkeleton);

    // 2. Convert TransactionSkeleton to Transaction
    const tx = helpers.createTransactionFromSkeleton(txSkeleton);

    // 3. Send transaction
    const rpc = new RPC(config.ckbNodeUrl);
    return await rpc.sendTransaction(tx, 'passthrough');
  }

  return {
    lock,
    address,
    signMessage,
    signTransaction,
    signAndSendTransaction,
  };
}

// Get local image file and return an ArrayBuffer
async function fetchLocalFile(src: string) {
  const buffer = readFileSync(resolve(__dirname, src));
  return new Uint8Array(buffer).buffer;
}

async function main() {
  // Use the testnet configuration
  const config = predefinedSporeConfigs.Aggron4;

  // NOTE: Be careful to protect this and do not make your private key public except you know what you are doing!
  const privateKey = '0xc153ee57dc8ae3dac3495c828d6f8c3fef6b1d0c74fc31101c064137b3269d6d';

  // Create out account/sign helper
  const account = createSecp256k1Wallet(privateKey, config);

  let { txSkeleton } = await createSpore({
    data: {
      // Specify the content's MIME type
      contentType: 'image/png',
      // Extra parameters of contentType
      contentTypeParameters: {
        immortal: true,
      },
      // Fill in the spore's content as bytes,
      content: await fetchLocalFile('./image.jpg'),
      // fill in the spores' belonging cluster's id, optional, here we leave it empty
      clusterId: undefined,
    },
    fromInfos: [account.address],
    toLock: account.lock,
    config,
  });

  const hash = await account.signAndSendTransaction(txSkeleton);
  console.log('createSpore sent, txHash:', hash);
}

main();
```

{% endcode %}

### **Step 3: Send Your Spore On-chain!**

1. Save the changes to your **`index.ts`** file.
2. Run the Script

{% tabs %}
{% tab title="Run " %}

```bash
yarn run ts-node ./index.ts
```

{% endtab %}

{% tab title="Output" %}

```
createSpore sent, txHash: 0xfd5be439b84ef0e8d1917e2db9370bb99283ce30c953a10c9797ee7464077687
```

{% endtab %}
{% endtabs %}

3. Check your transaction on [**https://pudge.explorer.nervos.org**](https://pudge.explorer.nervos.org) by searching for your transaction hash to see the transaction details and your Spore cell.

{% hint style="success" %}
Congratulations! You've created your very first spore and preserved it for eternity on the blockchain.
{% endhint %}

In this tutorial, you learned how to:

1. Create a CKB address and obtain CKBytes tokens.
2. Set up your project with Spore Protocol SDK.
3. Mint your spore on-chain and verify the transaction.
