# Game

## TypeScript SDK Quick Start

### 1 Purpose <a href="#id-1-purpose" id="id-1-purpose"></a>

In this article, we will use an in-game scenario to describe how to use the move language to develop a contract and deploy it to the Endless Blockchain. After the contract is successfully published, we will use TS-SDK to interact with the published contract and call the functions provided by the contract.

#### 1.1 Introduction Features <a href="#id-11-introduction-features" id="id-11-introduction-features"></a>

In game development, core elements such as character management, equipment systems, and item mechanics form fundamental building blocks of the creative process. Moreover, their design and implementation account for approximately 50% of development workloads, making them essential components that define the game's operational framework. If we want to introduce blockchain into traditional game development, we need to use blockchain-supported contracts to implement these basic game functions, which can enable players to have true ownership of their digital assets, thereby further increasing their engagement.

Next we will develop a contract that provides the creation of characters, equipment and item. They have different properties according to the design. Equipment can be enhanced with items to improve its properties. Characters can use equipment to improve properties. There is a clear subordinate relationship between them.

The characters, equipment, and items in the game are all unique and valuable. NFTs on the blockchain has the characteristics of uniqueness, indivisibility, irreplaceability, and scarcity. Therefore, it is very suitable to use NFTs to realize characters, equipment, and items.

#### 1.2 Move Contract's Function Description <a href="#id-12-move-contracts-function-description" id="id-12-move-contracts-function-description"></a>

We use the NFT functions provided by endless chain to implement a sample contract called endless-hero. The design functions of this contract are as follows:

1. Use NFT to implement hero, weapon, armor, shield and gem.
2. Hero can equip a weapon, an armor, and a shield, and has its own attributes gender and race.
3. Weapon can be inlaid with a gem, with weapon\_type, attack and weight
4. Armor can be inlaid with a gem, with defense and weight
5. Shield can be inlaid with a gem, with defense and weight
6. The contract creator acts as the sole administrator. This role is responsible for minting all NFTs (heroes, equipment, gems) and transferring them to ordinary players, such as Alice. Players can then inlay and remove gems from their equipment, as well as equip or unequip items on their heroes.

### 2 Endless Move Smart Contract Develop <a href="#id-2-endless-move-smart-contract-develop" id="id-2-endless-move-smart-contract-develop"></a>

#### 2.1 Install Endless CLI <a href="#id-21-install-endless-cli" id="id-21-install-endless-cli"></a>

Download endless-cli in <https://github.com/endless-labs/endless-release/releases>

**2.1.1 Windows**

Unzip the downloaded endless-cli-Windows-x86\_64.zip to a specified folder, and it is recommended to add the folder to the system PATH variable.

```
C:\Users\Alice>endless --version
endless 2.4.0
```

After installation, you can use `endless --help` to view detailed commands.

**2.1.2 Ubuntu**

Unzip the downloaded endless-cli-Ubuntu-x86\_64.zip to a specified folder, and it is recommended to add the folder to the system PATH variable.

#### 2.2 Install endless-vscode-plugin With VSCode <a href="#id-22-install-endless-vscode-plugin-with-vscode" id="id-22-install-endless-vscode-plugin-with-vscode"></a>

Flow <https://github.com/endless-labs/endless-vscode-plugin> 's introduction,install endless-vscode-plugin.

#### 2.3 Create endless-hero Move Contract <a href="#id-23-create-endless-hero-move-contract" id="id-23-create-endless-hero-move-contract"></a>

**2.3.1 create move directory**

create move directory in endless-hero

```
{path}\mkdir endless-hero
{path}\cd  endless-hero
{path}\endless-hero>mkdir move
{path}\endless-hero>cd move
```

**2.3.2 initialization move contract**

```
endless move init --name endless-hero
Success
```

This will create some directories and Move.toml

```
move
├─scripts
├─sources
└─tests
└─Move.toml
```

**2.3.3 Use endless to initialize a account used for deploy contract**

```
endless init
Configuring for profile default
Choose network from [testnet, mainnet, local, custom | defaults to testnet]
```

select testnet

```
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
```

Enter

```
No key given, generating key...
Account 0xd4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3 doesn't exist, creating it and funding it with 100000000 Veins
Account 0xd4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3 funded successfully

---
Endless CLI is now set up for account 0xd4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3 as profile default!  Run `endless --help` for more information about commands
Success
```

This will create default account and saved in .endless\config.yaml file.This accout is auto faucet 1 EDS.

.endless\config.yaml

```
---
profiles:
  default:
    private_key: "0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    public_key: "0x61f0767d5c8a158f10b6cdc19703558095c159b44da4d23745c6e00bdbbcf87e"
    account: d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3
    rest_url: "https://rpc-test.endless.link"

```

```
endless account list --query balance 
[1000000000]
```

**2.3.4 Modify Move.toml**

**2.3.4.1 add contract address**

add contract address under \[addresses]

```
[addresses]
hero_address = "d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3"
```

**2.3.4.2 add dependencies**

delete

```
[dependencies.EndlessFramework]
git = "https://github.com/endless-labs/endless-move-framework"
rev = "main"
subdir = "endless-framework"
```

add

```
[dependencies]
EndlessFramework = {git = "https://github.com/endless-labs/endless-move-framework", subdir = "endless-framework", rev = "8332b04"}
EndlessToken = {git = "https://github.com/endless-labs/endless-move-framework", subdir = "endless-token", rev = "8332b04"}
```

EndlessFramework: move contract develop necessary dependencies. EndlessToken:Providing NFT functionality

**2.3.5 Develop hero.move**

**2.3.5.1 Create hero.move file in endless-hero\move\sources**

hero.move provided interactive functions as follow

* entry fun mint\_hero : mint hero nft.
* entry fun mint\_weapon : mint weapon nft.
* entry fun mint\_gem : mint gem nft.
* entry fun transfer\_nft\_to\_player : transfer nft to player as alice. include hero weapon gem etc.
* entry fun hero\_equip\_weapon : hero equip weapon.
* entry fun hero\_unequip\_weapon : hero remove weapon.
* entry fun weapon\_equip\_gem : weapon inlaid gem.
* entry fun weapon\_unequip\_gem : weapon remove gem.

**2.3.5.2 Develop hero.move**

**2.3.5.2.1 Dependencies Required For Developing NFT**

In addition to the Move standard library, Endless also provides the Endless standard library and framework. The following is an introduction to the basic functions of each library.

* \[std] Provides error definition, basic encoding, hash, string, container and other functions.
* \[endless\_std] Provides basic data types, encryption algorithms, container encapsulation, etc.
* \[endless\_framework] Provides chain-related functions such as accounts, object, asset, pledges, signatures, voting, etc.
* \[endless\_token] New digital asset standard implementation, providing the functionality of both fungible and non-fungible asset.

We will use all the libraries mentioned above.

**2.3.5.2.2 Object and Resource**

Resource and object are two core state data structures.

| Feature            | Resource                                               | Object                                                      |
| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------- |
| Definition         | Defined as `struct` with abilities like `key`, `store` | Created via `object` module API (e.g., `create_object`)     |
| Lifecycle          | Bound to an account, stored under an account address   | Independent of accounts, can be owned or referenced         |
| Address Ownership  | Tied to a specific account address                     | Has its own object address, can be transferred or nested    |
| Access Method      | Accessed via account address and resource path         | Accessed via object ID and standard library functions       |
| Nesting Support    | Not supported (stored directly under accounts)         | Supports nesting, can be a child of another object          |
| Transferability    | Not directly transferable (requires custom logic)      | Transferable via `object::transfer`                         |
| Type Safety        | Strongly typed, checked at compile time                | Also strongly typed, with more flexible structure           |
| Typical Use Case   | Account-owned state like balances                      | Flexible data like NFTs, game characters, composable assets |
| Gas Cost           | Typically lower (simple structure)                     | Slightly higher (requires object storage management)        |
| Composability      | Not supported                                          | Supports composability and nesting                          |
| Underlying Storage | Stored directly in the global state under accounts     | Managed by the object module in global state                |

So we can define some resource to store their properties as as follows.

```
#[resource_group_member(group = endless_framework::object::ObjectGroup)]
struct Armor has key {
    defense: u64,
    gem: Option<Object<Gem>>,
    weight: u64,
}

#[resource_group_member(group = endless_framework::object::ObjectGroup)]
struct Gem has key {
    attack_modifier: u64,
    defense_modifier: u64,
    magic_attribute: String,
}
```

**2.3.5.2.3 Object and NFT**

Object is the underlying mechanism, providing the ability to nest, transfer, and combine;

NFT is a specific asset model based on Object, adding information such as TokenId and metadata.

In hero.move, we can provide a basic mint nft function, through which heroes, equipment and items are mint.

```
fun create(
    creator: &signer,
    description: String,
    name: String,
    uri: String,
): ConstructorRef acquires OnChainConfig {
    let on_chain_config = borrow_global<OnChainConfig>(signer::address_of(creator));
    token::create_named_token(
        creator,
        on_chain_config.collection,
        description,
        name,
        option::none(),
        uri,
    )
}
```

Mint a hero use function create\_hero.

```
public fun create_hero(
    creator: &signer,
    description: String,
    gender: String,
    name: String,
    race: String,
    uri: String,
): Object<Hero> acquires OnChainConfig {
    let constructor_ref = create(creator, description, name, uri);
    let token_signer = object::generate_signer(&constructor_ref);

    let hero = Hero {
        armor: option::none(),
        gender,
        race,
        shield: option::none(),
        weapon: option::none(),
        mutator_ref: token::generate_mutator_ref(&constructor_ref),
    };
    move_to(&token_signer, hero);

    object::address_to_object(signer::address_of(&token_signer))
}
```

**2.3.5.2.4 NFT Transfer**

Equipment uses items, heroes use equipment, which can essentially be seen as the transfer of NFT.

Transfer a nft can call object::transfer();

```
entry fun transfer_nft_to_player(
    owner: &signer,
    nft_address: address,
    to_player: address,
)  {
    if (exists<Armor>(nft_address)) {
        let armor_obj = object::address_to_object<Armor>(nft_address);
        assert!(object::is_owner(armor_obj, signer::address_of(owner)), EOWNER_NOT_MATCH);
        object::transfer(owner,armor_obj, to_player);
    } else if (exists<Gem>(nft_address)) {
        let gem_obj = object::address_to_object<Gem>(nft_address);
        assert!(object::is_owner(gem_obj, signer::address_of(owner)), EOWNER_NOT_MATCH);
        object::transfer(owner,gem_obj, to_player);
    } else if (exists<Hero>(nft_address)) {
        let hero_obj = object::address_to_object<Hero>(nft_address);
        assert!(object::is_owner(hero_obj, signer::address_of(owner)), EOWNER_NOT_MATCH);
        object::transfer(owner,hero_obj, to_player);
    } else if (exists<Shield>(nft_address)) {
        let shield_obj = object::address_to_object<Shield>(nft_address);
        assert!(object::is_owner(shield_obj, signer::address_of(owner)), EOWNER_NOT_MATCH);
        object::transfer(owner,shield_obj, to_player);
    } else if (exists<Weapon>(nft_address)) {
            let weapon_obj = object::address_to_object<Weapon>(nft_address);
        assert!(object::is_owner(weapon_obj, signer::address_of(owner)), EOWNER_NOT_MATCH);
        object::transfer(owner,weapon_obj, to_player);
    } else {
        abort EINVALID_TYPE
    }
}
```

The complete hero.move implementation can be found in the code repository.

**2.3.6 Compile endless-hero**

endless move compile

```
\endless-hero>cd move
endless-hero\move>endless move compile
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY EndlessFramework
INCLUDING DEPENDENCY EndlessStdlib
INCLUDING DEPENDENCY EndlessToken
INCLUDING DEPENDENCY MoveStdlib
BUILDING endless-hero
[d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3::hero]
```

**2.3.7 Deploy endless-hero**

```
\endless-hero\move>endless move publish 
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY EndlessFramework
INCLUDING DEPENDENCY EndlessStdlib
INCLUDING DEPENDENCY EndlessToken
INCLUDING DEPENDENCY MoveStdlib
BUILDING endless-hero
package size 6129 bytes
Do you want to submit a transaction for a range of [438600 - 657900] Veins at a gas unit price of 100 Veins? [yes/no] >
yes
transaction_hash:MVZyn5hamcCX5RhY8KrbVmLJ9Je7r3Ffh1dyDPdZuXH
gas_used:4386
gas_unit_price:100
sender:d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3
sequence_number:0
success:true
timestamp_us:1750486559926763
version:235395928
vm_status:Executed successfully
```

### 3 Create A TypeScript Project <a href="#id-3-create-a-typescript-project" id="id-3-create-a-typescript-project"></a>

#### 3.1 Install development environment <a href="#id-31-install-development-environment" id="id-31-install-development-environment"></a>

Install nodejs v20.19.2.

#### 3.2 Initialize TypeScript Project <a href="#id-32-initialize-typescript-project" id="id-32-initialize-typescript-project"></a>

```
{path}\endless-hero>npm init -y
```

This will create a package.json file, allowing you to install dependencies and run scripts.

#### 3.3 Install Dependencies <a href="#id-33-install-dependencies" id="id-33-install-dependencies"></a>

You will need the Endless TypeScript SDK and dotenv to manage environment variables:

```
npm install @endlesslab/endless-ts-sdk dotenv
npm install --save-dev @types/node
```

#### 3.4 Create tsconfig.json <a href="#id-34-create-tsconfigjson" id="id-34-create-tsconfigjson"></a>

Create a tsconfig.json file with the following:

```
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["node"],
    "lib": ["es2020"]
  }
}
```

This configuration ensures TypeScript properly recognizes Node.js types and provides appropriate type checking.

#### 3.5 Configure Environment Variables <a href="#id-35-configure-environment-variables" id="id-35-configure-environment-variables"></a>

Create a .env file with the following:

```
ENDLESS_NETWORK=testnet
```

By default, we’ll use testnet, but you can also choose mainnet depending on your needs.

#### 3.6 Create index.ts <a href="#id-36-create-indexts" id="id-36-create-indexts"></a>

#### 3.7 Create web3 dir <a href="#id-37-create-web3-dir" id="id-37-create-web3-dir"></a>

```
mkdir web3
```

This folder is used to store code related to interactions with the Endless chain, including creating accounts and faucet EDS on testnet.

### 4 Create account and get testnet EDS for development <a href="#id-4-create-account-and-get-testnet-eds-for-development" id="id-4-create-account-and-get-testnet-eds-for-development"></a>

#### 4.1 create account.ts in web3 and import "@endlesslab/endless-ts-sdk <a href="#id-41-create-accountts-in-web3-and-import-endlesslabendless-ts-sdk" id="id-41-create-accountts-in-web3-and-import-endlesslabendless-ts-sdk"></a>

```
// account.ts
import {
    Account,
    AccountAddress,
    Ed25519Account,
    Ed25519PrivateKey,
    Endless,
} from "@endlesslab/endless-ts-sdk";
import * as fs from 'fs';
```

#### 4.2 create account key and save in a file <a href="#id-42-create-account-key-and-save-in-a-file" id="id-42-create-account-key-and-save-in-a-file"></a>

Save a account as AccountData type, include private\_key and address as the following.\
If AccountName's file is not exist, use sdk's Account generate a key and save it, otherwise load it.

```
// account.ts
interface AccountData {
    address: string;
    private_key: string;
}

async function writeJsonFileAsync(filename: string, data: any): Promise<void> {
    try {
        const jsonString = JSON.stringify(data, null, 2);
        await fs.promises.writeFile(filename, jsonString, 'utf8');
        console.log(`✅ Asynchronous write success: Data has been saved to ${filename}`);
    } catch (error) {
        console.error(`❌ Asynchronous write failed ${filename}:`, error);
    }
}

async function readJsonFileAsync<T>(filename: string): Promise<T | null> {
    try {
        const fileContent = await fs.promises.readFile(filename, 'utf8');
        console.log(`✅ Asynchronous read succeeded: Read file contents from ${filename}.`);
        const parsedData: T = JSON.parse(fileContent);
        console.log(`✅ Deserialization successful: Parsed ${filename} contents into an object.`);
        return parsedData;
    } catch (error: any) {
        if (error.code === 'ENOENT') { // ENOENT Indicates that the file does not exist
            console.error(`❌ Read failed: file '${filename}' does not exist.`);
        } else if (error instanceof SyntaxError) {
            console.error(`❌ Read failed: The contents of file '${filename}' are not in valid JSON format.`, error.message);
        } else {
            console.error(`❌ Asynchronous read or deserialization failed from ${filename}:`, error);
        }
        return null;
    }
}

export async function createOrLoadAccount(accountName: string): Promise<Ed25519Account> {
    const filename = accountName + ".json";
    const json_data = await readJsonFileAsync<AccountData>(filename);
    let account = null;
    if (json_data == null) {
        account = Account.generate();
        const json_data: AccountData = {
            address: account.accountAddress.toString(),
            private_key: account.privateKey.toString()
        };
        writeJsonFileAsync(filename, json_data)
            .then(() => {
                console.log(`Completes a single object asynchronous write operation to ${filename}`);
            });

    } else {
        const address = AccountAddress.fromString(json_data.address);
        account = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(json_data.private_key) });
    }
    return account;
}
```

#### 4.3 faucet EDS on testnet <a href="#id-43-faucet-eds-on-testnet" id="id-43-faucet-eds-on-testnet"></a>

When faucet account on testnet, we wait some seconds to check it blances.

```
// account.ts
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

export async function fundAccount(chain: Endless, signer: Ed25519Account) {
    try {
        console.log("Funding ", signer.accountAddress.toString(), "'s account...");
        await chain.fundAccount({ signer });
    } catch (error: any) {
        console.error("Error:", error.message);
    }
    await sleep(3000);
    try {
        const balance = await chain.viewEDSBalance(signer.accountAddress);
        console.log("current balance:", balance);
    } catch (error: any) {
        console.error("Error:", error.message);
    }
}
```

#### 4.4 Verification function <a href="#id-44-verification-function" id="id-44-verification-function"></a>

**4.4.1 account.ts**

Now,account.ts is as the following.

```
// account.ts
import {
    Account,
    AccountAddress,
    Ed25519Account,
    Ed25519PrivateKey,
    Endless,
} from "@endlesslab/endless-ts-sdk";
import * as fs from 'fs';

interface AccountData {
    address: string;
    private_key: string;
}

async function writeJsonFileAsync(filename: string, data: any): Promise<void> {
    try {
        const jsonString = JSON.stringify(data, null, 2);
        await fs.promises.writeFile(filename, jsonString, 'utf8');
        console.log(`✅ Asynchronous write success: Data has been saved to ${filename}`);
    } catch (error) {
        console.error(`❌ Asynchronous write failed ${filename}:`, error);
    }
}

async function readJsonFileAsync<T>(filename: string): Promise<T | null> {
    try {
        const fileContent = await fs.promises.readFile(filename, 'utf8');
        console.log(`✅ Asynchronous read succeeded: Read file contents from ${filename}.`);
        const parsedData: T = JSON.parse(fileContent);
        console.log(`✅ Deserialization successful: Parsed ${filename} contents into an object.`);
        return parsedData;
    } catch (error: any) {
        if (error.code === 'ENOENT') { // ENOENT Indicates that the file does not exist
            console.error(`❌ Read failed: file '${filename}' does not exist.`);
        } else if (error instanceof SyntaxError) {
            console.error(`❌ Read failed: The contents of file '${filename}' are not in valid JSON format.`, error.message);
        } else {
            console.error(`❌ Asynchronous read or deserialization failed from ${filename}:`, error);
        }
        return null;
    }
}

export async function createOrLoadAccount(accountName: string): Promise<Ed25519Account> {
    const filename = accountName + ".json";
    const json_data = await readJsonFileAsync<AccountData>(filename);
    let account = null;
    if (json_data == null) {
        account = Account.generate();
        const json_data: AccountData = {
            address: account.accountAddress.toString(),
            private_key: account.privateKey.toString()
        };
        writeJsonFileAsync(filename, json_data)
            .then(() => {
                console.log(`Completes a single object asynchronous write operation to ${filename}`);
            });

    } else {
        const address = AccountAddress.fromString(json_data.address);
        account = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(json_data.private_key) });
    }
    return account;
}

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

export async function fundAccount(chain: Endless, signer: Ed25519Account) {
    try {
        console.log("Funding ", signer.accountAddress.toString(), "'s account...");
        await chain.fundAccount({ signer });
    } catch (error: any) {
        console.error("Error:", error.message);
    }
    await sleep(3000);
    try {
        const balance = await chain.viewEDSBalance(signer.accountAddress);
        console.log("current balance:", balance);
    } catch (error: any) {
        console.error("Error:", error.message);
    }
}
```

**4.4.2 index.ts**

Modify index.ts as follow.

```
import "dotenv/config";
import {
    Endless,
    EndlessConfig,
    Network,
    NetworkToNetworkName,
} from "@endlesslab/endless-ts-sdk";

import { createOrLoadAccount, fundAccount } from './web3/account'
// Verify environment variables are loaded
console.log("Environment variables loaded:", {
    ENDLESS_NETWORK: process.env.ENDLESS_NETWORK || "not set"
});

// console.log("Step 1: Setting up a client to connect to Aptos");
const ENDLESS_NETWORK = NetworkToNetworkName[process.env.ENDLESS_NETWORK!] || Network.DEVNET;
const config = new EndlessConfig({ network: ENDLESS_NETWORK });
const endless = new Endless(config);

const command = process.argv[2];

async function create_or_load_account() {
    const name = process.argv[3];
    if (name == null) {
        create_or_load_account_help()
        return;
    }
    await createOrLoadAccount(name);
    return;
}

function create_or_load_account_help() {
    console.log("1.[");
    console.log("\x1b[32mcommand:\x1b[0m");
    console.log("create_or_load_account");
    console.log("\x1b[32mparameters:\x1b[0m");
    console.log("account_name");
    console.log("\x1b[32mexample:\x1b[0m");
    console.log("npx ts-node index.ts create_or_load_account user1");
    console.log("]");
}


async function fund_account() {
    const name = process.argv[3];
    if (name == null) {
        fund_account_help()
        return;
    }
    const account = await createOrLoadAccount(name);
    await fundAccount(endless, account);
    return;
}

function fund_account_help() {
    console.log("2.[");
    console.log("\x1b[32mcommand:\x1b[0m");
    console.log("fund_account");
    console.log("\x1b[32mparameters:\x1b[0m");
    console.log("account_name");
    console.log("\x1b[32mexample:\x1b[0m");
    console.log("npx ts-node index.ts fund_account user1");
    console.log("]");
}

function help() {
    create_or_load_account_help();
}

async function main() {
    if (command === "help" || command == null) {
        help();
    } else {
        switch (command) {
            case "create_or_load_account": {
                await create_or_load_account();
            }
                break;
            case "fund_account": {
                await fund_account();
            }
                break;
        }
    }
}

main();

```

**4.4.3 Create a account with name alice**

* craete alice

```
npx ts-node index.ts create_or_load_account alice
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
❌ Read failed: file 'alice.json' does not exist.
✅ Asynchronous write success: Data has been saved to alice.json
Completes a single object asynchronous write operation to alice.json
```

* alice.json After craete alice,will create a alice.json.

```
{
  "address": "0x75f4fd0c6f2f6f29326f5a286fc7d368cb3cfcbea067d9cc9e8f262a8b596649",
  "private_key": "********"
}
```

* faucet alice

```
npx ts-node index.ts fund_account alice
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from alice.json.
✅ Deserialization successful: Parsed alice.json contents into an object.
Funding  0x75f4fd0c6f2f6f29326f5a286fc7d368cb3cfcbea067d9cc9e8f262a8b596649 's account...
current balance: 1000000000n
```

Account can faucet one times per day,now alice balance is 1 EDS.

### 5 Implementing interact with contracts use @endlesslab/endless-ts-sdk <a href="#id-5-implementing-interact-with-contracts-use-endlesslabendless-ts-sdk" id="id-5-implementing-interact-with-contracts-use-endlesslabendless-ts-sdk"></a>

#### 5.1 Configure contract address <a href="#id-51-configure-contract-address" id="id-51-configure-contract-address"></a>

open .env file. add CONTRACT\_ADDRESS=d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3

```
ENDLESS_NETWORK=testnet
CONTRACT_ADDRESS=d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3
```

#### 5.2 Mint a hero nft <a href="#id-52-mint-a-hero-nft" id="id-52-mint-a-hero-nft"></a>

* Create nft.ts and event.ts files in web3 directory.
* Define a MintEvent Data type in event.ts.

```
// event.ts
export interface CollectionMintData {
  collection: string;
  index: string;
  token: string;
}
```

* Implement mint a hero nft function in nft.ts.

```
//  nft.ts.
import {
    Account,
    AccountAddress,
    Endless,
    isUserTransactionResponse,
    MoveFunctionId,
    SimpleTransaction
} from "@endlesslab/endless-ts-sdk";
import { CollectionMintData } from "./event";

const contract_address = process.env.CONTRACT_ADDRESS || "d4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3";

const fun_mint_hero = toMoveFunctionId(`${contract_address}::hero::mint_hero`);

function toMoveFunctionId(str: string): MoveFunctionId {
    if (!str.includes("::")) throw new Error("Invalid function name format");
    const parts = str.split("::");
    if (parts.length !== 3) throw new Error("Function name must have 3 parts");
    if (!AccountAddress.isValid({ input: parts[0], strict: true })) throw new Error("Contract address is not valid.");
    return str as MoveFunctionId;
}

async function mintNFT(endless: Endless, mgrSigner: Account, transaction: SimpleTransaction): Promise<string | null> {
    let committedTxn = null;
    let result = null;
    try {
        // Sign and submit the transaction
        committedTxn = await endless.signAndSubmitTransaction({
            signer: mgrSigner,
            transaction: transaction,
        });
        console.log("Transaction submitted, hash:", committedTxn.hash);
    } catch (error: any) {
        console.error("committedTxn error:", error.message);
        return null;
    }

    try {
        // Wait for transaction confirmation
        const response = await endless.waitForTransaction({ transactionHash: committedTxn.hash });
        const response_str = JSON.stringify(response);
        console.log("Transaction confirmed:", response_str);
        if (isUserTransactionResponse(response) && response.vm_status == "Executed successfully") {
            for (const event of response.events) {
                if (event.type === "0x4::collection::Mint") {
                    const data = event.data as CollectionMintData;
                    return data.token;
                }
            }
        }
    } catch (error: any) {
        console.error("waitForTransaction error:", error.message);
        return null;
    }

    return result;
}

export async function mintHeroAndGetId(
    endless: Endless,
    mgrSigner: Account,
    heroName: string,
    heroDescription: string,
    heroUri: string,
    heroGender: string,
    heroRace: string
): Promise<string | null> {
    try {
        const transaction = await endless.transaction.build.simple({
            sender: mgrSigner.accountAddress,
            data: {
                function: fun_mint_hero,
                typeArguments: [],
                functionArguments: [
                    heroDescription,
                    heroGender,
                    heroName,
                    heroRace,
                    heroUri,
                ],
            },
        });
        return await mintNFT(endless, mgrSigner, transaction);
    } catch (error) {
        console.error("Error during NFT minting:", error);
        return null;
    }
}

```

* Add mint hero entry function in index.ts.

```
import "dotenv/config";
import {
    Endless,
    EndlessConfig,
    Network,
    NetworkToNetworkName,
} from "@endlesslab/endless-ts-sdk";

import { createOrLoadAccount, fundAccount } from './web3/account'
import { mintHeroAndGetId } from "./web3/nft";
// Verify environment variables are loaded
console.log("Environment variables loaded:", {
    ENDLESS_NETWORK: process.env.ENDLESS_NETWORK || "not set"
});

// console.log("Step 1: Setting up a client to connect to Aptos");
const ENDLESS_NETWORK = NetworkToNetworkName[process.env.ENDLESS_NETWORK!] || Network.DEVNET;
const config = new EndlessConfig({ network: ENDLESS_NETWORK });
const endless = new Endless(config);

const command = process.argv[2];

async function create_or_load_account() {
    const name = process.argv[3];
    if (name == null) {
        create_or_load_account_help()
        return;
    }
    await createOrLoadAccount(name);
    return;
}

function create_or_load_account_help() {
    console.log("1.[");
    console.log("\x1b[32mcommand:\x1b[0m");
    console.log("create_or_load_account");
    console.log("\x1b[32mparameters:\x1b[0m");
    console.log("account_name");
    console.log("\x1b[32mexample:\x1b[0m");
    console.log("npx ts-node index.ts create_or_load_account user1");
    console.log("]");
}


async function fund_account() {
    const name = process.argv[3];
    if (name == null) {
        fund_account_help()
        return;
    }
    const account = await createOrLoadAccount(name);
    await fundAccount(endless, account);
    return;
}

function fund_account_help() {
    console.log("2.[");
    console.log("\x1b[32mcommand:\x1b[0m");
    console.log("fund_account");
    console.log("\x1b[32mparameters:\x1b[0m");
    console.log("account_name");
    console.log("\x1b[32mexample:\x1b[0m");
    console.log("npx ts-node index.ts fund_account user1");
    console.log("]");
}

async function mint_hero() {
    const creator = process.argv[3];
    const heroName = process.argv[4];
    const heroDescription = process.argv[5];
    const heroUri = process.argv[6];
    const heroGender = process.argv[7];
    const heroRace = process.argv[8];
    if (creator == null || heroName == null || heroDescription == null
        || heroUri == null || heroGender == null || heroRace == null
    ) {
        mint_hero_help()
        return;
    }
    const creator_account = await createOrLoadAccount(creator);
    const id = await mintHeroAndGetId(endless, creator_account, heroName, heroDescription, heroUri, heroGender, heroRace);
    console.log(id);
}


function mint_hero_help() {
    console.log("3.[");
    console.log("\x1b[32mcommand:\x1b[0m");
    console.log("mint_hero");
    console.log("\x1b[32mparameters:\x1b[0m");
    console.log("creator heroName heroDescription heroUri heroGender heroRace");
    console.log("\x1b[32mexample:\x1b[0m");
    console.log("npx ts-node index.ts mint_hero mgr heroName heroDescription heroUri heroGender heroRace");
    console.log("]");
}

function help() {
    create_or_load_account_help();
    fund_account_help();
    mint_hero_help();
}

async function main() {
    if (command === "help" || command == null) {
        help();
    } else {
        switch (command) {
            case "create_or_load_account": {
                await create_or_load_account();
            }
                break;
            case "fund_account": {
                await fund_account();
            }
                break;
            case "mint_hero": {
                await mint_hero();
            }
        }
    }
}

main();

```

* Configure contract Creator's file. create mgr.json . "address" is account field in move.endless\config.yaml "private\_key" is private\_key field in move.endless\config.yaml

```
{
  "address": "0xd4a2aa8290cde4791919c6f0b74d00464e598104dcc2cd3158ae1d7d6e5b91b3",
  "private_key": "0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

* Mint a hero nft. H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ is hero nft's object ID(Address).

```
npx ts-node index.ts mint_hero mgr test_heroName heroDescription heroUri heroGender heroRace
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0xefa136421b0807116e6bac457d0d8fb297630541b284629dcf97733d8afbac0a
H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ
```

#### 5.3 Complete the remaining nft interaction functions <a href="#id-53-complete-the-remaining-nft-interaction-functions" id="id-53-complete-the-remaining-nft-interaction-functions"></a>

According to the process of 5.2, we can complete the remaining functions of web3/nft.ts and index.ts. The complete code implementation can be obtained in the code repository.

### 6 Interact with contracts (Exapmle) <a href="#id-6-interact-with-contracts-exapmle" id="id-6-interact-with-contracts-exapmle"></a>

#### 6.1 Prepare <a href="#id-61-prepare" id="id-61-prepare"></a>

According to the above steps, you will have two json files and a hero nft:

* mgr.json: representing the nft contract
* alice.json: representing a player.
* hero nft: H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ,You can check it out at: <https://scan.endless.link/nft/H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ?network=testnet>

You can find it's attribute and resources in <https://scan.endless.link/object/H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ/resources?network=testnet> 's Resources section.

```
Type:

FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Hero
{
  "armor": {
    "vec": []
  },
  "gender": "heroGender",
  "mutator_ref": {
    "self": "H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ"
  },
  "race": "heroRace",
  "shield": {
    "vec": []
  },
  "weapon": {
    "vec": []
  }
}

Type:

0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ",
        "creation_num": "1125899906842624"
      }
    }
  }
}

```

new hero's armor,shield,weapon is null.

#### 6.2 Mint a weapon and gem <a href="#id-62-mint-a-weapon-and-gem" id="id-62-mint-a-weapon-and-gem"></a>

**6.2.1 Mint a weapon**

```
npx ts-node index.ts mint_weapon mgr weaponName weaponDescription weaponUri weaponType 2000 20
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0x932dc6af2e7cf89f96cc3e0b38d2bd98068f462710161dd82f490e3ac3e3ab57
......
AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon
```

Mint weapon NFT address is AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon

You can find it's Attribute in <https://scan.endless.link/object/AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon/resources?network=testnet> 's Resources section.

```
Type:

FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Weapon
{
  "attack": "2000",
  "gem": {
    "vec": []
  },
  "weapon_type": "weaponType",
  "weight": "20"
}

Type:

0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon",
        "creation_num": "1125899906842624"
      }
    }
  }
}

```

new weapon's gem is null.

**6.2.2 Mint a gem**

```
npx ts-node index.ts mint_gem mgr gemName_003 gemDescription_001 gemUri 300 2000 gemMagicAttribute
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0x035ab7d7f68701dce84ce75d5359cb94f1baafa3bad04814c8c960d5e74d265c
......
EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP
```

Mint gem NFT address is EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP.

You can find it's Attribute in <https://scan.endless.link/object/EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP/resources?network=testnet> 's Resources section.

```
Type:

FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Gem
{
  "attack_modifier": "300",
  "defense_modifier": "2000",
  "magic_attribute": "gemMagicAttribute"
}

Type:

0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP",
        "creation_num": "1125899906842624"
      }
    }
  }
}
```

#### 6.3 Transfer nft <a href="#id-63-transfer-nft" id="id-63-transfer-nft"></a>

**6.3.1 NFT Owner**

NFTs mentioned in 5.1 and 5.2(hero,weapon and gem),You can see in the blockchain browser that they all belong to the hero contract FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c (bs58 format of mgr address)。The initial owner of the minted NFT is it. Now we can transfer the minted NFT to alice(8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW)。

**6.3.2 Transfer NFT to Alice**

Transfer hero(H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ),weapon(AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon) and gem(EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP) to alice

```
npx ts-node index.ts transfer_nft mgr H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ 8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0x25bfd4f15107a8aa025c2e841440d2a94220ab7f72bba991c8330ec5ee166277

npx ts-node index.ts transfer_nft mgr AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon 8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW 
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0x0e02fb6a520251c5e3203cdc3a1b47c0c2d0f7343d1c2613fd7aa4eccf6f4e21

npx ts-node index.ts transfer_nft mgr EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP 8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from mgr.json.
✅ Deserialization successful: Parsed mgr.json contents into an object.
Transaction submitted, hash: 0x471d46bfcd424e1a3b9164614a80750fc66e0007a49dd805b3f95e893d2d7693
```

Now you can see on the browser that the owner of these three nfts has become alice(8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW).

#### 6.4 Equip and unequip <a href="#id-64-equip-and-unequip" id="id-64-equip-and-unequip"></a>

**6.4.1 Weapon equip gem**

```
npx ts-node index.ts weapon_equip_gem alice AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from alice.json.
✅ Deserialization successful: Parsed alice.json contents into an object.
Transaction submitted, hash: 0x5d23295c1dc599e6cc7386c27b68218884ce2a296b17a37aac5974a6b6bc7851
```

After this transaction is executed, gem's owner is changed to weapon.

```
#gem
#https://scan.endless.link/object/EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP/resources?network=testnet

Type:   0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP",
        "creation_num": "1125899906842624"
      }
    }
  }
}
```

Weapon’s resources data is also changed."gem" filed is fill's gem nft's address EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP.

```
#weapon
#https://scan.endless.link/object/AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon/resources?network=testnet

Type:   FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Weapon
{
  "attack": "2000",
  "gem": {
    "vec": [
      {
        "inner": "EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP"
      }
    ]
  },
  "weapon_type": "weaponType",
  "weight": "20"
}
```

**6.4.2 Hero equip weapon**

```
>npx ts-node index.ts hero_equip_weapon alice H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ  AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon  
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from alice.json.
✅ Deserialization successful: Parsed alice.json contents into an object.
Transaction submitted, hash: 0x31e87d5694f572f586545910e40f42e5520c2e3ee2fb9320ffbbe11bf27e29ea
```

Weapon's owner change to hero

```
#weapon
#https://scan.endless.link/object/AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon/resources?network=testnet

Type:   0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon",
        "creation_num": "1125899906842624"
      }
    }
  }
}

```

Hero's weapon is "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon"

```
#hero
#https://scan.endless.link/object/H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ/resources?network=testnet

Type:   FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Hero
{
  "armor": {
    "vec": []
  },
  "gender": "heroGender",
  "mutator_ref": {
    "self": "H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ"
  },
  "race": "heroRace",
  "shield": {
    "vec": []
  },
  "weapon": {
    "vec": [
      {
        "inner": "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon"
      }
    ]
  }
}
```

**6.4.3 Hero unequip weapon**

```
npx ts-node index.ts hero_unequip_weapon alice H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ  AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from alice.json.
✅ Deserialization successful: Parsed alice.json contents into an object.
Transaction submitted, hash: 0x4d38fd8d473ea1795848a2cd21796fee180451adfc0f83a879d739b2f6bac4a7
```

weapon's owner is change again alice and hero's weapon is empty.

```
#weapon
#https://scan.endless.link/object/AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon/resources?network=testnet

Type:   0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon",
        "creation_num": "1125899906842624"
      }
    }
  }
}
```

```
#hero
#https://scan.endless.link/object/H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ/resources?network=testnet

Type:   FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Hero
{
  "armor": {
    "vec": []
  },
  "gender": "heroGender",
  "mutator_ref": {
    "self": "H9ZVsV1rME62Fr8yYGbb2DxekDFcUm9H2p3j2QFE8mTJ"
  },
  "race": "heroRace",
  "shield": {
    "vec": []
  },
  "weapon": {
    "vec": []
  }
}
```

**6.4.4 Weapon unquip gem**

```
npx ts-node index.ts weapon_unequip_gem alice AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP
Environment variables loaded: { ENDLESS_NETWORK: 'testnet' }
✅ Asynchronous read succeeded: Read file contents from alice.json.
✅ Deserialization successful: Parsed alice.json contents into an object.
Transaction submitted, hash: 0x17652893b2abcc00fac2fbec1aea831bb5469c518c857cf72dca2f42ba2cdf98
```

gem's owner is change again alice and weapon's gem is empty.

```
#gem
#https://scan.endless.link/object/EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP/resources?network=testnet

Type:   0x1::object::ObjectCore
{
  "allow_ungated_transfer": true,
  "guid_creation_num": "1125899906842625",
  "owner": "8wTNScjbYY4Gz4CbRa3oYmjBGCbTRekWDHJr4sHTYahW",
  "transfer_events": {
    "counter": "0",
    "guid": {
      "id": {
        "addr": "EiCX3rtKrhcXZsbeey1dqD5jeDPodcvqaWxG7JDKePoP",
        "creation_num": "1125899906842624"
      }
    }
  }
}
```

```
#weapon
#https://scan.endless.link/object/AAsAGWsPCLyn3Gx5b6hPixYWc1vM67YogwDNgx3mXyon/resources?network=testnet

Type:  FK3J64indQEVV42Qz1a1s38o4bx6DaZxyWL4WygKvc7c::hero::Weapon
{
  "attack": "2000",
  "gem": {
    "vec": []
  },
  "weapon_type": "weaponType",
  "weight": "20"
}
```

### 7 Summary <a href="#id-7-summary" id="id-7-summary"></a>

Through the example above, we have demonstrated from scratch how to interact with Endless using TypeScript. The content covers account creation, contract development and deployment, as well as how to call contracts. In developing these contracts, we leveraged Endless' NFT functionalities, including the processes of minting, transferring, and nesting NFTs. We hope this provides helpful insights into understanding Endless contracts and their interactions. Thank you.


---

# 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/show-cases/game.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.
