# Your First NFT

This tutorial describes how to create and transfer non-fungible assets on the Endless blockchain. The Endless no-code implementation for non-fungible digital assets can be found in the [nft.move](https://github.com/endless-labs/endless-move-framework/blob/main/endless-token/sources/nft.move) Move module.

## Step 1: Pick an SDK <a href="#step-1-pick-an-sdk" id="step-1-pick-an-sdk"></a>

Install your preferred SDK from the below list:

* TypeScript SDK

## Step 2: Run the example <a href="#step-2-run-the-example" id="step-2-run-the-example"></a>

Clone the `endless-ts-sdk` repo and build it:

```
git clone https://github.com/endless-labs/endless-ts-sdk.git
cd endless-ts-sdk
pnpm install
pnpm build
```

Navigate to the Typescript examples directory:

```
cd examples/typescript-esm
```

Install the necessary dependencies and build it:

```
pnpm install
pnpm build
```

Run the Typescript [`simple_digital_asset`](https://github.com/endless-labs/endless-ts-sdk/blob/main/examples/typescript-esm/simple_digital_asset.ts) example:

```
pnpm run simple_digital_asset
```

Clone the `endless-core` repo:

```
git clone https://github.com/endless-labs/endless.git
```

Navigate to the Python SDK directory:

```
cd endless-core/ecosystem/python/sdk
```

Install the necessary dependencies:

```
curl -sSL https://install.python-poetry.org | python3
poetry install
```

Run the Python [`simple_endless_token`](https://github.com/endless-labs/endless/blob/main/ecosystem/python/sdk/examples/simple_endless_token.py) example:

```
poetry run python -m examples.simple_endless_token
```

***

## Step 3: Understand the output <a href="#step-3-understand-the-output" id="step-3-understand-the-output"></a>

The following output should appear after executing the `simple_digital_asset` example, though some values will be different:

```
=== Addresses ===

Alice's address is: 0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162

=== Create the collection ===

Alice's collection: {
    "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753",
    "collection_name": "Example Collection",
    "creator_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162",
    "current_supply": 0,
    "description": "Example description.",
    "last_transaction_timestamp": "2023-11-29T21:26:03.204874",
    "last_transaction_version": 8001101,
    "max_supply": 18446744073709552000,
    "mutable_description": true,
    "mutable_uri": true,
    "table_handle_v1": null,
    "token_standard": "v2",
    "total_minted_v2": 0,
    "uri": "endless.dev"
}

=== Alice Mints the digital asset ===

Alice's digital assets balance: 1
Alice's digital asset: {
    "token_standard": "v2",
    "token_properties_mutated_v1": null,
    "token_data_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c",
    "table_type_v1": null,
    "storage_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c",
    "property_version_v1": 0,
    "owner_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162",
    "last_transaction_version": 8001117,
    "last_transaction_timestamp": "2023-11-29T21:26:04.521624",
    "is_soulbound_v2": false,
    "is_fungible_v2": false,
    "amount": 1,
    "current_token_data": {
        "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753",
        "description": "Example asset description.",
        "is_fungible_v2": false,
        "largest_property_version_v1": null,
        "last_transaction_timestamp": "2023-11-29T21:26:04.521624",
        "last_transaction_version": 8001117,
        "maximum": null,
        "supply": 0,
        "token_data_id": "0x9f4460e29a66b4e41cef1671767dc8a5e8c52a2291e36f84b8596e0d1205fd8c",
        "token_name": "Example Asset",
        "token_properties": {},
        "token_standard": "v2",
        "token_uri": "endless.dev/asset",
        "current_collection": {
            "collection_id": "0x23ece6c35415f5c5a720dc4de2820cabece0a6f1768095db479f657ad2c05753",
            "collection_name": "Example Collection",
            "creator_address": "0x770dbeb6101056eac5a19de9a73ad72fac512e0de909e7bcb13a9d9241d1d162",
            "current_supply": 1,
            "description": "Example description.",
            "last_transaction_timestamp": "2023-11-29T21:26:04.521624",
            "last_transaction_version": 8001117,
            "max_supply": 18446744073709552000,
            "mutable_description": true,
            "mutable_uri": true,
            "table_handle_v1": null,
            "token_standard": "v2",
            "total_minted_v2": 1,
            "uri": "endless.dev"
        }
    }
}

=== Transfer the digital asset to Bob ===

Alice's digital assets balance: 0
Bob's digital assets balance: 1
```

This example demonstrates:

\* Initializing the Endless client. \* The creation of two accounts: Alice and Bob. \* The funding and creation of Alice and Bob's accounts. \* The creation of a collection and a token using Alice's account. \* Alice sending a token to Bob.

The following output should appear after executing the `simple_endless_token` example, though some values will be different:

```
=== Addresses ===
Alice: 0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89
Bob: 0xfbca055c91d12989dc6a2c1a5e41ae7ba69a35454b04c69f03094bbccd5210b4

=== Initial Coin Balances ===
Alice: 100000000
Bob: 100000000

=== Creating Collection and Token ===

Collection data: {
    "address": "0x38f5310a8f6f3baef9a54daea8a356d807438d3cfe1880df563fb116731b671c",
    "creator": "0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89",
    "name": "Alice's",
    "description": "Alice's simple collection",
    "uri": "https://endless.dev"
}

Token owner: Alice
Token data: {
    "address": "0x57710a3887eaa7062f96967ebf966a83818017b8f3a8a613a09894d8465e7624",
    "owner": "0x391f8b07439768674023fb87ae5740e90fb8508600486d8ee9cc411b4365fe89",
    "collection": "0x38f5310a8f6f3baef9a54daea8a356d807438d3cfe1880df563fb116731b671c",
    "description": "Alice's simple token",
    "name": "Alice's first token",
    "uri": "https://endless.dev/img/nyan.jpeg",
    "index": "1"
}

=== Transferring the token to Bob ===
Token owner: Bob

=== Transferring the token back to Alice ===
Token owner: Alice
```

This example demonstrates:

\* Initializing the REST and faucet clients. \* The creation of two accounts: Alice and Bob. \* The funding and creation of Alice and Bob's accounts. \* The creation of a collection and a token using Alice's account. \* Alice sending a token to Bob. \* Bob sending the token back to Alice.

***

## Step 4: The SDK in depth <a href="#step-4-the-sdk-in-depth" id="step-4-the-sdk-in-depth"></a>

{% hint style="info" %}
See the full code See [`simple_digital_asset`](https://github.com/endless-labs/endless-ts-sdk/blob/main/examples/typescript-esm/simple_digital_asset.ts) for the complete code as you follow the below steps.
{% endhint %}

{% hint style="info" %}
See the full code See [`simple_endless_token`](https://github.com/endless-labs/endless/blob/main/ecosystem/python/sdk/examples/simple_endless_token.py) for the complete code as you follow the below steps.
{% endhint %}

***

### Step 4.1: Initializing the clients <a href="#step-4.1-initializing-the-clients" id="step-4.1-initializing-the-clients"></a>

In the first step, the `simple_digital_asset` example initializes the Endless client:

```
const ENDLESS_NETWORK: Network =
  NetworkToNetworkName[process.env.ENDLESS_NETWORK] || Network.DEVNET;
const config = new EndlessConfig({ network: ENDLESS_NETWORK });
const endless = new Endless(config);
```

{% hint style="info" %}
By default, the Endless client points to Endless devnet services. However, it can be configured with the `network` input argument
{% endhint %}

In the first step, the example initializes both the API and faucet clients.

* The API client interacts with the REST API.
* The faucet client interacts with the devnet Faucet service for creating and funding accounts.

```
:!: static/sdks/python/examples/simple_endless_token.py section_1a
```

Using the API client we can create a `TokenClient` that we use for common token operations such as creating collections and tokens, transferring them, claiming them, and so on.

```
:!: static/sdks/python/examples/simple_endless_token.py section_1b
```

[`common.py`](https://github.com/endless-labs/endless/tree/main/ecosystem/python/sdk/examples/common.py) initializes these values as follows:

```
:!: static/sdks/python/examples/common.py section_1
```

{% hint style="info" %}
By default, the URLs for both the services point to Endless devnet services. However, they can be configured with the following environment variables:

* `ENDLESS_NODE_URL`
* `ENDLESS_FAUCET_URL`
  {% endhint %}

***

### Step 4.2: Creating local accounts <a href="#step-4.2-creating-local-accounts" id="step-4.2-creating-local-accounts"></a>

The next step is to create two accounts locally. Accounts consist of a public address and the public/private key pair used to authenticate ownership of the account. This step demonstrates how to generate an Account and store its key pair and address in a variable.

```
const alice = Account.generate();
const bob = Account.generate();
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_2
```

{% hint style="info" %}
Note that this only generates the local keypair. After generating the keypair and public address, the account still does not exist on-chain.
{% endhint %}

***

### Step 4.3: Creating blockchain accounts <a href="#step-4.3-creating-blockchain-accounts" id="step-4.3-creating-blockchain-accounts"></a>

In order to actually instantiate the Account on-chain, it must be explicitly created somehow. On the devnet network, you can request free coins with the Faucet API to use for testing purposes. This example leverages the faucet to fund and inadvertently create Alice and Bob's accounts:

```
await endless.fundAccount({
  accountAddress: alice.accountAddress,
  amount: 100_000_000,
});
await endless.faucet.fundAccount({
  accountAddress: bob.accountAddress,
  amount: 100_000_000,
});
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_3
```

***

### Step 4.4: Creating a collection <a href="#step-4.4-creating-a-collection" id="step-4.4-creating-a-collection"></a>

Now begins the process of creating the digital, non-fungible assets. First, as the creator, you must create a collection that groups the assets. A collection can contain zero, one, or many distinct fungible or non-fungible assets within it. The collection is simply a container, intended only to group assets for a creator.

Your application will call `createCollectionTransaction` and then `signAndSubmitTransaction` to chain:

```
const createCollectionTransaction = await endless.createCollectionTransaction({
  creator: alice,
  description: collectionDescription,
  name: collectionName,
  uri: collectionURI,
});

const committedTxn = await endless.signAndSubmitTransaction({
  signer: alice,
  transaction: createCollectionTransaction,
});
```

This is the function signature of `createCollectionTransaction`. It returns a `SingleSignerTransaction` that can be simulated or submitted to chain:

```
export async function createCollectionTransaction(
  args: {
    creator: Account;
    description: string;
    name: string;
    uri: string;
    options?: InputGenerateTransactionOptions;
  } & CreateCollectionOptions,
): Promise<SingleSignerTransaction>;
```

Your application will call `create_collection`:

```
:!: static/sdks/python/examples/simple_endless_token.py section_4
```

This is the function signature of `create_collection`. It returns a transaction hash:

```
:!: static/sdks/python/endless_sdk/endless_token_client.py create_collection
```

***

### Step 4.5: Creating a token <a href="#step-4.5-creating-a-token" id="step-4.5-creating-a-token"></a>

To create a token, the creator must specify an associated collection. A token must be associated with a collection, and that collection must have remaining tokens that can be minted. There are many attributes associated with a token, but the helper API exposes only the minimal amount required to create static content.

Your application will call `mintTokenTransaction`:

```
const mintTokenTransaction = await endless.mintTokenTransaction({
  creator: alice,
  collection: collectionName,
  description: tokenDescription,
  name: tokenName,
  uri: tokenURI,
});

const committedTxn = await endless.signAndSubmitTransaction({
  signer: alice,
  transaction: mintTokenTransaction,
});
```

This is the function signature of `mintTokenTransaction`. It returns a `SingleSignerTransaction` that can be simulated or submitted to chain:

```
async mintTokenTransaction(args: {
    creator: Account;
    collection: string;
    description: string;
    name: string;
    uri: string;
    options?: InputGenerateTransactionOptions;
  }): Promise<SingleSignerTransaction>
```

Your application will call `mint_token`:

```
:!: static/sdks/python/examples/simple_endless_token.py section_5
```

This is the function signature of `mint_token`. It returns a transaction hash:

```
:!: static/sdks/python/endless_sdk/endless_token_client.py mint_token
```

***

### Step 4.6: Reading token and collection metadata <a href="#step-4.6-reading-token-and-collection-metadata" id="step-4.6-reading-token-and-collection-metadata"></a>

Both the collection and token assets are Objects on-chain with unique addresses. Their metadata is stored at the object address. The SDKs provide convenience wrappers around querying this data:

To read a collection's metadata:

```
const alicesCollection = await endless.getCollectionData({
  creatorAddress: alice.accountAddress,
  collectionName,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});
console.log(`Alice's collection: ${JSON.stringify(alicesCollection, null, 4)}`);
```

To read an owned token's metadata:

```
const alicesDigitalAsset = await endless.getOwnedDigitalAssets({
  ownerAddress: alice.accountAddress,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});

console.log(
  `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`,
);
```

To read a collection's metadata:

```
:!: static/sdks/python/examples/simple_endless_token.py section_6
```

To read a token's metadata:

```
:!: static/sdks/python/examples/simple_endless_token.py get_token_data
```

***

### Step 4.7: Reading an object's owner <a href="#step-4.7-reading-an-objects-owner" id="step-4.7-reading-an-objects-owner"></a>

Each object created from the endless`_token.move` contract is a distinct asset. The assets owned by a user are stored separately from the user's account. To check if a user owns an object, check the object's owner:

```
const alicesDigitalAsset = await endless.getOwnedDigitalAssets({
  ownerAddress: alice.accountAddress,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});

console.log(
  `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`,
);
```

```
export async function getOwnedDigitalAssets(args: {
  endlessConfig: EndlessConfig;
  ownerAddress: AccountAddressInput;
  options?: PaginationArgs & OrderByArg<GetTokenActivityResponse[0]>;
}): Promise<GetOwnedTokensResponse> {
  const { endlessConfig, ownerAddress, options } = args;

  const whereCondition: CurrentTokenOwnershipsV2BoolExp = {
    owner_address: { _eq: AccountAddress.from(ownerAddress).toStringLong() },
    amount: { _gt: 0 },
  };

  const graphqlQuery = {
    query: GetCurrentTokenOwnership,
    variables: {
      where_condition: whereCondition,
      offset: options?.offset,
      limit: options?.limit,
      order_by: options?.orderBy,
    },
  };

  const data = await queryIndexer<GetCurrentTokenOwnershipQuery>({
    endlessConfig,
    query: graphqlQuery,
    originMethod: "getOwnedDigitalAssets",
  });

  return data.current_token_ownerships_v2;
}
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_7
```

```
:!: static/sdks/python/examples/simple_endless_token.py owners
```

***

### Step 4.8: Transfer the object back and forth <a href="#step-4.8-transfer-the-object-back-and-forth" id="step-4.8-transfer-the-object-back-and-forth"></a>

Each object created from the endless`_token.move` contract is a distinct asset. The assets owned by a user are stored separately from the user's account. To check if a user owns an object, check the object's owner:

```
const alicesDigitalAsset = await endless.getOwnedDigitalAssets({
  ownerAddress: alice.accountAddress,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});

console.log(
  `Alice's digital asset: ${JSON.stringify(alicesDigitalAsset[0], null, 4)}`,
);
```

```
const transferTransaction = await endless.transferDigitalAsset({
  sender: alice,
  digitalAssetAddress: alicesDigitalAsset[0].token_data_id,
  recipient: bob.accountAddress,
});
const committedTxn = await endless.signAndSubmitTransaction({
  signer: alice,
  transaction: transferTransaction,
});
const pendingTxn = await endless.waitForTransaction({
  transactionHash: committedTxn.hash,
});
```

```
const alicesDigitalAssetsAfter = await endless.getOwnedDigitalAssets({
  ownerAddress: alice.accountAddress,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});
console.log(
  `Alices's digital assets balance: ${alicesDigitalAssetsAfter.length}`,
);

const bobDigitalAssetsAfter = await endless.getOwnedDigitalAssets({
  ownerAddress: bob.accountAddress,
  minimumLedgerVersion: BigInt(pendingTxn.version),
});
console.log(`Bob's digital assets balance: ${bobDigitalAssetsAfter.length}`);
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_8
```

```
:!: static/sdks/python/endless_sdk/endless_token_client.py transfer_token
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_9
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_10
```

```
:!: static/sdks/python/examples/simple_endless_token.py section_11
```

***

## Supporting documentation <a href="#supporting-documentation" id="supporting-documentation"></a>

* [Account basics](/endless/devbuild/technical-documentation/endless-account.md)
* [TypeScript SDK](https://github.com/endless-labs/endless-ts-sdk)
* [Golang SDK](https://github.com/endless-labs/endless-go-sdk)
* [Rust SDK](https://github.com/endless-labs/endless-rust-sdk)
* [REST API specification](https://rpc.endless.link/v1/spec#/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.endless.link/endless/devbuild/build/tutorials/your-first-nft.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
